-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[ty] Diagnostic for generic classes that reference typevars in enclosing scope #20822
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 11 commits
97feecb
7f62ddd
cae5c63
1865d0d
5476cd1
2a6cb1e
5118548
940c191
bcd78c4
ba721fb
d90c207
ae0f00a
5965ffe
6ca9f93
23f81f3
10a1b54
e44582a
570b30d
c526090
40c48cc
8823fd1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
--- | ||
source: crates/ty_test/src/lib.rs | ||
expression: snapshot | ||
--- | ||
--- | ||
mdtest name: scoping.md - Scoping rules for type variables - Nested formal typevars must be distinct - Generic class within generic function | ||
mdtest path: crates/ty_python_semantic/resources/mdtest/generics/scoping.md | ||
--- | ||
|
||
# Python source files | ||
|
||
## mdtest_snippet.py | ||
|
||
``` | ||
1 | from typing import Iterable | ||
2 | | ||
3 | def f[T](x: T, y: T) -> None: | ||
4 | class Ok[S]: ... | ||
5 | # error: [invalid-generic-class] "Generic class `Bad1` must not reference type variables bound in an enclosing scope" | ||
6 | class Bad1[T]: ... | ||
7 | # error: [invalid-generic-class] "Generic class `Bad2` must not reference type variables bound in an enclosing scope" | ||
8 | class Bad2(Iterable[T]): ... | ||
``` | ||
|
||
# Diagnostics | ||
|
||
``` | ||
error[invalid-generic-class]: Generic class `Bad1` must not reference type variables bound in an enclosing scope | ||
--> src/mdtest_snippet.py:3:1 | ||
| | ||
1 | from typing import Iterable | ||
2 | | ||
3 | / def f[T](x: T, y: T) -> None: | ||
4 | | class Ok[S]: ... | ||
5 | | # error: [invalid-generic-class] "Generic class `Bad1` must not reference type variables bound in an enclosing scope" | ||
6 | | class Bad1[T]: ... | ||
| | ^^^^ | ||
7 | | # error: [invalid-generic-class] "Generic class `Bad2` must not reference type variables bound in an enclosing scope" | ||
8 | | class Bad2(Iterable[T]): ... | ||
| |________________________________^ Type variable `T` is bound in this enclosing scope | ||
| | ||
info: rule `invalid-generic-class` is enabled by default | ||
|
||
``` | ||
|
||
``` | ||
error[invalid-generic-class]: Generic class `Bad2` must not reference type variables bound in an enclosing scope | ||
--> src/mdtest_snippet.py:3:1 | ||
| | ||
1 | from typing import Iterable | ||
2 | | ||
3 | / def f[T](x: T, y: T) -> None: | ||
4 | | class Ok[S]: ... | ||
5 | | # error: [invalid-generic-class] "Generic class `Bad1` must not reference type variables bound in an enclosing scope" | ||
6 | | class Bad1[T]: ... | ||
7 | | # error: [invalid-generic-class] "Generic class `Bad2` must not reference type variables bound in an enclosing scope" | ||
8 | | class Bad2(Iterable[T]): ... | ||
| |___________^^^^_________________^ Type variable `T` is bound in this enclosing scope | ||
| | ||
info: rule `invalid-generic-class` is enabled by default | ||
|
||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
--- | ||
source: crates/ty_test/src/lib.rs | ||
expression: snapshot | ||
--- | ||
--- | ||
mdtest name: scoping.md - Scoping rules for type variables - Nested formal typevars must be distinct - Generic class within generic class | ||
mdtest path: crates/ty_python_semantic/resources/mdtest/generics/scoping.md | ||
--- | ||
|
||
# Python source files | ||
|
||
## mdtest_snippet.py | ||
|
||
``` | ||
1 | from typing import Iterable | ||
2 | | ||
3 | class C[T]: | ||
4 | class Ok1[S]: ... | ||
5 | # error: [invalid-generic-class] "Generic class `Bad1` must not reference type variables bound in an enclosing scope" | ||
6 | class Bad1[T]: ... | ||
7 | # error: [invalid-generic-class] "Generic class `Bad2` must not reference type variables bound in an enclosing scope" | ||
8 | class Bad2(Iterable[T]): ... | ||
``` | ||
|
||
# Diagnostics | ||
|
||
``` | ||
error[invalid-generic-class]: Generic class `Bad1` must not reference type variables bound in an enclosing scope | ||
--> src/mdtest_snippet.py:3:1 | ||
| | ||
1 | from typing import Iterable | ||
2 | | ||
3 | / class C[T]: | ||
4 | | class Ok1[S]: ... | ||
5 | | # error: [invalid-generic-class] "Generic class `Bad1` must not reference type variables bound in an enclosing scope" | ||
6 | | class Bad1[T]: ... | ||
| | ^^^^ | ||
7 | | # error: [invalid-generic-class] "Generic class `Bad2` must not reference type variables bound in an enclosing scope" | ||
8 | | class Bad2(Iterable[T]): ... | ||
| |________________________________^ Type variable `T` is bound in this enclosing scope | ||
| | ||
info: rule `invalid-generic-class` is enabled by default | ||
|
||
``` | ||
|
||
``` | ||
error[invalid-generic-class]: Generic class `Bad2` must not reference type variables bound in an enclosing scope | ||
--> src/mdtest_snippet.py:3:1 | ||
| | ||
1 | from typing import Iterable | ||
2 | | ||
3 | / class C[T]: | ||
4 | | class Ok1[S]: ... | ||
5 | | # error: [invalid-generic-class] "Generic class `Bad1` must not reference type variables bound in an enclosing scope" | ||
6 | | class Bad1[T]: ... | ||
7 | | # error: [invalid-generic-class] "Generic class `Bad2` must not reference type variables bound in an enclosing scope" | ||
8 | | class Bad2(Iterable[T]): ... | ||
| |___________^^^^_________________^ Type variable `T` is bound in this enclosing scope | ||
| | ||
info: rule `invalid-generic-class` is enabled by default | ||
|
||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,17 +14,17 @@ use crate::types::instance::{Protocol, ProtocolInstanceType}; | |
use crate::types::signatures::{Parameter, Parameters, Signature}; | ||
use crate::types::tuple::{TupleSpec, TupleType, walk_tuple_type}; | ||
use crate::types::{ | ||
ApplyTypeMappingVisitor, BoundTypeVarInstance, ClassLiteral, FindLegacyTypeVarsVisitor, | ||
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, KnownClass, KnownInstanceType, | ||
MaterializationKind, NormalizedVisitor, Type, TypeContext, TypeMapping, TypeRelation, | ||
TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, TypeVarVariance, UnionType, | ||
binding_type, declaration_type, | ||
ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, ClassLiteral, | ||
FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, | ||
KnownClass, KnownInstanceType, MaterializationKind, NormalizedVisitor, Type, TypeContext, | ||
TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, | ||
TypeVarVariance, UnionType, binding_type, declaration_type, | ||
}; | ||
use crate::{Db, FxOrderMap, FxOrderSet}; | ||
|
||
/// Returns an iterator of any generic context introduced by the given scope or any enclosing | ||
/// scope. | ||
fn enclosing_generic_contexts<'db>( | ||
pub(crate) fn enclosing_generic_contexts<'db>( | ||
db: &'db dyn Db, | ||
index: &SemanticIndex<'db>, | ||
scope: FileScopeId, | ||
|
@@ -317,15 +317,30 @@ impl<'db> GenericContext<'db> { | |
/// list. | ||
pub(crate) fn from_base_classes( | ||
db: &'db dyn Db, | ||
definition: impl FnOnce() -> Definition<'db>, | ||
|
||
bases: impl Iterator<Item = Type<'db>>, | ||
) -> Option<Self> { | ||
let mut variables = FxOrderSet::default(); | ||
for base in bases { | ||
base.find_legacy_typevars(db, None, &mut variables); | ||
} | ||
|
||
// If there are no legacy typevars mentioned in the base class list, we can return early. | ||
if variables.is_empty() { | ||
return None; | ||
} | ||
|
||
// If there are legacy typevars, filter out the ones that are not bound by this class. (We | ||
// do this as a post-processing step, instead of by passing in a parameter to | ||
// `find_legacy_typevars`, since there are very many classes that do not reference legacy | ||
// typevars at all, and this avoids adding a salsa dependency on the class `Definition` in | ||
// those cases.) | ||
let binding_context = BindingContext::Definition(definition()); | ||
variables.retain(|bound_typevar| bound_typevar.binding_context(db) == binding_context); | ||
if variables.is_empty() { | ||
return None; | ||
} | ||
|
||
Some(Self::from_typevar_instances(db, variables)) | ||
} | ||
|
||
|
@@ -413,6 +428,15 @@ impl<'db> GenericContext<'db> { | |
.all(|bound_typevar| other_variables.contains_key(&bound_typevar)) | ||
} | ||
|
||
pub(crate) fn binds_named_typevar( | ||
self, | ||
db: &'db dyn Db, | ||
name: &'db ast::name::Name, | ||
) -> Option<BoundTypeVarInstance<'db>> { | ||
self.variables(db) | ||
.find(|self_bound_typevar| self_bound_typevar.typevar(db).name(db) == name) | ||
} | ||
|
||
pub(crate) fn binds_typevar( | ||
self, | ||
db: &'db dyn Db, | ||
|
Uh oh!
There was an error while loading. Please reload this page.