Merge branch 'dcreager/source-order-constraints' into dcreager/callable-return

* dcreager/source-order-constraints: (30 commits)
  clippy
  fix test expectations (again)
  include source_order in display_graph output
  place bounds/constraints first
  don't always bump
  only fold once
  document display source_order
  more comments
  remove now-unused items
  fix test expectation
  use source order in specialize_constrained too
  document overall approach
  more comment
  reuse self source_order
  sort specialize_constrained by source_order
  lots of renaming
  remove source_order_for
  simpler source_order_for
  doc
  restore TODOs
  ...
This commit is contained in:
Douglas Creager
2025-12-15 10:52:26 -05:00
37 changed files with 1748 additions and 698 deletions

View File

@@ -201,7 +201,7 @@ python-version = "3.12"
```py
type IntOrStr = int | str
reveal_type(IntOrStr.__or__) # revealed: bound method typing.TypeAliasType.__or__(right: Any, /) -> _SpecialForm
reveal_type(IntOrStr.__or__) # revealed: bound method TypeAliasType.__or__(right: Any, /) -> _SpecialForm
```
## Method calls on types not disjoint from `None`

View File

@@ -567,7 +567,7 @@ def f(x: int):
super(x, x)
type IntAlias = int
# error: [invalid-super-argument] "`typing.TypeAliasType` is not a valid class"
# error: [invalid-super-argument] "`TypeAliasType` is not a valid class"
super(IntAlias, 0)
# error: [invalid-super-argument] "`str` is not an instance or subclass of `<class 'int'>` in `super(<class 'int'>, str)` call"

View File

@@ -521,6 +521,73 @@ frozen = MyFrozenChildClass()
del frozen.x # TODO this should emit an [invalid-assignment]
```
### frozen/non-frozen inheritance
If a non-frozen dataclass inherits from a frozen dataclass, an exception is raised at runtime. We
catch this error:
<!-- snapshot-diagnostics -->
`a.py`:
```py
from dataclasses import dataclass
@dataclass(frozen=True)
class FrozenBase:
x: int
@dataclass
# error: [invalid-frozen-dataclass-subclass] "Non-frozen dataclass `Child` cannot inherit from frozen dataclass `FrozenBase`"
class Child(FrozenBase):
y: int
```
Frozen dataclasses inheriting from non-frozen dataclasses are also illegal:
`b.py`:
```py
from dataclasses import dataclass
@dataclass
class Base:
x: int
@dataclass(frozen=True)
# error: [invalid-frozen-dataclass-subclass] "Frozen dataclass `FrozenChild` cannot inherit from non-frozen dataclass `Base`"
class FrozenChild(Base):
y: int
```
Example of diagnostics when there are multiple files involved:
`module.py`:
```py
import dataclasses
@dataclasses.dataclass(frozen=False)
class NotFrozenBase:
x: int
```
`main.py`:
```py
from functools import total_ordering
from typing import final
from dataclasses import dataclass
from module import NotFrozenBase
@final
@dataclass(frozen=True)
@total_ordering
class FrozenChild(NotFrozenBase): # error: [invalid-frozen-dataclass-subclass]
y: str
```
### `match_args`
If `match_args` is set to `True` (the default), the `__match_args__` attribute is a tuple created

View File

@@ -9,7 +9,7 @@ from typing import ParamSpec
P = ParamSpec("P")
reveal_type(type(P)) # revealed: <class 'ParamSpec'>
reveal_type(P) # revealed: typing.ParamSpec
reveal_type(P) # revealed: ParamSpec
reveal_type(P.__name__) # revealed: Literal["P"]
```

View File

