Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/pyrefly_config/src/error_kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
43 changes: 43 additions & 0 deletions pyrefly/lib/alt/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions pyrefly/lib/test/calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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#"
Expand Down
13 changes: 13 additions & 0 deletions website/docs/error-kinds.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading