[ty] Apply class decorators via try_call() (#22375)
## Summary Decorators are now called with the class as an argument, and the return type becomes the class's type. This mirrors how function decorators already work. Closes https://github.com/astral-sh/ty/issues/2313.
This commit is contained in:
@@ -2797,6 +2797,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
body: _,
|
||||
} = class_node;
|
||||
|
||||
let mut decorator_types_and_nodes: Vec<(Type<'db>, &ast::Decorator)> =
|
||||
Vec::with_capacity(decorator_list.len());
|
||||
let mut deprecated = None;
|
||||
let mut type_check_only = false;
|
||||
let mut dataclass_params = None;
|
||||
@@ -2831,6 +2833,20 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip identity decorators to avoid salsa cycles on typeshed.
|
||||
if decorator_ty.as_function_literal().is_some_and(|function| {
|
||||
matches!(
|
||||
function.known(self.db()),
|
||||
Some(
|
||||
KnownFunction::Final
|
||||
| KnownFunction::DisjointBase
|
||||
| KnownFunction::RuntimeCheckable
|
||||
)
|
||||
)
|
||||
}) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Type::FunctionLiteral(f) = decorator_ty {
|
||||
// We do not yet detect or flag `@dataclass_transform` applied to more than one
|
||||
// overload, or an overload and the implementation both. Nevertheless, this is not
|
||||
@@ -2852,6 +2868,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
dataclass_transformer_params = Some(params);
|
||||
continue;
|
||||
}
|
||||
|
||||
decorator_types_and_nodes.push((decorator_ty, decorator));
|
||||
}
|
||||
|
||||
let body_scope = self
|
||||
@@ -2868,7 +2886,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
)
|
||||
};
|
||||
|
||||
let ty = match (maybe_known_class, &*name.id) {
|
||||
let inferred_ty = match (maybe_known_class, &*name.id) {
|
||||
(None, "NamedTuple") if in_typing_module() => {
|
||||
Type::SpecialForm(SpecialFormType::NamedTuple)
|
||||
}
|
||||
@@ -2885,10 +2903,19 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
)),
|
||||
};
|
||||
|
||||
// Validate decorator calls (but don't use return types yet).
|
||||
for (decorator_ty, decorator_node) in decorator_types_and_nodes.iter().rev() {
|
||||
if let Err(CallError(_, bindings)) =
|
||||
decorator_ty.try_call(self.db(), &CallArguments::positional([inferred_ty]))
|
||||
{
|
||||
bindings.report_diagnostics(&self.context, (*decorator_node).into());
|
||||
}
|
||||
}
|
||||
|
||||
self.add_declaration_with_binding(
|
||||
class_node.into(),
|
||||
definition,
|
||||
&DeclaredAndInferredType::are_the_same_type(ty),
|
||||
&DeclaredAndInferredType::are_the_same_type(inferred_ty),
|
||||
);
|
||||
|
||||
// if there are type parameters, then the keywords and bases are within that scope
|
||||
|
||||
Reference in New Issue
Block a user