@@ -22,7 +22,7 @@ from typing import TypeVar
T = TypeVar("T")
reveal_type(type(T)) # revealed: <class 'TypeVar'>
reveal_type(T) # revealed: typing.TypeVar
reveal_type(T) # revealed: TypeVar
reveal_type(T.__name__) # revealed: Literal["T"]
```
@@ -146,7 +146,7 @@ from typing import TypeVar
T = TypeVar("T", default=int)
reveal_type(type(T)) # revealed: <class 'TypeVar'>
reveal_type(T) # revealed: typing.TypeVar
reveal_type(T) # revealed: TypeVar
reveal_type(T.__default__) # revealed: int
reveal_type(T.__bound__) # revealed: None
reveal_type(T.__constraints__) # revealed: tuple[()]
@@ -187,7 +187,7 @@ from typing import TypeVar
T = TypeVar("T", bound=int)
reveal_type(type(T)) # revealed: <class 'TypeVar'>
reveal_type(T) # revealed: typing.TypeVar
reveal_type(T) # revealed: TypeVar
reveal_type(T.__bound__) # revealed: int
reveal_type(T.__constraints__) # revealed: tuple[()]
@@ -211,7 +211,7 @@ from typing import TypeVar
T = TypeVar("T", int, str)
reveal_type(type(T)) # revealed: <class 'TypeVar'>
reveal_type(T) # revealed: typing.TypeVar
reveal_type(T) # revealed: TypeVar
reveal_type(T.__constraints__) # revealed: tuple[int, str]
S = TypeVar("S")

View File

@@ -12,7 +12,7 @@ python-version = "3.13"
```py
def foo1[**P]() -> None:
reveal_type(P) # revealed: typing.ParamSpec
reveal_type(P) # revealed: ParamSpec
```
## Bounds and constraints
@@ -45,14 +45,14 @@ The default value for a `ParamSpec` can be either a list of types, `...`, or ano
```py
def foo2[**P = ...]() -> None:
reveal_type(P) # revealed: typing.ParamSpec
reveal_type(P) # revealed: ParamSpec
def foo3[**P = [int, str]]() -> None:
reveal_type(P) # revealed: typing.ParamSpec
reveal_type(P) # revealed: ParamSpec
def foo4[**P, **Q = P]():
reveal_type(P) # revealed: typing.ParamSpec
reveal_type(Q) # revealed: typing.ParamSpec
reveal_type(P) # revealed: ParamSpec
reveal_type(Q) # revealed: ParamSpec
```
Other values are invalid.

View File

@@ -17,7 +17,7 @@ instances of `typing.TypeVar`, just like legacy type variables.
```py
def f[T]():
reveal_type(type(T)) # revealed: <class 'TypeVar'>
reveal_type(T) # revealed: typing.TypeVar
reveal_type(T) # revealed: TypeVar
reveal_type(T.__name__) # revealed: Literal["T"]
```
@@ -33,7 +33,7 @@ python-version = "3.13"
```py
def f[T = int]():
reveal_type(type(T)) # revealed: <class 'TypeVar'>
reveal_type(T) # revealed: typing.TypeVar
reveal_type(T) # revealed: TypeVar
reveal_type(T.__default__) # revealed: int
reveal_type(T.__bound__) # revealed: None
reveal_type(T.__constraints__) # revealed: tuple[()]
@@ -66,7 +66,7 @@ class Invalid[S = T]: ...
```py
def f[T: int]():
reveal_type(type(T)) # revealed: <class 'TypeVar'>
reveal_type(T) # revealed: typing.TypeVar
reveal_type(T) # revealed: TypeVar
reveal_type(T.__bound__) # revealed: int
reveal_type(T.__constraints__) # revealed: tuple[()]
@@ -79,7 +79,7 @@ def g[S]():
```py
def f[T: (int, str)]():
reveal_type(type(T)) # revealed: <class 'TypeVar'>
reveal_type(T) # revealed: typing.TypeVar
reveal_type(T) # revealed: TypeVar
reveal_type(T.__constraints__) # revealed: tuple[int, str]
reveal_type(T.__bound__) # revealed: None

View File

