mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 05:44:56 +00:00
[ty] Allow annotation expressions to be ast::Attribute
nodes (#20413)
Fixes https://github.com/astral-sh/ty/issues/1187
This commit is contained in:
parent
1f1365a0fa
commit
8341da7f63
2 changed files with 65 additions and 44 deletions
|
@ -9,6 +9,7 @@ For more details on the semantics of pure class variables, see [this test](../at
|
||||||
## Basic
|
## Basic
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
import typing
|
||||||
from typing import ClassVar, Annotated
|
from typing import ClassVar, Annotated
|
||||||
|
|
||||||
class C:
|
class C:
|
||||||
|
@ -17,12 +18,14 @@ class C:
|
||||||
c: ClassVar[Annotated[int, "the annotation for c"]] = 1
|
c: ClassVar[Annotated[int, "the annotation for c"]] = 1
|
||||||
d: ClassVar = 1
|
d: ClassVar = 1
|
||||||
e: "ClassVar[int]" = 1
|
e: "ClassVar[int]" = 1
|
||||||
|
f: typing.ClassVar = 1
|
||||||
|
|
||||||
reveal_type(C.a) # revealed: int
|
reveal_type(C.a) # revealed: int
|
||||||
reveal_type(C.b) # revealed: int
|
reveal_type(C.b) # revealed: int
|
||||||
reveal_type(C.c) # revealed: int
|
reveal_type(C.c) # revealed: int
|
||||||
reveal_type(C.d) # revealed: Unknown | Literal[1]
|
reveal_type(C.d) # revealed: Unknown | Literal[1]
|
||||||
reveal_type(C.e) # revealed: int
|
reveal_type(C.e) # revealed: int
|
||||||
|
reveal_type(C.f) # revealed: Unknown | Literal[1]
|
||||||
|
|
||||||
c = C()
|
c = C()
|
||||||
|
|
||||||
|
@ -36,6 +39,8 @@ c.c = 2
|
||||||
c.d = 2
|
c.d = 2
|
||||||
# error: [invalid-attribute-access]
|
# error: [invalid-attribute-access]
|
||||||
c.e = 2
|
c.e = 2
|
||||||
|
# error: [invalid-attribute-access]
|
||||||
|
c.f = 3
|
||||||
```
|
```
|
||||||
|
|
||||||
## From stubs
|
## From stubs
|
||||||
|
|
|
@ -42,6 +42,53 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
&mut self,
|
&mut self,
|
||||||
annotation: &ast::Expr,
|
annotation: &ast::Expr,
|
||||||
) -> TypeAndQualifiers<'db> {
|
) -> TypeAndQualifiers<'db> {
|
||||||
|
fn infer_name_or_attribute<'db>(
|
||||||
|
ty: Type<'db>,
|
||||||
|
annotation: &ast::Expr,
|
||||||
|
builder: &TypeInferenceBuilder<'db, '_>,
|
||||||
|
) -> TypeAndQualifiers<'db> {
|
||||||
|
match ty {
|
||||||
|
Type::SpecialForm(SpecialFormType::ClassVar) => {
|
||||||
|
TypeAndQualifiers::new(Type::unknown(), TypeQualifiers::CLASS_VAR)
|
||||||
|
}
|
||||||
|
Type::SpecialForm(SpecialFormType::Final) => {
|
||||||
|
TypeAndQualifiers::new(Type::unknown(), TypeQualifiers::FINAL)
|
||||||
|
}
|
||||||
|
Type::SpecialForm(SpecialFormType::Required) => {
|
||||||
|
TypeAndQualifiers::new(Type::unknown(), TypeQualifiers::REQUIRED)
|
||||||
|
}
|
||||||
|
Type::SpecialForm(SpecialFormType::NotRequired) => {
|
||||||
|
TypeAndQualifiers::new(Type::unknown(), TypeQualifiers::NOT_REQUIRED)
|
||||||
|
}
|
||||||
|
Type::SpecialForm(SpecialFormType::ReadOnly) => {
|
||||||
|
TypeAndQualifiers::new(Type::unknown(), TypeQualifiers::READ_ONLY)
|
||||||
|
}
|
||||||
|
Type::ClassLiteral(class) if class.is_known(builder.db(), KnownClass::InitVar) => {
|
||||||
|
if let Some(builder) =
|
||||||
|
builder.context.report_lint(&INVALID_TYPE_FORM, annotation)
|
||||||
|
{
|
||||||
|
builder
|
||||||
|
.into_diagnostic("`InitVar` may not be used without a type argument");
|
||||||
|
}
|
||||||
|
TypeAndQualifiers::new(Type::unknown(), TypeQualifiers::INIT_VAR)
|
||||||
|
}
|
||||||
|
_ => ty
|
||||||
|
.in_type_expression(
|
||||||
|
builder.db(),
|
||||||
|
builder.scope(),
|
||||||
|
builder.typevar_binding_context,
|
||||||
|
)
|
||||||
|
.unwrap_or_else(|error| {
|
||||||
|
error.into_fallback_type(
|
||||||
|
&builder.context,
|
||||||
|
annotation,
|
||||||
|
builder.is_reachable(annotation),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// https://typing.python.org/en/latest/spec/annotations.html#grammar-token-expression-grammar-annotation_expression
|
// https://typing.python.org/en/latest/spec/annotations.html#grammar-token-expression-grammar-annotation_expression
|
||||||
let annotation_ty = match annotation {
|
let annotation_ty = match annotation {
|
||||||
// String annotations: https://typing.python.org/en/latest/spec/annotations.html#string-annotations
|
// String annotations: https://typing.python.org/en/latest/spec/annotations.html#string-annotations
|
||||||
|
@ -68,52 +115,21 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
TypeAndQualifiers::unknown()
|
TypeAndQualifiers::unknown()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ast::Expr::Attribute(attribute) => match attribute.ctx {
|
||||||
|
ast::ExprContext::Load => infer_name_or_attribute(
|
||||||
|
self.infer_attribute_expression(attribute),
|
||||||
|
annotation,
|
||||||
|
self,
|
||||||
|
),
|
||||||
|
ast::ExprContext::Invalid => TypeAndQualifiers::unknown(),
|
||||||
|
ast::ExprContext::Store | ast::ExprContext::Del => {
|
||||||
|
todo_type!("Attribute expression annotation in Store/Del context").into()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
ast::Expr::Name(name) => match name.ctx {
|
ast::Expr::Name(name) => match name.ctx {
|
||||||
ast::ExprContext::Load => {
|
ast::ExprContext::Load => {
|
||||||
let name_expr_ty = self.infer_name_expression(name);
|
infer_name_or_attribute(self.infer_name_expression(name), annotation, self)
|
||||||
match name_expr_ty {
|
|
||||||
Type::SpecialForm(SpecialFormType::ClassVar) => {
|
|
||||||
TypeAndQualifiers::new(Type::unknown(), TypeQualifiers::CLASS_VAR)
|
|
||||||
}
|
|
||||||
Type::SpecialForm(SpecialFormType::Final) => {
|
|
||||||
TypeAndQualifiers::new(Type::unknown(), TypeQualifiers::FINAL)
|
|
||||||
}
|
|
||||||
Type::SpecialForm(SpecialFormType::Required) => {
|
|
||||||
TypeAndQualifiers::new(Type::unknown(), TypeQualifiers::REQUIRED)
|
|
||||||
}
|
|
||||||
Type::SpecialForm(SpecialFormType::NotRequired) => {
|
|
||||||
TypeAndQualifiers::new(Type::unknown(), TypeQualifiers::NOT_REQUIRED)
|
|
||||||
}
|
|
||||||
Type::SpecialForm(SpecialFormType::ReadOnly) => {
|
|
||||||
TypeAndQualifiers::new(Type::unknown(), TypeQualifiers::READ_ONLY)
|
|
||||||
}
|
|
||||||
Type::ClassLiteral(class)
|
|
||||||
if class.is_known(self.db(), KnownClass::InitVar) =>
|
|
||||||
{
|
|
||||||
if let Some(builder) =
|
|
||||||
self.context.report_lint(&INVALID_TYPE_FORM, annotation)
|
|
||||||
{
|
|
||||||
builder.into_diagnostic(
|
|
||||||
"`InitVar` may not be used without a type argument",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
TypeAndQualifiers::new(Type::unknown(), TypeQualifiers::INIT_VAR)
|
|
||||||
}
|
|
||||||
_ => name_expr_ty
|
|
||||||
.in_type_expression(
|
|
||||||
self.db(),
|
|
||||||
self.scope(),
|
|
||||||
self.typevar_binding_context,
|
|
||||||
)
|
|
||||||
.unwrap_or_else(|error| {
|
|
||||||
error.into_fallback_type(
|
|
||||||
&self.context,
|
|
||||||
annotation,
|
|
||||||
self.is_reachable(annotation),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.into(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ast::ExprContext::Invalid => TypeAndQualifiers::unknown(),
|
ast::ExprContext::Invalid => TypeAndQualifiers::unknown(),
|
||||||
ast::ExprContext::Store | ast::ExprContext::Del => {
|
ast::ExprContext::Store | ast::ExprContext::Del => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue