[red-knot] Add support for typing.ClassVar (#15550)

## Summary

Add support for `typing.ClassVar`, i.e. emit a diagnostic in this
scenario:
```py
from typing import ClassVar

class C:
    x: ClassVar[int] = 1

c = C()
c.x = 3  # error: "Cannot assign to pure class variable `x` from an instance of type `C`"
```

## Test Plan

- New tests for the `typing.ClassVar` qualifier
- Fixed one TODO in `attributes.md`
This commit is contained in:
David Peter
2025-01-18 13:51:35 +01:00
committed by GitHub
parent 9730ff3a25
commit fb15da5694
5 changed files with 499 additions and 114 deletions

View File

@@ -169,6 +169,10 @@ class C:
pure_class_variable1: ClassVar[str] = "value in class body"
pure_class_variable2: ClassVar = 1
def method(self):
# TODO: this should be an error
self.pure_class_variable1 = "value set through instance"
reveal_type(C.pure_class_variable1) # revealed: str
# TODO: this should be `Literal[1]`, or `Unknown | Literal[1]`.
@@ -182,7 +186,7 @@ reveal_type(c_instance.pure_class_variable1) # revealed: str
# TODO: Should be `Unknown | Literal[1]`.
reveal_type(c_instance.pure_class_variable2) # revealed: Unknown
# TODO: should raise an error. It is not allowed to reassign a pure class variable on an instance.
# error: [invalid-attribute-access] "Cannot assign to ClassVar `pure_class_variable1` from an instance of type `C`"
c_instance.pure_class_variable1 = "value set on instance"
C.pure_class_variable1 = "overwritten on class"

View File

@@ -0,0 +1,93 @@
# `typing.ClassVar`
[`typing.ClassVar`] is a type qualifier that is used to indicate that a class variable may not be
written to from instances of that class.
This test makes sure that we discover the type qualifier while inferring types from an annotation.
For more details on the semantics of pure class variables, see [this test](../attributes.md).
## Basic
```py
from typing import ClassVar, Annotated
class C:
a: ClassVar[int] = 1
b: Annotated[ClassVar[int], "the annotation for b"] = 1
c: ClassVar[Annotated[int, "the annotation for c"]] = 1
d: ClassVar = 1
e: "ClassVar[int]" = 1
reveal_type(C.a) # revealed: int
reveal_type(C.b) # revealed: int
reveal_type(C.c) # revealed: int
# TODO: should be Unknown | Literal[1]
reveal_type(C.d) # revealed: Unknown
reveal_type(C.e) # revealed: int
c = C()
# error: [invalid-attribute-access]
c.a = 2
# error: [invalid-attribute-access]
c.b = 2
# error: [invalid-attribute-access]
c.c = 2
# error: [invalid-attribute-access]
c.d = 2
# error: [invalid-attribute-access]
c.e = 2
```
## Conflicting type qualifiers
We currently ignore conflicting qualifiers and simply union them, which is more conservative than
intersecting them. This means that we consider `a` to be a `ClassVar` here:
```py
from typing import ClassVar
def flag() -> bool:
return True
class C:
if flag():
a: ClassVar[int] = 1
else:
a: str
reveal_type(C.a) # revealed: int | str
c = C()
# error: [invalid-attribute-access]
c.a = 2
```
## Too many arguments
```py
class C:
# error: [invalid-type-form] "Type qualifier `typing.ClassVar` expects exactly one type parameter"
x: ClassVar[int, str] = 1
```
## Illegal `ClassVar` in type expression
```py
class C:
# error: [invalid-type-form] "Type qualifier `typing.ClassVar` is not allowed in type expressions (only in annotation expressions)"
x: ClassVar | int
# error: [invalid-type-form] "Type qualifier `typing.ClassVar` is not allowed in type expressions (only in annotation expressions)"
y: int | ClassVar[str]
```
## Used outside of a class
```py
# TODO: this should be an error
x: ClassVar[int] = 1
```
[`typing.classvar`]: https://docs.python.org/3/library/typing.html#typing.ClassVar

View File

@@ -1,6 +1,7 @@
use std::hash::Hash;
use std::iter;
use bitflags::bitflags;
use context::InferContext;
use diagnostic::{report_not_iterable, report_not_iterable_possibly_unbound};
use indexmap::IndexSet;
@@ -92,7 +93,7 @@ fn symbol<'db>(db: &'db dyn Db, scope: ScopeId<'db>, name: &str) -> Symbol<'db>
// on inference from bindings.
let declarations = use_def.public_declarations(symbol);
let declared = declarations_ty(db, declarations);
let declared = declarations_ty(db, declarations).map(|SymbolAndQualifiers(ty, _)| ty);
match declared {
// Symbol is declared, trust the declared type
@@ -126,7 +127,7 @@ fn symbol<'db>(db: &'db dyn Db, scope: ScopeId<'db>, name: &str) -> Symbol<'db>
Err((declared_ty, _)) => {
// Intentionally ignore conflicting declared types; that's not our problem,
// it's the problem of the module we are importing from.
declared_ty.into()
declared_ty.inner_ty().into()
}
}
@@ -246,7 +247,7 @@ pub(crate) fn binding_ty<'db>(db: &'db dyn Db, definition: Definition<'db>) -> T
}
/// Infer the type of a declaration.
fn declaration_ty<'db>(db: &'db dyn Db, definition: Definition<'db>) -> Type<'db> {
fn declaration_ty<'db>(db: &'db dyn Db, definition: Definition<'db>) -> TypeAndQualifiers<'db> {
let inference = infer_definition_types(db, definition);
inference.declaration_ty(definition)
}
@@ -356,22 +357,50 @@ fn bindings_ty<'db>(
}
}
/// A type with declaredness information, and a set of type qualifiers.
///
/// This is used to represent the result of looking up the declared type. Consider this
/// example:
/// ```py
/// class C:
/// if flag:
/// variable: ClassVar[int]
/// ```
/// If we look up the declared type of `variable` in the scope of class `C`, we will get
/// the type `int`, a "declaredness" of [`Boundness::PossiblyUnbound`], and the information
/// that this comes with a [`TypeQualifiers::CLASS_VAR`] type qualifier.
pub(crate) struct SymbolAndQualifiers<'db>(Symbol<'db>, TypeQualifiers);
impl SymbolAndQualifiers<'_> {
fn is_class_var(&self) -> bool {
self.1.contains(TypeQualifiers::CLASS_VAR)
}
}
impl<'db> From<Symbol<'db>> for SymbolAndQualifiers<'db> {
fn from(symbol: Symbol<'db>) -> Self {
SymbolAndQualifiers(symbol, TypeQualifiers::empty())
}
}
impl<'db> From<Type<'db>> for SymbolAndQualifiers<'db> {
fn from(ty: Type<'db>) -> Self {
SymbolAndQualifiers(ty.into(), TypeQualifiers::empty())
}
}
/// The result of looking up a declared type from declarations; see [`declarations_ty`].
type DeclaredTypeResult<'db> = Result<Symbol<'db>, (Type<'db>, Box<[Type<'db>]>)>;
type DeclaredTypeResult<'db> =
Result<SymbolAndQualifiers<'db>, (TypeAndQualifiers<'db>, Box<[Type<'db>]>)>;
/// Build a declared type from a [`DeclarationsIterator`].
///
/// If there is only one declaration, or all declarations declare the same type, returns
/// `Ok(declared_type)`. If there are conflicting declarations, returns
/// `Err((union_of_declared_types, conflicting_declared_types))`.
/// `Ok(..)`. If there are conflicting declarations, returns an `Err(..)` variant with
/// a union of the declared types as well as a list of all conflicting types.
///
/// If undeclared is a possibility, `undeclared_ty` type will be part of the return type but it
/// will not be considered to be conflicting with any other types.
///
/// # Panics
/// Will panic if there are no declarations and no `undeclared_ty` is provided. This is a logic
/// error, as any symbol with zero live declarations clearly must be undeclared, and the caller
/// should provide an `undeclared_ty`.
/// This function also returns declaredness information (see [`Symbol`]) and a set of
/// [`TypeQualifiers`] that have been specified on the declaration(s).
fn declarations_ty<'db>(
db: &'db dyn Db,
declarations: DeclarationsIterator<'_, 'db>,
@@ -408,14 +437,19 @@ fn declarations_ty<'db>(
if let Some(first) = types.next() {
let mut conflicting: Vec<Type<'db>> = vec![];
let declared_ty = if let Some(second) = types.next() {
let mut builder = UnionBuilder::new(db).add(first);
let ty_first = first.inner_ty();
let mut qualifiers = first.qualifiers();
let mut builder = UnionBuilder::new(db).add(ty_first);
for other in std::iter::once(second).chain(types) {
if !first.is_equivalent_to(db, other) {
conflicting.push(other);
let other_ty = other.inner_ty();
if !ty_first.is_equivalent_to(db, other_ty) {
conflicting.push(other_ty);
}
builder = builder.add(other);
builder = builder.add(other_ty);
qualifiers = qualifiers.union(other.qualifiers());
}
builder.build()
TypeAndQualifiers::new(builder.build(), qualifiers)
} else {
first
};
@@ -428,15 +462,20 @@ fn declarations_ty<'db>(
Truthiness::Ambiguous => Boundness::PossiblyUnbound,
};
Ok(Symbol::Type(declared_ty, boundness))
Ok(SymbolAndQualifiers(
Symbol::Type(declared_ty.inner_ty(), boundness),
declared_ty.qualifiers(),
))
} else {
Err((
declared_ty,
std::iter::once(first).chain(conflicting).collect(),
std::iter::once(first.inner_ty())
.chain(conflicting)
.collect(),
))
}
} else {
Ok(Symbol::Unbound)
Ok(Symbol::Unbound.into())
}
}
@@ -1661,7 +1700,10 @@ impl<'db> Type<'db> {
(Some(KnownClass::VersionInfo), "minor") => {
Type::IntLiteral(Program::get(db).python_version(db).minor.into()).into()
}
_ => class.instance_member(db, name),
_ => {
let SymbolAndQualifiers(symbol, _) = class.instance_member(db, name);
symbol
}
},
Type::Union(union) => {
@@ -2285,11 +2327,18 @@ impl<'db> Type<'db> {
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::BareAnnotated],
fallback_type: Type::unknown(),
}),
Type::KnownInstance(KnownInstanceType::ClassVar) => {
// TODO: A bare `ClassVar` should rather be treated as if the symbol was not
// declared at all.
Ok(Type::unknown())
}
Type::KnownInstance(KnownInstanceType::ClassVar) => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec![
InvalidTypeExpression::ClassVarInTypeExpression
],
fallback_type: Type::unknown(),
}),
Type::KnownInstance(KnownInstanceType::Final) => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec![
InvalidTypeExpression::FinalInTypeExpression
],
fallback_type: Type::unknown(),
}),
Type::KnownInstance(KnownInstanceType::Literal) => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::BareLiteral],
fallback_type: Type::unknown(),
@@ -2466,6 +2515,60 @@ impl std::fmt::Display for DynamicType {
}
}
bitflags! {
/// Type qualifiers that appear in an annotation expression.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) struct TypeQualifiers: u8 {
/// `typing.ClassVar`
const CLASS_VAR = 1 << 0;
/// `typing.Final`
const FINAL = 1 << 1;
}
}
/// When inferring the type of an annotation expression, we can also encounter type qualifiers
/// such as `ClassVar` or `Final`. These do not affect the inferred type itself, but rather
/// control how a particular symbol can be accessed or modified. This struct holds a type and
/// a set of type qualifiers.
///
/// Example: `Annotated[ClassVar[tuple[int]], "metadata"]` would have type `tuple[int]` and the
/// qualifier `ClassVar`.
#[derive(Clone, Debug, Copy, Eq, PartialEq)]
pub(crate) struct TypeAndQualifiers<'db> {
inner: Type<'db>,
qualifiers: TypeQualifiers,
}
impl<'db> TypeAndQualifiers<'db> {
pub(crate) fn new(inner: Type<'db>, qualifiers: TypeQualifiers) -> Self {
Self { inner, qualifiers }
}
/// Forget about type qualifiers and only return the inner type.
pub(crate) fn inner_ty(&self) -> Type<'db> {
self.inner
}
/// Insert/add an additional type qualifier.
pub(crate) fn add_qualifier(&mut self, qualifier: TypeQualifiers) {
self.qualifiers |= qualifier;
}
/// Return the set of type qualifiers.
pub(crate) fn qualifiers(&self) -> TypeQualifiers {
self.qualifiers
}
}
impl<'db> From<Type<'db>> for TypeAndQualifiers<'db> {
fn from(inner: Type<'db>) -> Self {
Self {
inner,
qualifiers: TypeQualifiers::empty(),
}
}
}
/// Error struct providing information on type(s) that were deemed to be invalid
/// in a type expression context, and the type we should therefore fallback to
/// for the problematic type expression.
@@ -2499,6 +2602,10 @@ enum InvalidTypeExpression {
BareAnnotated,
/// `x: Literal` is invalid as an annotation
BareLiteral,
/// The `ClassVar` type qualifier was used in a type expression
ClassVarInTypeExpression,
/// The `Final` type qualifier was used in a type expression
FinalInTypeExpression,
}
impl InvalidTypeExpression {
@@ -2506,6 +2613,8 @@ impl InvalidTypeExpression {
match self {
Self::BareAnnotated => "`Annotated` requires at least two arguments when used in an annotation or type expression",
Self::BareLiteral => "`Literal` requires at least one argument when used in a type expression",
Self::ClassVarInTypeExpression => "Type qualifier `typing.ClassVar` is not allowed in type expressions (only in annotation expressions)",
Self::FinalInTypeExpression => "Type qualifier `typing.Final` is not allowed in type expressions (only in annotation expressions)",
}
}
}
@@ -3980,15 +4089,16 @@ impl<'db> Class<'db> {
/// defined attribute that is only present in a method (typically `__init__`).
///
/// The attribute might also be defined in a superclass of this class.
pub(crate) fn instance_member(self, db: &'db dyn Db, name: &str) -> Symbol<'db> {
pub(crate) fn instance_member(self, db: &'db dyn Db, name: &str) -> SymbolAndQualifiers<'db> {
for superclass in self.iter_mro(db) {
match superclass {
ClassBase::Dynamic(_) => {
return todo_type!("instance attribute on class with dynamic base").into();
}
ClassBase::Class(class) => {
let member = class.own_instance_member(db, name);
if !member.is_unbound() {
if let member @ SymbolAndQualifiers(Symbol::Type(_, _), _) =
class.own_instance_member(db, name)
{
return member;
}
}
@@ -4002,7 +4112,7 @@ impl<'db> Class<'db> {
/// A helper function for `instance_member` that looks up the `name` attribute only on
/// this class, not on its superclasses.
fn own_instance_member(self, db: &'db dyn Db, name: &str) -> Symbol<'db> {
fn own_instance_member(self, db: &'db dyn Db, name: &str) -> SymbolAndQualifiers<'db> {
// TODO: There are many things that are not yet implemented here:
// - `typing.ClassVar` and `typing.Final`
// - Proper diagnostics
@@ -4018,7 +4128,7 @@ impl<'db> Class<'db> {
let declarations = use_def.public_declarations(symbol);
match declarations_ty(db, declarations) {
Ok(Symbol::Type(declared_ty, _)) => {
Ok(SymbolAndQualifiers(Symbol::Type(declared_ty, _), qualifiers)) => {
if let Some(function) = declared_ty.into_function_literal() {
// TODO: Eventually, we are going to process all decorators correctly. This is
// just a temporary heuristic to provide a broad categorization into properties
@@ -4029,28 +4139,31 @@ impl<'db> Class<'db> {
todo_type!("bound method").into()
}
} else {
Symbol::Type(declared_ty, Boundness::Bound)
SymbolAndQualifiers(Symbol::Type(declared_ty, Boundness::Bound), qualifiers)
}
}
Ok(Symbol::Unbound) => {
Ok(SymbolAndQualifiers(Symbol::Unbound, qualifiers)) => {
let bindings = use_def.public_bindings(symbol);
let inferred_ty = bindings_ty(db, bindings);
match inferred_ty {
Symbol::Type(ty, _) => Symbol::Type(
UnionType::from_elements(db, [Type::unknown(), ty]),
Boundness::Bound,
Symbol::Type(ty, _) => SymbolAndQualifiers(
Symbol::Type(
UnionType::from_elements(db, [Type::unknown(), ty]),
Boundness::Bound,
),
qualifiers,
),
Symbol::Unbound => Symbol::Unbound,
Symbol::Unbound => SymbolAndQualifiers(Symbol::Unbound, qualifiers),
}
}
Err((declared_ty, _)) => {
Err((declared_ty, _conflicting_declarations)) => {
// Ignore conflicting declarations
declared_ty.into()
SymbolAndQualifiers(declared_ty.inner_ty().into(), declared_ty.qualifiers())
}
}
} else {
Symbol::Unbound
Symbol::Unbound.into()
}
}

View File

@@ -1,5 +1,4 @@
use super::context::InferContext;
use crate::declare_lint;
use crate::lint::{Level, LintRegistryBuilder, LintStatus};
use crate::suppression::FileSuppressionId;
use crate::types::string_annotation::{
@@ -7,7 +6,8 @@ use crate::types::string_annotation::{
IMPLICIT_CONCATENATED_STRING_TYPE_ANNOTATION, INVALID_SYNTAX_IN_FORWARD_ANNOTATION,
RAW_STRING_TYPE_ANNOTATION,
};
use crate::types::{ClassLiteralType, Type};
use crate::types::{ClassLiteralType, KnownInstanceType, Type};
use crate::{declare_lint, Db};
use ruff_db::diagnostic::{Diagnostic, DiagnosticId, Severity};
use ruff_db::files::File;
use ruff_python_ast::{self as ast, AnyNodeRef};
@@ -59,6 +59,7 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
registry.register_lint(&UNSUPPORTED_OPERATOR);
registry.register_lint(&ZERO_STEPSIZE_IN_SLICE);
registry.register_lint(&STATIC_ASSERT_ERROR);
registry.register_lint(&INVALID_ATTRIBUTE_ACCESS);
// String annotations
registry.register_lint(&BYTE_STRING_TYPE_ANNOTATION);
@@ -750,6 +751,25 @@ declare_lint! {
}
}
declare_lint! {
/// ## What it does
/// Makes sure that instance attribute accesses are valid.
///
/// ## Examples
/// ```python
/// class C:
/// var: ClassVar[int] = 1
///
/// C.var = 3 # okay
/// C().var = 3 # error: Cannot assign to class variable
/// ```
pub(crate) static INVALID_ATTRIBUTE_ACCESS = {
summary: "Invalid attribute access",
status: LintStatus::preview("1.0.0"),
default_level: Level::Error,
}
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct TypeCheckDiagnostic {
pub(crate) id: DiagnosticId,
@@ -1060,3 +1080,18 @@ pub(crate) fn report_base_with_incompatible_slots(context: &InferContext, node:
format_args!("Class base has incompatible `__slots__`"),
);
}
pub(crate) fn report_invalid_arguments_to_annotated<'db>(
db: &'db dyn Db,
context: &InferContext<'db>,
subscript: &ast::ExprSubscript,
) {
context.report_lint(
&INVALID_TYPE_FORM,
subscript.into(),
format_args!(
"Special form `{}` expected at least 2 arguments (one type and at least one metadata element)",
KnownInstanceType::Annotated.repr(db)
),
);
}

View File

@@ -51,9 +51,10 @@ use crate::semantic_index::SemanticIndex;
use crate::stdlib::builtins_module_scope;
use crate::types::call::{Argument, CallArguments};
use crate::types::diagnostic::{
report_invalid_assignment, report_unresolved_module, TypeCheckDiagnostics, CALL_NON_CALLABLE,
CALL_POSSIBLY_UNBOUND_METHOD, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS,
CYCLIC_CLASS_DEFINITION, DIVISION_BY_ZERO, DUPLICATE_BASE, INCONSISTENT_MRO, INVALID_BASE,
report_invalid_arguments_to_annotated, report_invalid_assignment, report_unresolved_module,
TypeCheckDiagnostics, CALL_NON_CALLABLE, CALL_POSSIBLY_UNBOUND_METHOD,
CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS, CYCLIC_CLASS_DEFINITION, DIVISION_BY_ZERO,
DUPLICATE_BASE, INCONSISTENT_MRO, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE,
INVALID_CONTEXT_MANAGER, INVALID_DECLARATION, INVALID_PARAMETER_DEFAULT, INVALID_TYPE_FORM,
INVALID_TYPE_VARIABLE_CONSTRAINTS, POSSIBLY_UNBOUND_ATTRIBUTE, POSSIBLY_UNBOUND_IMPORT,
UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_IMPORT, UNSUPPORTED_OPERATOR,
@@ -65,8 +66,9 @@ use crate::types::{
typing_extensions_symbol, Boundness, CallDunderResult, Class, ClassLiteralType, DynamicType,
FunctionType, InstanceType, IntersectionBuilder, IntersectionType, IterationOutcome,
KnownClass, KnownFunction, KnownInstanceType, MetaclassCandidate, MetaclassErrorKind,
SliceLiteralType, SubclassOfType, Symbol, Truthiness, TupleType, Type, TypeAliasType,
TypeArrayDisplay, TypeVarBoundOrConstraints, TypeVarInstance, UnionBuilder, UnionType,
SliceLiteralType, SubclassOfType, Symbol, SymbolAndQualifiers, Truthiness, TupleType, Type,
TypeAliasType, TypeAndQualifiers, TypeArrayDisplay, TypeQualifiers, TypeVarBoundOrConstraints,
TypeVarInstance, UnionBuilder, UnionType,
};
use crate::unpack::Unpack;
use crate::util::subscript::{PyIndex, PySlice};
@@ -113,7 +115,7 @@ fn infer_definition_types_cycle_recovery<'db>(
let mut inference = TypeInference::empty(input.scope(db));
let category = input.category(db);
if category.is_declaration() {
inference.declarations.insert(input, Type::unknown());
inference.declarations.insert(input, Type::unknown().into());
}
if category.is_binding() {
inference.bindings.insert(input, Type::unknown());
@@ -241,8 +243,8 @@ pub(crate) struct TypeInference<'db> {
/// The types of every binding in this region.
bindings: FxHashMap<Definition<'db>, Type<'db>>,
/// The types of every declaration in this region.
declarations: FxHashMap<Definition<'db>, Type<'db>>,
/// The types and type qualifiers of every declaration in this region.
declarations: FxHashMap<Definition<'db>, TypeAndQualifiers<'db>>,
/// The definitions that are deferred.
deferred: FxHashSet<Definition<'db>>,
@@ -281,7 +283,7 @@ impl<'db> TypeInference<'db> {
}
#[track_caller]
pub(crate) fn declaration_ty(&self, definition: Definition<'db>) -> Type<'db> {
pub(crate) fn declaration_ty(&self, definition: Definition<'db>) -> TypeAndQualifiers<'db> {
self.declarations[&definition]
}
@@ -318,7 +320,7 @@ enum DeclaredAndInferredType<'db> {
AreTheSame(Type<'db>),
/// Declared and inferred types might be different, we need to check assignability.
MightBeDifferent {
declared_ty: Type<'db>,
declared_ty: TypeAndQualifiers<'db>,
inferred_ty: Type<'db>,
},
}
@@ -563,7 +565,9 @@ impl<'db> TypeInferenceBuilder<'db> {
.filter_map(|(definition, ty)| {
// Filter out class literals that result from imports
if let DefinitionKind::Class(class) = definition.kind(self.db()) {
ty.into_class_literal().map(|ty| (ty.class, class.node()))
ty.inner_ty()
.into_class_literal()
.map(|ty| (ty.class, class.node()))
} else {
None
}
@@ -855,7 +859,7 @@ impl<'db> TypeInferenceBuilder<'db> {
let declarations = use_def.declarations_at_binding(binding);
let mut bound_ty = ty;
let declared_ty = declarations_ty(self.db(), declarations)
.map(|s| s.ignore_possibly_unbound().unwrap_or(Type::unknown()))
.map(|SymbolAndQualifiers(s, _)| s.ignore_possibly_unbound().unwrap_or(Type::unknown()))
.unwrap_or_else(|(ty, conflicting)| {
// TODO point out the conflicting declarations in the diagnostic?
let symbol_table = self.index.symbol_table(binding.file_scope(self.db()));
@@ -868,7 +872,7 @@ impl<'db> TypeInferenceBuilder<'db> {
conflicting.display(self.db())
),
);
ty
ty.inner_ty()
});
if !bound_ty.is_assignable_to(self.db(), declared_ty) {
report_invalid_assignment(&self.context, node, declared_ty, bound_ty);
@@ -879,7 +883,12 @@ impl<'db> TypeInferenceBuilder<'db> {
self.types.bindings.insert(binding, bound_ty);
}
fn add_declaration(&mut self, node: AnyNodeRef, declaration: Definition<'db>, ty: Type<'db>) {
fn add_declaration(
&mut self,
node: AnyNodeRef,
declaration: Definition<'db>,
ty: TypeAndQualifiers<'db>,
) {
debug_assert!(declaration.is_declaration(self.db()));
let use_def = self.index.use_def_map(declaration.file_scope(self.db()));
let prior_bindings = use_def.bindings_at_declaration(declaration);
@@ -887,7 +896,7 @@ impl<'db> TypeInferenceBuilder<'db> {
let inferred_ty = bindings_ty(self.db(), prior_bindings)
.ignore_possibly_unbound()
.unwrap_or(Type::Never);
let ty = if inferred_ty.is_assignable_to(self.db(), ty) {
let ty = if inferred_ty.is_assignable_to(self.db(), ty.inner_ty()) {
ty
} else {
self.context.report_lint(
@@ -895,11 +904,11 @@ impl<'db> TypeInferenceBuilder<'db> {
node,
format_args!(
"Cannot declare type `{}` for inferred type `{}`",
ty.display(self.db()),
ty.inner_ty().display(self.db()),
inferred_ty.display(self.db())
),
);
Type::unknown()
Type::unknown().into()
};
self.types.declarations.insert(declaration, ty);
}
@@ -913,23 +922,28 @@ impl<'db> TypeInferenceBuilder<'db> {
debug_assert!(definition.is_binding(self.db()));
debug_assert!(definition.is_declaration(self.db()));
let (declared_ty, inferred_ty) = match declared_and_inferred_ty {
DeclaredAndInferredType::AreTheSame(ty) => (ty, ty),
let (declared_ty, inferred_ty) = match *declared_and_inferred_ty {
DeclaredAndInferredType::AreTheSame(ty) => (ty.into(), ty),
DeclaredAndInferredType::MightBeDifferent {
declared_ty,
inferred_ty,
} => {
if inferred_ty.is_assignable_to(self.db(), *declared_ty) {
if inferred_ty.is_assignable_to(self.db(), declared_ty.inner_ty()) {
(declared_ty, inferred_ty)
} else {
report_invalid_assignment(&self.context, node, *declared_ty, *inferred_ty);
report_invalid_assignment(
&self.context,
node,
declared_ty.inner_ty(),
inferred_ty,
);
// if the assignment is invalid, fall back to assuming the annotation is correct
(declared_ty, declared_ty)
(declared_ty, declared_ty.inner_ty())
}
}
};
self.types.declarations.insert(definition, *declared_ty);
self.types.bindings.insert(definition, *inferred_ty);
self.types.declarations.insert(definition, declared_ty);
self.types.bindings.insert(definition, inferred_ty);
}
fn add_unknown_declaration_with_binding(
@@ -1220,7 +1234,7 @@ impl<'db> TypeInferenceBuilder<'db> {
let declared_and_inferred_ty = if let Some(default_ty) = default_ty {
if default_ty.is_assignable_to(self.db(), declared_ty) {
DeclaredAndInferredType::MightBeDifferent {
declared_ty,
declared_ty: declared_ty.into(),
inferred_ty: UnionType::from_elements(self.db(), [declared_ty, default_ty]),
}
} else if self.in_stub()
@@ -2066,7 +2080,7 @@ impl<'db> TypeInferenceBuilder<'db> {
);
// Handle various singletons.
if let Type::Instance(InstanceType { class }) = declared_ty {
if let Type::Instance(InstanceType { class }) = declared_ty.inner_ty() {
if class.is_known(self.db(), KnownClass::SpecialForm) {
if let Some(name_expr) = target.as_name_expr() {
if let Some(known_instance) = KnownInstanceType::try_from_file_and_name(
@@ -2074,7 +2088,7 @@ impl<'db> TypeInferenceBuilder<'db> {
self.file(),
&name_expr.id,
) {
declared_ty = Type::KnownInstance(known_instance);
declared_ty.inner = Type::KnownInstance(known_instance);
}
}
}
@@ -2083,7 +2097,7 @@ impl<'db> TypeInferenceBuilder<'db> {
if let Some(value) = value.as_deref() {
let inferred_ty = self.infer_expression(value);
let inferred_ty = if self.in_stub() && value.is_ellipsis_literal_expr() {
declared_ty
declared_ty.inner_ty()
} else {
inferred_ty
};
@@ -3391,14 +3405,33 @@ impl<'db> TypeInferenceBuilder<'db> {
fn infer_attribute_expression(&mut self, attribute: &ast::ExprAttribute) -> Type<'db> {
let ast::ExprAttribute {
value,
attr: _,
attr,
range: _,
ctx,
} = attribute;
match ctx {
ExprContext::Load => self.infer_attribute_load(attribute),
ExprContext::Store | ExprContext::Del => {
ExprContext::Store => {
let value_ty = self.infer_expression(value);
if let Type::Instance(instance) = value_ty {
let instance_member = instance.class.instance_member(self.db(), attr);
if instance_member.is_class_var() {
self.context.report_lint(
&INVALID_ATTRIBUTE_ACCESS,
attribute.into(),
format_args!(
"Cannot assign to ClassVar `{attr}` from an instance of type `{ty}`",
ty = value_ty.display(self.db()),
),
);
}
}
Type::Never
}
ExprContext::Del => {
self.infer_expression(value);
Type::Never
}
@@ -4698,7 +4731,7 @@ impl<'db> TypeInferenceBuilder<'db> {
&mut self,
annotation: &ast::Expr,
deferred_state: DeferredExpressionState,
) -> Type<'db> {
) -> TypeAndQualifiers<'db> {
let previous_deferred_state = std::mem::replace(&mut self.deferred_state, deferred_state);
let annotation_ty = self.infer_annotation_expression_impl(annotation);
self.deferred_state = previous_deferred_state;
@@ -4713,21 +4746,24 @@ impl<'db> TypeInferenceBuilder<'db> {
&mut self,
annotation: Option<&ast::Expr>,
deferred_state: DeferredExpressionState,
) -> Option<Type<'db>> {
) -> Option<TypeAndQualifiers<'db>> {
annotation.map(|expr| self.infer_annotation_expression(expr, deferred_state))
}
/// Implementation of [`infer_annotation_expression`].
///
/// [`infer_annotation_expression`]: TypeInferenceBuilder::infer_annotation_expression
fn infer_annotation_expression_impl(&mut self, annotation: &ast::Expr) -> Type<'db> {
fn infer_annotation_expression_impl(
&mut self,
annotation: &ast::Expr,
) -> TypeAndQualifiers<'db> {
// https://typing.readthedocs.io/en/latest/spec/annotations.html#grammar-token-expression-grammar-annotation_expression
let annotation_ty = match annotation {
// String annotations: https://typing.readthedocs.io/en/latest/spec/annotations.html#string-annotations
ast::Expr::StringLiteral(string) => self.infer_string_annotation_expression(string),
// Annotation expressions also get special handling for `*args` and `**kwargs`.
ast::Expr::Starred(starred) => self.infer_starred_expression(starred),
ast::Expr::Starred(starred) => self.infer_starred_expression(starred).into(),
ast::Expr::BytesLiteral(bytes) => {
self.context.report_lint(
@@ -4735,7 +4771,7 @@ impl<'db> TypeInferenceBuilder<'db> {
bytes.into(),
format_args!("Type expressions cannot use bytes literal"),
);
Type::unknown()
Type::unknown().into()
}
ast::Expr::FString(fstring) => {
@@ -4745,21 +4781,126 @@ impl<'db> TypeInferenceBuilder<'db> {
format_args!("Type expressions cannot use f-strings"),
);
self.infer_fstring_expression(fstring);
Type::unknown()
Type::unknown().into()
}
ast::Expr::Name(name) => match name.ctx {
ast::ExprContext::Load => {
let name_expr_ty = self.infer_name_expression(name);
match name_expr_ty {
Type::KnownInstance(KnownInstanceType::ClassVar) => {
TypeAndQualifiers::new(Type::unknown(), TypeQualifiers::CLASS_VAR)
}
Type::KnownInstance(KnownInstanceType::Final) => {
TypeAndQualifiers::new(Type::unknown(), TypeQualifiers::FINAL)
}
_ => name_expr_ty
.in_type_expression(self.db())
.unwrap_or_else(|error| {
error.into_fallback_type(&self.context, annotation)
})
.into(),
}
}
ast::ExprContext::Invalid => Type::unknown().into(),
ast::ExprContext::Store | ast::ExprContext::Del => todo_type!().into(),
},
ast::Expr::Subscript(subscript @ ast::ExprSubscript { value, slice, .. }) => {
let value_ty = self.infer_expression(value);
let slice = &**slice;
match value_ty {
Type::KnownInstance(KnownInstanceType::Annotated) => {
// This branch is similar to the corresponding branch in `infer_parameterized_known_instance_type_expression`, but
// `Annotated[…]` can appear both in annotation expressions and in type expressions, and needs to be handled slightly
// differently in each case (calling either `infer_type_expression_*` or `infer_annotation_expression_*`).
if let ast::Expr::Tuple(ast::ExprTuple {
elts: arguments, ..
}) = slice
{
if arguments.len() < 2 {
report_invalid_arguments_to_annotated(
self.db(),
&self.context,
subscript,
);
}
if let [inner_annotation, metadata @ ..] = &arguments[..] {
for element in metadata {
self.infer_expression(element);
}
let inner_annotation_ty =
self.infer_annotation_expression_impl(inner_annotation);
self.store_expression_type(slice, inner_annotation_ty.inner_ty());
inner_annotation_ty
} else {
self.infer_type_expression(slice);
Type::unknown().into()
}
} else {
report_invalid_arguments_to_annotated(
self.db(),
&self.context,
subscript,
);
self.infer_annotation_expression_impl(slice)
}
}
Type::KnownInstance(
known_instance @ (KnownInstanceType::ClassVar | KnownInstanceType::Final),
) => match slice {
ast::Expr::Tuple(..) => {
self.context.report_lint(
&INVALID_TYPE_FORM,
subscript.into(),
format_args!(
"Type qualifier `{type_qualifier}` expects exactly one type parameter",
type_qualifier = known_instance.repr(self.db()),
),
);
Type::unknown().into()
}
_ => {
let mut type_and_qualifiers =
self.infer_annotation_expression_impl(slice);
match known_instance {
KnownInstanceType::ClassVar => {
type_and_qualifiers.add_qualifier(TypeQualifiers::CLASS_VAR);
}
KnownInstanceType::Final => {
type_and_qualifiers.add_qualifier(TypeQualifiers::FINAL);
}
_ => unreachable!(),
}
type_and_qualifiers
}
},
_ => self
.infer_subscript_type_expression_no_store(subscript, slice, value_ty)
.into(),
}
}
// All other annotation expressions are (possibly) valid type expressions, so handle
// them there instead.
type_expr => self.infer_type_expression_no_store(type_expr),
type_expr => self.infer_type_expression_no_store(type_expr).into(),
};
self.store_expression_type(annotation, annotation_ty);
self.store_expression_type(annotation, annotation_ty.inner_ty());
annotation_ty
}
/// Infer the type of a string annotation expression.
fn infer_string_annotation_expression(&mut self, string: &ast::ExprStringLiteral) -> Type<'db> {
fn infer_string_annotation_expression(
&mut self,
string: &ast::ExprStringLiteral,
) -> TypeAndQualifiers<'db> {
match parse_string_annotation(&self.context, string) {
Some(parsed) => {
// String annotations are always evaluated in the deferred context.
@@ -4768,7 +4909,7 @@ impl<'db> TypeInferenceBuilder<'db> {
DeferredExpressionState::InStringAnnotation,
)
}
None => Type::unknown(),
None => Type::unknown().into(),
}
}
}
@@ -4855,16 +4996,7 @@ impl<'db> TypeInferenceBuilder<'db> {
let value_ty = self.infer_expression(value);
match value_ty {
Type::ClassLiteral(class_literal_ty) => {
match class_literal_ty.class.known(self.db()) {
Some(KnownClass::Tuple) => self.infer_tuple_type_expression(slice),
Some(KnownClass::Type) => self.infer_subclass_of_type_expression(slice),
_ => self.infer_subscript_type_expression(subscript, value_ty),
}
}
_ => self.infer_subscript_type_expression(subscript, value_ty),
}
self.infer_subscript_type_expression_no_store(subscript, slice, value_ty)
}
ast::Expr::BinOp(binary) => {
@@ -4980,6 +5112,22 @@ impl<'db> TypeInferenceBuilder<'db> {
}
}
fn infer_subscript_type_expression_no_store(
&mut self,
subscript: &ast::ExprSubscript,
slice: &ast::Expr,
value_ty: Type<'db>,
) -> Type<'db> {
match value_ty {
Type::ClassLiteral(class_literal_ty) => match class_literal_ty.class.known(self.db()) {
Some(KnownClass::Tuple) => self.infer_tuple_type_expression(slice),
Some(KnownClass::Type) => self.infer_subclass_of_type_expression(slice),
_ => self.infer_subscript_type_expression(subscript, value_ty),
},
_ => self.infer_subscript_type_expression(subscript, value_ty),
}
}
/// Infer the type of a string type expression.
fn infer_string_type_expression(&mut self, string: &ast::ExprStringLiteral) -> Type<'db> {
match parse_string_annotation(&self.context, string) {
@@ -5163,22 +5311,11 @@ impl<'db> TypeInferenceBuilder<'db> {
let arguments_slice = &*subscript.slice;
match known_instance {
KnownInstanceType::Annotated => {
let report_invalid_arguments = || {
self.context.report_lint(
&INVALID_TYPE_FORM,
subscript.into(),
format_args!(
"Special form `{}` expected at least 2 arguments (one type and at least one metadata element)",
known_instance.repr(self.db())
),
);
};
let ast::Expr::Tuple(ast::ExprTuple {
elts: arguments, ..
}) = arguments_slice
else {
report_invalid_arguments();
report_invalid_arguments_to_annotated(self.db(), &self.context, subscript);
// `Annotated[]` with less than two arguments is an error at runtime.
// However, we still treat `Annotated[T]` as `T` here for the purpose of
@@ -5188,7 +5325,7 @@ impl<'db> TypeInferenceBuilder<'db> {
};
if arguments.len() < 2 {
report_invalid_arguments();
report_invalid_arguments_to_annotated(self.db(), &self.context, subscript);
}
let [type_expr, metadata @ ..] = &arguments[..] else {
@@ -5345,13 +5482,16 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_type_expression(arguments_slice);
todo_type!("`NotRequired[]` type qualifier")
}
KnownInstanceType::ClassVar => {
let ty = self.infer_type_expression(arguments_slice);
ty
}
KnownInstanceType::Final => {
self.infer_type_expression(arguments_slice);
todo_type!("`Final[]` type qualifier")
KnownInstanceType::ClassVar | KnownInstanceType::Final => {
self.context.report_lint(
&INVALID_TYPE_FORM,
subscript.into(),
format_args!(
"Type qualifier `{}` is not allowed in type expressions (only in annotation expressions)",
known_instance.repr(self.db())
),
);
self.infer_type_expression(arguments_slice)
}
KnownInstanceType::Required => {
self.infer_type_expression(arguments_slice);