From 3ceb3ca220902c0ae9f3a867b52eefbd9062a558 Mon Sep 17 00:00:00 2001 From: Asuka Minato Date: Wed, 17 Jun 2026 15:43:04 +0900 Subject: [PATCH] fix --- crates/pyrefly_config/src/error_kind.rs | 2 ++ pyrefly/lib/alt/call.rs | 43 +++++++++++++++++++++++++ pyrefly/lib/test/calls.rs | 15 +++++++++ website/docs/error-kinds.mdx | 13 ++++++++ 4 files changed, 73 insertions(+) diff --git a/crates/pyrefly_config/src/error_kind.rs b/crates/pyrefly_config/src/error_kind.rs index 3dea6f55f3..99b20b9b62 100644 --- a/crates/pyrefly_config/src/error_kind.rs +++ b/crates/pyrefly_config/src/error_kind.rs @@ -366,6 +366,8 @@ pub enum ErrorKind { Unsupported, /// Attempting to `del` something that cannot be deleted UnsupportedDelete, + /// A dynamically created class has a base that cannot be statically resolved. + UnsupportedDynamicBase, /// Attempting to apply an operation to arguments that do not support it. UnsupportedOperation, /// Import is missing an expected stubs package diff --git a/pyrefly/lib/alt/call.rs b/pyrefly/lib/alt/call.rs index 547c19618e..54f5aa9367 100644 --- a/pyrefly/lib/alt/call.rs +++ b/pyrefly/lib/alt/call.rs @@ -1248,6 +1248,43 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> { } } + fn check_dynamic_type_bases(&self, bases: &Expr, errors: &ErrorCollector) { + let Expr::Tuple(tuple) = bases else { + self.error( + errors, + bases.range(), + ErrorKind::UnsupportedDynamicBase, + "Base classes in `type()` calls must be a tuple literal of statically known classes" + .to_owned(), + ); + return; + }; + for base in &tuple.elts { + if matches!(base, Expr::Starred(_)) { + self.error( + errors, + base.range(), + ErrorKind::UnsupportedDynamicBase, + "Base classes in `type()` calls cannot use unpacking".to_owned(), + ); + continue; + } + let base_ty = self.expr_infer(base, errors); + if base_ty.is_any() || base_ty.is_error() || matches!(base_ty, Type::ClassDef(_)) { + continue; + } + self.error( + errors, + base.range(), + ErrorKind::UnsupportedDynamicBase, + format!( + "Base class `{}` in `type()` call is not a statically known class", + self.for_display(base_ty) + ), + ); + } + } + fn call_infer_with_callee_range( &self, call_target: CallTarget, @@ -2004,6 +2041,12 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> { let arg_ty = self.expr_infer(&x.arguments.args[0], errors); self.type_of(arg_ty) } + _ if matches!(ty, Type::ClassDef(cls) if cls == self.stdlib.builtins_type().class_object()) + && x.arguments.args.len() == 3 => + { + self.check_dynamic_type_bases(&x.arguments.args[1], errors); + self.freeform_call_infer(ty.clone(), &args, &kws, x.func.range(), x.arguments.range(), hint, errors) + } // Decorators can be applied in two ways: // - (common, idiomatic) via `@decorator`: // @staticmethod diff --git a/pyrefly/lib/test/calls.rs b/pyrefly/lib/test/calls.rs index 31776cfc3d..6ac836eb08 100644 --- a/pyrefly/lib/test/calls.rs +++ b/pyrefly/lib/test/calls.rs @@ -129,6 +129,21 @@ take_callable(old_function) # E: `old_function` is deprecated "#, ); +testcase!( + test_type_call_dynamic_base, + r#" +class Base: ... + +def factory(base: type[Base]) -> type: + return type("Dynamic", (base,), {}) # E: Base class `type[Base]` in `type()` call is not a statically known class + +type("Static", (Base,), {}) + +bases = (Base,) +type("AlsoDynamic", bases, {}) # E: Base classes in `type()` calls must be a tuple literal of statically known classes +"#, +); + testcase!( test_deprecated_method_call, r#" diff --git a/website/docs/error-kinds.mdx b/website/docs/error-kinds.mdx index 8da9026fc0..7b1c445db3 100644 --- a/website/docs/error-kinds.mdx +++ b/website/docs/error-kinds.mdx @@ -1773,6 +1773,19 @@ This error occurs when attempting to `del` something that cannot be deleted. Besides obvious things like built-in values (you can't `del True`!), some object attributes are protected from deletion. For example, read-only and required `TypedDict` fields cannot be deleted. +## unsupported-dynamic-base + +This error is raised for dynamic class definitions created with `type()` when +the base classes are not statically known class literals. + +```python +class Base: ... + +def factory(base: type[Base]) -> type: + return type("Dynamic", (base,), {}) + # Base class `type[Base]` in `type()` call is not a statically known class [unsupported-dynamic-base] +``` + ## unsupported-operation This error arises when attempting to perform an operation between values of two incompatible types.