@@ -400,7 +400,7 @@ reveal_type(ListOrTuple) # revealed: <types.UnionType special form 'list[T@List
reveal_type(ListOrTupleLegacy)
reveal_type(MyCallable) # revealed: <typing.Callable special form '(**P@MyCallable) -> T@MyCallable'>
reveal_type(AnnotatedType) # revealed: <special form 'typing.Annotated[T@AnnotatedType, <metadata>]'>
reveal_type(TransparentAlias) # revealed: typing.TypeVar
reveal_type(TransparentAlias) # revealed: TypeVar
reveal_type(MyOptional) # revealed: <types.UnionType special form 'T@MyOptional | None'>
def _(

View File

@@ -12,7 +12,7 @@ python-version = "3.12"
```py
type IntOrStr = int | str
reveal_type(IntOrStr) # revealed: typing.TypeAliasType
reveal_type(IntOrStr) # revealed: TypeAliasType
reveal_type(IntOrStr.__name__) # revealed: Literal["IntOrStr"]
x: IntOrStr = 1
@@ -205,7 +205,7 @@ from typing_extensions import TypeAliasType, Union
IntOrStr = TypeAliasType("IntOrStr", Union[int, str])
reveal_type(IntOrStr) # revealed: typing.TypeAliasType
reveal_type(IntOrStr) # revealed: TypeAliasType
reveal_type(IntOrStr.__name__) # revealed: Literal["IntOrStr"]

View File

@@ -13,7 +13,7 @@ diagnostic message for `invalid-exception-caught` expects to construct `typing.P
def foo[**P]() -> None:
try:
pass
# error: [invalid-exception-caught] "Invalid object caught in an exception handler: Object has type `typing.ParamSpec`"
# error: [invalid-exception-caught] "Invalid object caught in an exception handler: Object has type `ParamSpec`"
except P:
pass
```

View File

@@ -0,0 +1,120 @@
# Implicit class body attributes
## Class body implicit attributes
Python makes certain names available implicitly inside class body scopes. These are `__qualname__`,
`__module__`, and `__doc__`, as documented at
<https://docs.python.org/3/reference/datamodel.html#creating-the-class-object>.
```py
class Foo:
reveal_type(__qualname__) # revealed: str
reveal_type(__module__) # revealed: str
reveal_type(__doc__) # revealed: str | None
```
## `__firstlineno__` (Python 3.13+)
Python 3.13 added `__firstlineno__` to the class body namespace.
### Available in Python 3.13+
```toml
[environment]
python-version = "3.13"
```
```py
class Foo:
reveal_type(__firstlineno__) # revealed: int
```
### Not available in Python 3.12 and earlier
```toml
[environment]
python-version = "3.12"
```
```py
class Foo:
# error: [unresolved-reference]
__firstlineno__
```
## Nested classes
These implicit attributes are also available in nested classes, and refer to the nested class:
```py
class Outer:
class Inner:
reveal_type(__qualname__) # revealed: str
reveal_type(__module__) # revealed: str
```
## Class body implicit attributes have priority over globals
If a global variable with the same name exists, the class body implicit attribute takes priority
within the class body:
```py
__qualname__ = 42
__module__ = 42
class Foo:
# Inside the class body, these are the implicit class attributes
reveal_type(__qualname__) # revealed: str
reveal_type(__module__) # revealed: str
# Outside the class, the globals are visible
reveal_type(__qualname__) # revealed: Literal[42]
reveal_type(__module__) # revealed: Literal[42]
```
## `__firstlineno__` has priority over globals (Python 3.13+)
The same applies to `__firstlineno__` on Python 3.13+:
```toml
[environment]
python-version = "3.13"
```
```py
__firstlineno__ = "not an int"
class Foo:
reveal_type(__firstlineno__) # revealed: int
reveal_type(__firstlineno__) # revealed: Literal["not an int"]
```
## Class body implicit attributes are not visible in methods
The implicit class body attributes are only available directly in the class body, not in nested
function scopes (methods):
```py
class Foo:
# Available directly in the class body
x = __qualname__
reveal_type(x) # revealed: str
def method(self):
# Not available in methods - falls back to builtins/globals
# error: [unresolved-reference]
__qualname__
```
## Real-world use case: logging
A common use case is defining a logger with the class name:
```py
import logging
class MyClass:
logger = logging.getLogger(__qualname__)
reveal_type(logger) # revealed: Logger
```

View File

@@ -0,0 +1,154 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: dataclasses.md - Dataclasses - Other dataclass parameters - frozen/non-frozen inheritance
mdtest path: crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.md
---
# Python source files
## a.py
```
1 | from dataclasses import dataclass
2 |
3 | @dataclass(frozen=True)
4 | class FrozenBase:
5 | x: int
6 |
7 | @dataclass
8 | # error: [invalid-frozen-dataclass-subclass] "Non-frozen dataclass `Child` cannot inherit from frozen dataclass `FrozenBase`"
9 | class Child(FrozenBase):
10 | y: int
```
## b.py
```
1 | from dataclasses import dataclass
2 |
3 | @dataclass
4 | class Base:
5 | x: int
6 |
7 | @dataclass(frozen=True)
8 | # error: [invalid-frozen-dataclass-subclass] "Frozen dataclass `FrozenChild` cannot inherit from non-frozen dataclass `Base`"
9 | class FrozenChild(Base):
10 | y: int
```
## module.py
```
1 | import dataclasses
2 |
3 | @dataclasses.dataclass(frozen=False)
4 | class NotFrozenBase:
5 | x: int
```
## main.py
```
1 | from functools import total_ordering
2 | from typing import final
3 | from dataclasses import dataclass
4 |
5 | from module import NotFrozenBase
6 |
7 | @final
8 | @dataclass(frozen=True)
9 | @total_ordering
10 | class FrozenChild(NotFrozenBase): # error: [invalid-frozen-dataclass-subclass]
11 | y: str
```
# Diagnostics
```
error[invalid-frozen-dataclass-subclass]: Non-frozen dataclass cannot inherit from frozen dataclass
--> src/a.py:7:1
|
5 | x: int
6 |
7 | @dataclass
| ---------- `Child` dataclass parameters
8 | # error: [invalid-frozen-dataclass-subclass] "Non-frozen dataclass `Child` cannot inherit from frozen dataclass `FrozenBase`"
9 | class Child(FrozenBase):
| ^^^^^^----------^ Subclass `Child` is not frozen but base class `FrozenBase` is
10 | y: int
|
info: This causes the class creation to fail
info: Base class definition
--> src/a.py:3:1
|
1 | from dataclasses import dataclass
2 |
3 | @dataclass(frozen=True)
| ----------------------- `FrozenBase` dataclass parameters
4 | class FrozenBase:
| ^^^^^^^^^^ `FrozenBase` definition
5 | x: int
|
info: rule `invalid-frozen-dataclass-subclass` is enabled by default
```
```
error[invalid-frozen-dataclass-subclass]: Frozen dataclass cannot inherit from non-frozen dataclass
--> src/b.py:7:1
|
5 | x: int
6 |
7 | @dataclass(frozen=True)
| ----------------------- `FrozenChild` dataclass parameters
8 | # error: [invalid-frozen-dataclass-subclass] "Frozen dataclass `FrozenChild` cannot inherit from non-frozen dataclass `Base`"
9 | class FrozenChild(Base):
| ^^^^^^^^^^^^----^ Subclass `FrozenChild` is frozen but base class `Base` is not
10 | y: int
|
info: This causes the class creation to fail
info: Base class definition
--> src/b.py:3:1
|
1 | from dataclasses import dataclass
2 |
3 | @dataclass
| ---------- `Base` dataclass parameters
4 | class Base:
| ^^^^ `Base` definition
5 | x: int
|
info: rule `invalid-frozen-dataclass-subclass` is enabled by default
```
```
error[invalid-frozen-dataclass-subclass]: Frozen dataclass cannot inherit from non-frozen dataclass
--> src/main.py:8:1
|
7 | @final
8 | @dataclass(frozen=True)
| ----------------------- `FrozenChild` dataclass parameters
9 | @total_ordering
10 | class FrozenChild(NotFrozenBase): # error: [invalid-frozen-dataclass-subclass]
| ^^^^^^^^^^^^-------------^ Subclass `FrozenChild` is frozen but base class `NotFrozenBase` is not
11 | y: str
|
info: This causes the class creation to fail
info: Base class definition
--> src/module.py:3:1
|
1 | import dataclasses
2 |
3 | @dataclasses.dataclass(frozen=False)
| ------------------------------------ `NotFrozenBase` dataclass parameters
4 | class NotFrozenBase:
| ^^^^^^^^^^^^^ `NotFrozenBase` definition
5 | x: int
|
info: rule `invalid-frozen-dataclass-subclass` is enabled by default
```

View File

@@ -1,4 +1,5 @@
use ruff_db::files::File;
use ruff_python_ast::PythonVersion;
use crate::dunder_all::dunder_all_names;
use crate::module_resolver::{KnownModule, file_to_module, resolve_module_confident};
@@ -1633,6 +1634,35 @@ mod implicit_globals {
}
}
/// Looks up the type of an "implicit class body symbol". Returns [`Place::Undefined`] if
/// `name` is not present as an implicit symbol in class bodies.
///
/// Implicit class body symbols are symbols such as `__qualname__`, `__module__`, `__doc__`,
/// and `__firstlineno__` that Python implicitly makes available inside a class body during
/// class creation.
///
/// See <https://docs.python.org/3/reference/datamodel.html#creating-the-class-object>
pub(crate) fn class_body_implicit_symbol<'db>(
db: &'db dyn Db,
name: &str,
) -> PlaceAndQualifiers<'db> {
match name {
"__qualname__" => Place::bound(KnownClass::Str.to_instance(db)).into(),
"__module__" => Place::bound(KnownClass::Str.to_instance(db)).into(),
// __doc__ is `str` if there's a docstring, `None` if there isn't
"__doc__" => Place::bound(UnionType::from_elements(
db,
[KnownClass::Str.to_instance(db), Type::none(db)],
))
.into(),
// __firstlineno__ was added in Python 3.13
"__firstlineno__" if Program::get(db).python_version(db) >= PythonVersion::PY313 => {
Place::bound(KnownClass::Int.to_instance(db)).into()
}
_ => Place::Undefined.into(),
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub(crate) enum RequiresExplicitReExport {
Yes,

View File

@@ -690,6 +690,12 @@ bitflags! {
}
}
impl DataclassFlags {
pub(crate) const fn is_frozen(self) -> bool {
self.contains(Self::FROZEN)
}
}
pub(crate) const DATACLASS_FLAGS: &[(&str, DataclassFlags)] = &[
("init", DataclassFlags::INIT),
("repr", DataclassFlags::REPR),

View File

@@ -689,20 +689,10 @@ impl<'db> IntersectionBuilder<'db> {
}
}
pub(crate) fn order_elements(mut self, val: bool) -> Self {
self.order_elements = val;
self
}
pub(crate) fn add_positive(self, ty: Type<'db>) -> Self {
self.add_positive_impl(ty, &mut vec![])
}
pub(crate) fn add_positive_in_place(&mut self, ty: Type<'db>) {
let updated = std::mem::replace(self, Self::empty(self.db)).add_positive(ty);
*self = updated;
}
pub(crate) fn add_positive_impl(
mut self,
ty: Type<'db>,

View File

@@ -4659,15 +4659,6 @@ fn asynccontextmanager_return_type<'db>(db: &'db dyn Db, func_ty: Type<'db>) ->
.ok()?
.homogeneous_element_type(db);
if yield_ty.is_divergent()
|| signature
.parameters()
.iter()
.any(|param| param.annotated_type().is_some_and(|ty| ty.is_divergent()))
{
return Some(yield_ty);
}
let context_manager =
known_module_symbol(db, KnownModule::Contextlib, "_AsyncGeneratorContextManager")
.place

View File

@@ -1871,6 +1871,28 @@ impl<'db> ClassLiteral<'db> {
.filter_map(|decorator| decorator.known(db))
}
/// Iterate through the decorators on this class, returning the position of the first one
/// that matches the given predicate.
pub(super) fn find_decorator_position(
self,
db: &'db dyn Db,
predicate: impl Fn(Type<'db>) -> bool,
) -> Option<usize> {
self.decorators(db)
.iter()
.position(|decorator| predicate(*decorator))
}
/// Iterate through the decorators on this class, returning the index of the first one
/// that is either `@dataclass` or `@dataclass(...)`.
pub(super) fn find_dataclass_decorator_position(self, db: &'db dyn Db) -> Option<usize> {
self.find_decorator_position(db, |ty| match ty {
Type::FunctionLiteral(function) => function.is_known(db, KnownFunction::Dataclass),
Type::DataclassDecorator(_) => true,
_ => false,
})
}
/// Is this class final?
pub(super) fn is_final(self, db: &'db dyn Db) -> bool {
self.known_function_decorators(db)

File diff suppressed because it is too large Load Diff

View File

@@ -30,7 +30,7 @@ use crate::types::{
ProtocolInstanceType, SpecialFormType, SubclassOfInner, Type, TypeContext, binding_type,
protocol_class::ProtocolClass,
};
use crate::types::{KnownInstanceType, MemberLookupPolicy};
use crate::types::{DataclassFlags, KnownInstanceType, MemberLookupPolicy};
use crate::{Db, DisplaySettings, FxIndexMap, Module, ModuleName, Program, declare_lint};
use itertools::Itertools;
use ruff_db::{
@@ -121,6 +121,7 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
registry.register_lint(&INVALID_METHOD_OVERRIDE);
registry.register_lint(&INVALID_EXPLICIT_OVERRIDE);
registry.register_lint(&SUPER_CALL_IN_NAMED_TUPLE_METHOD);
registry.register_lint(&INVALID_FROZEN_DATACLASS_SUBCLASS);
// String annotations
registry.register_lint(&BYTE_STRING_TYPE_ANNOTATION);
@@ -2220,6 +2221,44 @@ declare_lint! {
}
}
declare_lint! {
/// ## What it does
/// Checks for dataclasses with invalid frozen inheritance:
/// - A frozen dataclass cannot inherit from a non-frozen dataclass.
/// - A non-frozen dataclass cannot inherit from a frozen dataclass.
///
/// ## Why is this bad?
/// Python raises a `TypeError` at runtime when either of these inheritance
/// patterns occurs.
///
/// ## Example
///
/// ```python
/// from dataclasses import dataclass
///
/// @dataclass
/// class Base:
/// x: int
///
/// @dataclass(frozen=True)
/// class Child(Base): # Error raised here
/// y: int
///
/// @dataclass(frozen=True)
/// class FrozenBase:
/// x: int
///
/// @dataclass
/// class NonFrozenChild(FrozenBase): # Error raised here
/// y: int
/// ```
pub(crate) static INVALID_FROZEN_DATACLASS_SUBCLASS = {
summary: "detects dataclasses with invalid frozen/non-frozen subclassing",
status: LintStatus::stable("0.0.1-alpha.35"),
default_level: Level::Error,
}
}
/// A collection of type check diagnostics.
#[derive(Default, Eq, PartialEq, get_size2::GetSize)]
pub struct TypeCheckDiagnostics {
@@ -4269,6 +4308,94 @@ fn report_unsupported_binary_operation_impl<'a>(
Some(diagnostic)
}
pub(super) fn report_bad_frozen_dataclass_inheritance<'db>(
context: &InferContext<'db, '_>,
class: ClassLiteral<'db>,
class_node: &ast::StmtClassDef,
base_class: ClassLiteral<'db>,
base_class_node: &ast::Expr,
base_class_params: DataclassFlags,
) {
let db = context.db();
let Some(builder) =
context.report_lint(&INVALID_FROZEN_DATACLASS_SUBCLASS, class.header_range(db))
else {
return;
};
let mut diagnostic = if base_class_params.is_frozen() {
let mut diagnostic =
builder.into_diagnostic("Non-frozen dataclass cannot inherit from frozen dataclass");
diagnostic.set_concise_message(format_args!(
"Non-frozen dataclass `{}` cannot inherit from frozen dataclass `{}`",
class.name(db),
base_class.name(db)
));
diagnostic.set_primary_message(format_args!(
"Subclass `{}` is not frozen but base class `{}` is",
class.name(db),
base_class.name(db)
));
diagnostic
} else {
let mut diagnostic =
builder.into_diagnostic("Frozen dataclass cannot inherit from non-frozen dataclass");
diagnostic.set_concise_message(format_args!(
"Frozen dataclass `{}` cannot inherit from non-frozen dataclass `{}`",
class.name(db),
base_class.name(db)
));
diagnostic.set_primary_message(format_args!(
"Subclass `{}` is frozen but base class `{}` is not",
class.name(db),
base_class.name(db)
));
diagnostic
};
diagnostic.annotate(context.secondary(base_class_node));
if let Some(position) = class.find_dataclass_decorator_position(db) {
diagnostic.annotate(
context
.secondary(&class_node.decorator_list[position])
.message(format_args!("`{}` dataclass parameters", class.name(db))),
);
}
diagnostic.info("This causes the class creation to fail");
if let Some(decorator_position) = base_class.find_dataclass_decorator_position(db) {
let mut sub = SubDiagnostic::new(
SubDiagnosticSeverity::Info,
format_args!("Base class definition"),
);
sub.annotate(
Annotation::primary(base_class.header_span(db))
.message(format_args!("`{}` definition", base_class.name(db))),
);
let base_class_file = base_class.file(db);
let module = parsed_module(db, base_class_file).load(db);
let decorator_range = base_class
.body_scope(db)
.node(db)
.expect_class()
.node(&module)
.decorator_list[decorator_position]
.range();
sub.annotate(
Annotation::secondary(Span::from(base_class_file).with_range(decorator_range)).message(
format_args!("`{}` dataclass parameters", base_class.name(db)),
),
);
diagnostic.sub(sub);
}
}
/// This function receives an unresolved `from foo import bar` import,
/// where `foo` can be resolved to a module but that module does not
/// have a `bar` member or submodule.

View File

@@ -2358,7 +2358,7 @@ impl<'db> FmtDetailed<'db> for DisplayKnownInstanceRepr<'db> {
.fmt_detailed(f)?;
f.write_str("'>")
} else {
f.with_type(ty).write_str("typing.TypeAliasType")
f.with_type(ty).write_str("TypeAliasType")
}
}
// This is a legacy `TypeVar` _outside_ of any generic class or function, so we render
@@ -2366,9 +2366,9 @@ impl<'db> FmtDetailed<'db> for DisplayKnownInstanceRepr<'db> {
// have a `Type::TypeVar(_)`, which is rendered as the typevar's name.
KnownInstanceType::TypeVar(typevar_instance) => {
if typevar_instance.kind(self.db).is_paramspec() {
f.with_type(ty).write_str("typing.ParamSpec")
f.with_type(ty).write_str("ParamSpec")
} else {
f.with_type(ty).write_str("typing.TypeVar")
f.with_type(ty).write_str("TypeVar")
}
}
KnownInstanceType::Deprecated(_) => f.write_str("warnings.deprecated"),

View File

@@ -28,9 +28,9 @@ use crate::module_resolver::{
use crate::node_key::NodeKey;
use crate::place::{
ConsideredDefinitions, Definedness, LookupError, Place, PlaceAndQualifiers, TypeOrigin,
builtins_module_scope, builtins_symbol, explicit_global_symbol, global_symbol,
module_type_implicit_global_declaration, module_type_implicit_global_symbol, place,
place_from_bindings, place_from_declarations, typing_extensions_symbol,
builtins_module_scope, builtins_symbol, class_body_implicit_symbol, explicit_global_symbol,
global_symbol, module_type_implicit_global_declaration, module_type_implicit_global_symbol,
place, place_from_bindings, place_from_declarations, typing_extensions_symbol,
};
use crate::semantic_index::ast_ids::node_key::ExpressionNodeKey;
use crate::semantic_index::ast_ids::{HasScopedUseId, ScopedUseId};
@@ -69,15 +69,16 @@ use crate::types::diagnostic::{
UNRESOLVED_IMPORT, UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR, USELESS_OVERLOAD_BODY,
hint_if_stdlib_attribute_exists_on_other_versions,
hint_if_stdlib_submodule_exists_on_other_versions, report_attempted_protocol_instantiation,
report_bad_dunder_set_call, report_cannot_pop_required_field_on_typed_dict,
report_duplicate_bases, report_implicit_return_type, report_index_out_of_bounds,
report_instance_layout_conflict, report_invalid_arguments_to_annotated,
report_invalid_assignment, report_invalid_attribute_assignment,
report_invalid_exception_caught, report_invalid_exception_cause,
report_invalid_exception_raised, report_invalid_exception_tuple_caught,
report_invalid_generator_function_return_type, report_invalid_key_on_typed_dict,
report_invalid_or_unsupported_base, report_invalid_return_type,
report_invalid_type_checking_constant, report_named_tuple_field_with_leading_underscore,
report_bad_dunder_set_call, report_bad_frozen_dataclass_inheritance,
report_cannot_pop_required_field_on_typed_dict, report_duplicate_bases,
report_implicit_return_type, report_index_out_of_bounds, report_instance_layout_conflict,
report_invalid_arguments_to_annotated, report_invalid_assignment,
report_invalid_attribute_assignment, report_invalid_exception_caught,
report_invalid_exception_cause, report_invalid_exception_raised,
report_invalid_exception_tuple_caught, report_invalid_generator_function_return_type,
report_invalid_key_on_typed_dict, report_invalid_or_unsupported_base,
report_invalid_return_type, report_invalid_type_checking_constant,
report_named_tuple_field_with_leading_underscore,
report_namedtuple_field_without_default_after_field_with_default, report_non_subscriptable,
report_possibly_missing_attribute, report_possibly_unresolved_reference,
report_rebound_typevar, report_slice_step_size_zero, report_unsupported_augmented_assignment,
@@ -755,6 +756,27 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
));
}
}
let (base_class_literal, _) = base_class.class_literal(self.db());
if let (Some(base_params), Some(class_params)) = (
base_class_literal.dataclass_params(self.db()),
class.dataclass_params(self.db()),
) {
let base_params = base_params.flags(self.db());
let class_is_frozen = class_params.flags(self.db()).is_frozen();
if base_params.is_frozen() != class_is_frozen {
report_bad_frozen_dataclass_inheritance(
&self.context,
class,
class_node,
base_class_literal,
&class_node.bases()[i],
base_params,
);
}
}
}
// (4) Check that the class's MRO is resolvable
@@ -9188,6 +9210,25 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
PlaceAndQualifiers::from(Place::Undefined)
// If we're in a class body, check for implicit class body symbols first.
// These take precedence over globals.
.or_fall_back_to(db, || {
if scope.node(db).scope_kind().is_class()
&& let Some(symbol) = place_expr.as_symbol()
{
let implicit = class_body_implicit_symbol(db, symbol.name());
if implicit.place.is_definitely_bound() {
return implicit.map_type(|ty| {
self.narrow_place_with_applicable_constraints(
place_expr,
ty,
&constraint_keys,
)
});
}
}
Place::Undefined.into()
})
// No nonlocal binding? Check the module's explicit globals.
// Avoid infinite recursion if `self.scope` already is the module's global scope.
.or_fall_back_to(db, || {

View File

@@ -222,14 +222,14 @@ fn pep695_type_params() {
);
};
check_typevar("T", "typing.TypeVar", None, None, None);
check_typevar("U", "typing.TypeVar", Some("A"), None, None);
check_typevar("V", "typing.TypeVar", None, Some(&["A", "B"]), None);
check_typevar("W", "typing.TypeVar", None, None, Some("A"));
check_typevar("X", "typing.TypeVar", Some("A"), None, Some("A1"));
check_typevar("T", "TypeVar", None, None, None);
check_typevar("U", "TypeVar", Some("A"), None, None);
check_typevar("V", "TypeVar", None, Some(&["A", "B"]), None);
check_typevar("W", "TypeVar", None, None, Some("A"));
check_typevar("X", "TypeVar", Some("A"), None, Some("A1"));
// a typevar with less than two constraints is treated as unconstrained
check_typevar("Y", "typing.TypeVar", None, None, None);
check_typevar("Y", "TypeVar", None, None, None);
}
/// Test that a symbol known to be unbound in a scope does not still trigger cycle-causing