diff --git a/crates/ty/docs/rules.md b/crates/ty/docs/rules.md
index 3c1938378cd176..302cbb038fb05d 100644
--- a/crates/ty/docs/rules.md
+++ b/crates/ty/docs/rules.md
@@ -8,7 +8,7 @@
Default level: error ·
Added in 0.0.13 ·
Related issues ·
-View source
+View source
@@ -49,7 +49,7 @@ class Derived(Base): # Error: `Derived` does not implement `method`
Default level: warn ·
Added in 0.0.1-alpha.20 ·
Related issues ·
-View source
+View source
@@ -90,7 +90,7 @@ class SubProto(BaseProto, Protocol):
Default level: error ·
Added in 0.0.14 ·
Related issues ·
-View source
+View source
@@ -126,7 +126,7 @@ def _(x: int):
Default level: error ·
Preview (since 0.0.16) ·
Related issues ·
-View source
+View source
@@ -175,7 +175,7 @@ Foo.method() # Error: cannot call abstract classmethod
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -199,7 +199,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
Default level: error ·
Added in 0.0.7 ·
Related issues ·
-View source
+View source
@@ -230,7 +230,7 @@ def f(x: object):
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -262,7 +262,7 @@ f(int) # error
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -293,7 +293,7 @@ a = 1
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -325,7 +325,7 @@ class C(A, B): ...
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -357,7 +357,7 @@ class B(A): ...
Default level: error ·
Added in 0.0.1-alpha.29 ·
Related issues ·
-View source
+View source
@@ -385,7 +385,7 @@ type B = A
Default level: error ·
Added in 0.0.15 ·
Related issues ·
-View source
+View source
@@ -417,7 +417,7 @@ class Example:
Default level: warn ·
Added in 0.0.1-alpha.16 ·
Related issues ·
-View source
+View source
@@ -444,7 +444,7 @@ old_func() # emits [deprecated] diagnostic
Default level: ignore ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -473,7 +473,7 @@ false positives it can produce.
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -500,7 +500,7 @@ class B(A, A): ...
Default level: error ·
Added in 0.0.1-alpha.12 ·
Related issues ·
-View source
+View source
@@ -538,7 +538,7 @@ class A: # Crash at runtime
Default level: error ·
Added in 0.0.14 ·
Related issues ·
-View source
+View source
@@ -609,7 +609,7 @@ def foo() -> "intt\b": ...
Default level: error ·
Added in 0.0.20 ·
Related issues ·
-View source
+View source
@@ -641,7 +641,7 @@ def my_function() -> int:
Default level: error ·
Added in 0.0.15 ·
Related issues ·
-View source
+View source
@@ -736,7 +736,7 @@ def test(): -> "Literal[5]":
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -766,7 +766,7 @@ class C(A, B): ...
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -792,7 +792,7 @@ t[3] # IndexError: tuple index out of range
Default level: warn ·
Added in 0.0.1-alpha.33 ·
Related issues ·
-View source
+View source
@@ -826,7 +826,7 @@ class MyClass: ...
Default level: error ·
Added in 0.0.1-alpha.12 ·
Related issues ·
-View source
+View source
@@ -915,7 +915,7 @@ an atypical memory layout.
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -942,7 +942,7 @@ func("foo") # error: [invalid-argument-type]
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -970,7 +970,7 @@ a: int = ''
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1004,7 +1004,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
Default level: error ·
Added in 0.0.1-alpha.19 ·
Related issues ·
-View source
+View source
@@ -1040,7 +1040,7 @@ asyncio.run(main())
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1064,7 +1064,7 @@ class A(42): ... # error: [invalid-base]
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1091,7 +1091,7 @@ with 1:
Default level: error ·
Added in 0.0.12 ·
Related issues ·
-View source
+View source
@@ -1128,7 +1128,7 @@ class Foo(NamedTuple):
Default level: error ·
Added in 0.0.13 ·
Related issues ·
-View source
+View source
@@ -1160,7 +1160,7 @@ class A:
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1189,7 +1189,7 @@ a: str
Default level: warn ·
Added in 0.0.20 ·
Related issues ·
-View source
+View source
@@ -1238,7 +1238,7 @@ class Pet(Enum):
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1282,7 +1282,7 @@ except ZeroDivisionError:
Default level: error ·
Added in 0.0.1-alpha.28 ·
Related issues ·
-View source
+View source
@@ -1324,7 +1324,7 @@ class D(A):
Default level: error ·
Added in 0.0.1-alpha.35 ·
Related issues ·
-View source
+View source
@@ -1368,7 +1368,7 @@ class NonFrozenChild(FrozenBase): # Error raised here
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1406,7 +1406,7 @@ class D(Generic[U, T]): ...
Default level: error ·
Added in 0.0.12 ·
Related issues ·
-View source
+View source
@@ -1485,7 +1485,7 @@ a = 20 / 0 # type: ignore
Default level: error ·
Added in 0.0.1-alpha.17 ·
Related issues ·
-View source
+View source
@@ -1524,7 +1524,7 @@ carol = Person(name="Carol", aeg=25) # typo!
Default level: warn ·
Added in 0.0.15 ·
Related issues ·
-View source
+View source
@@ -1585,7 +1585,7 @@ def f(x, y, /): # Python 3.8+ syntax
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1620,7 +1620,7 @@ def f(t: TypeVar("U")): ...
Default level: error ·
Added in 0.0.18 ·
Related issues ·
-View source
+View source
@@ -1648,7 +1648,7 @@ match x:
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1682,7 +1682,7 @@ class B(metaclass=f): ...
Default level: error ·
Added in 0.0.1-alpha.20 ·
Related issues ·
-View source
+View source
@@ -1789,7 +1789,7 @@ Correct use of `@override` is enforced by ty's [`invalid-explicit-override`](#in
Default level: error ·
Added in 0.0.1-alpha.19 ·
Related issues ·
-View source
+View source
@@ -1843,7 +1843,7 @@ AttributeError: Cannot overwrite NamedTuple attribute _asdict
Default level: error ·
Added in 0.0.1-alpha.27 ·
Related issues ·
-View source
+View source
@@ -1873,7 +1873,7 @@ Baz = NewType("Baz", int | str) # error: invalid base for `typing.NewType`
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1923,7 +1923,7 @@ def foo(x: int) -> int: ...
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1949,7 +1949,7 @@ def f(a: int = ''): ...
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1980,7 +1980,7 @@ P2 = ParamSpec("S2") # error: ParamSpec name must match the variable it's assig
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2014,7 +2014,7 @@ TypeError: Protocols can only inherit from other protocols, got
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2063,7 +2063,7 @@ def g():
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2092,7 +2092,7 @@ def func() -> int:
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2188,7 +2188,7 @@ class C: ...
Default level: error ·
Added in 0.0.10 ·
Related issues ·
-View source
+View source
@@ -2228,13 +2228,42 @@ class MyClass:
return True
```
+## `invalid-type-alias`
+
+
+Default level: error ·
+Added in 0.0.28 ·
+Related issues ·
+View source
+
+
+
+**What it does**
+
+Checks for invalid PEP 695 `type` statements.
+
+**Why is this bad?**
+
+A `type` statement is only valid in module and class scopes, and a type alias name should
+not be redeclared in the same scope.
+
+**Examples**
+
+```python
+type Alias = int
+type Alias = str # error: type alias already defined
+
+def f():
+ type Local = int # error: type statements are not allowed in function scopes
+```
+
## `invalid-type-alias-type`
Default level: error ·
Added in 0.0.1-alpha.6 ·
Related issues ·
-View source
+View source
@@ -2261,7 +2290,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
Default level: error ·
Added in 0.0.1-alpha.29 ·
Related issues ·
-View source
+View source
@@ -2308,7 +2337,7 @@ Bar[int] # error: too few arguments
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2338,7 +2367,7 @@ TYPE_CHECKING = ''
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2368,7 +2397,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
Default level: error ·
Added in 0.0.1-alpha.11 ·
Related issues ·
-View source
+View source
@@ -2402,7 +2431,7 @@ f(10) # Error
Default level: error ·
Added in 0.0.1-alpha.11 ·
Related issues ·
-View source
+View source
@@ -2436,7 +2465,7 @@ class C:
Default level: error ·
Added in 0.0.15 ·
Related issues ·
-View source
+View source
@@ -2467,7 +2496,7 @@ def g[U, T: U](): ... # error: [invalid-type-variable-bound]
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2514,7 +2543,7 @@ U = TypeVar('U', list[int], int) # valid constrained Type
Default level: error ·
Added in 0.0.16 ·
Related issues ·
-View source
+View source
@@ -2546,7 +2575,7 @@ U = TypeVar("U", int, str, default=bytes) # error: [invalid-type-variable-defau
Default level: error ·
Added in 0.0.28 ·
Related issues ·
-View source
+View source
@@ -2577,7 +2606,7 @@ class Child(Base):
Default level: error ·
Added in 0.0.14 ·
Related issues ·
-View source
+View source
@@ -2612,7 +2641,7 @@ def f(x: dict):
Default level: error ·
Added in 0.0.9 ·
Related issues ·
-View source
+View source
@@ -2643,7 +2672,7 @@ class Foo(TypedDict):
Default level: error ·
Added in 0.0.25 ·
Related issues ·
-View source
+View source
@@ -2674,7 +2703,7 @@ def gen() -> Iterator[int]:
Default level: error ·
Added in 0.0.14 ·
Related issues ·
-View source
+View source
@@ -2729,7 +2758,7 @@ def h(arg2: type):
Default level: error ·
Added in 0.0.15 ·
Related issues ·
-View source
+View source
@@ -2772,7 +2801,7 @@ def g(arg: object):
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2797,7 +2826,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
Default level: error ·
Added in 0.0.1-alpha.20 ·
Related issues ·
-View source
+View source
@@ -2830,7 +2859,7 @@ alice["age"] # KeyError
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2859,7 +2888,7 @@ func("string") # error: [no-matching-overload]
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2885,7 +2914,7 @@ for i in 34: # TypeError: 'int' object is not iterable
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2909,7 +2938,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
Default level: error ·
Added in 0.0.1-alpha.29 ·
Related issues ·
-View source
+View source
@@ -2942,7 +2971,7 @@ class B(A):
Default level: error ·
Added in 0.0.16 ·
Related issues ·
-View source
+View source
@@ -2975,7 +3004,7 @@ class B(A):
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3002,7 +3031,7 @@ f(1, x=2) # Error raised here
Default level: error ·
Added in 0.0.1-alpha.22 ·
Related issues ·
-View source
+View source
@@ -3029,7 +3058,7 @@ f(x=1) # Error raised here
Default level: ignore ·
Added in 0.0.1-alpha.22 ·
Related issues ·
-View source
+View source
@@ -3062,7 +3091,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
Default level: warn ·
Added in 0.0.1-alpha.22 ·
Related issues ·
-View source
+View source
@@ -3094,7 +3123,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
Default level: ignore ·
Added in 0.0.1-alpha.22 ·
Related issues ·
-View source
+View source
@@ -3131,7 +3160,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
Default level: warn ·
Added in 0.0.23 ·
Related issues ·
-View source
+View source
@@ -3158,7 +3187,7 @@ html.parser # AttributeError: module 'html' has no attribute 'parser'
Default level: ignore ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3222,7 +3251,7 @@ def test(): -> "int":
Default level: warn ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3249,7 +3278,7 @@ cast(int, f()) # Redundant
Default level: warn ·
Added in 0.0.18 ·
Related issues ·
-View source
+View source
@@ -3281,7 +3310,7 @@ class C:
Default level: error ·
Added in 0.0.20 ·
Related issues ·
-View source
+View source
@@ -3315,7 +3344,7 @@ class Outer[T]:
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3345,7 +3374,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3374,7 +3403,7 @@ class B(A): ... # Error raised here
Default level: error ·
Added in 0.0.1-alpha.30 ·
Related issues ·
-View source
+View source
@@ -3408,7 +3437,7 @@ class F(NamedTuple):
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3435,7 +3464,7 @@ f("foo") # Error raised here
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3463,7 +3492,7 @@ def _(x: int):
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3509,7 +3538,7 @@ class A:
Default level: error ·
Added in 0.0.20 ·
Related issues ·
-View source
+View source
@@ -3546,7 +3575,7 @@ class C(Generic[T]):
Default level: warn ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3570,7 +3599,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3597,7 +3626,7 @@ f(x=1, y=2) # Error raised here
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3625,7 +3654,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
Default level: warn ·
Added in 0.0.1-alpha.15 ·
Related issues ·
-View source
+View source
@@ -3683,7 +3712,7 @@ def g():
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3708,7 +3737,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3733,7 +3762,7 @@ print(x) # NameError: name 'x' is not defined
Default level: warn ·
Added in 0.0.1-alpha.7 ·
Related issues ·
-View source
+View source
@@ -3772,7 +3801,7 @@ class D(C): ... # error: [unsupported-base]
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3809,7 +3838,7 @@ b1 < b2 < b1 # exception raised here
Default level: ignore ·
Added in 0.0.12 ·
Related issues ·
-View source
+View source
@@ -3849,7 +3878,7 @@ def factory(base: type[Base]) -> type:
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3877,7 +3906,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'
Default level: warn ·
Preview (since 0.0.21) ·
Related issues ·
-View source
+View source
@@ -3983,7 +4012,7 @@ to `false`.
Default level: warn ·
Added in 0.0.1-alpha.22 ·
Related issues ·
-View source
+View source
@@ -4046,7 +4075,7 @@ def foo(x: int | str) -> int | str:
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
diff --git a/crates/ty_python_semantic/resources/mdtest/class/super.md b/crates/ty_python_semantic/resources/mdtest/class/super.md
index e449c46907becb..7a359d78ec0468 100644
--- a/crates/ty_python_semantic/resources/mdtest/class/super.md
+++ b/crates/ty_python_semantic/resources/mdtest/class/super.md
@@ -579,6 +579,7 @@ def f(x: int):
# error: [invalid-super-argument] "`int` is not a valid class"
super(x, x)
+ # error: [invalid-type-alias] "`type` statements are not allowed in function scopes"
type IntAlias = int
# error: [invalid-super-argument] "`TypeAliasType` is not a valid class"
super(IntAlias, 0)
diff --git a/crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md b/crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md
index 1941be3e9bf331..44e8580da7827d 100644
--- a/crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md
+++ b/crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md
@@ -321,11 +321,13 @@ python-version = "3.12"
```py
def _():
+ # error: [invalid-type-alias] "`type` statements are not allowed in function scopes"
# error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
# error: [invalid-syntax] "yield expression cannot be used within a TypeVar bound"
type X[T: (yield 1)] = int
def _():
+ # error: [invalid-type-alias] "`type` statements are not allowed in function scopes"
# error: [invalid-type-form] "`yield` expressions are not allowed in type alias values"
# error: [invalid-syntax] "yield expression cannot be used within a type alias"
type Y = (yield 1)
diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/aliases.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/aliases.md
index 396181bfc4db5a..3798b5d4dbff3d 100644
--- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/aliases.md
+++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/aliases.md
@@ -251,9 +251,9 @@ def _(g: G):
Unless a type default was provided:
```py
-type G[T = int] = list[T]
+type GWithDefault[T = int] = list[T]
-def _(g: G):
+def _(g: GWithDefault):
reveal_type(g) # revealed: list[int]
```
@@ -268,9 +268,9 @@ A self-referential default that does not reference itself in the alias body shou
even when the default is evaluated (e.g., by omitting the type argument):
```py
-type B[T = B] = list[T]
+type SelfDefaultB[T = SelfDefaultB] = list[T]
-def _(x: B) -> None:
+def _(x: SelfDefaultB) -> None:
pass
```
@@ -427,12 +427,12 @@ reveal_type(get_value(d, "a")) # revealed: int
It also works in the reverse direction, where the type alias is used as the argument type:
```py
-type MyList[T] = list[T]
+type MyListAlias[T] = list[T]
def head[T](l: list[T]) -> T:
return l[0]
-def _(x: MyList[int]):
+def _(x: MyListAlias[int]):
reveal_type(head(x)) # revealed: int
```
diff --git a/crates/ty_python_semantic/resources/mdtest/pep695_type_aliases.md b/crates/ty_python_semantic/resources/mdtest/pep695_type_aliases.md
index 35967424387674..59e1886e26f360 100644
--- a/crates/ty_python_semantic/resources/mdtest/pep695_type_aliases.md
+++ b/crates/ty_python_semantic/resources/mdtest/pep695_type_aliases.md
@@ -123,12 +123,45 @@ class Foo:
But narrowing of names used in the type alias is still respected:
```py
-def _(flag: bool):
- t = int if flag else None
- if t is not None:
- type Alias = t | str
- def f(x: Alias):
- reveal_type(x) # revealed: int | str
+flag = True
+t = int if flag else None
+if t is not None:
+ type NarrowedAlias = t | str
+
+def f(x: NarrowedAlias):
+ reveal_type(x) # revealed: int | str
+```
+
+`type` statements are only allowed in module and class scopes:
+
+```py
+class C:
+ type Alias = int
+
+def _():
+ # error: [invalid-type-alias] "`type` statements are not allowed in function scopes"
+ type Local = int
+```
+
+## Redeclared aliases
+
+
+
+```py
+type Redeclared = int
+# error: [invalid-type-alias] "Type alias `Redeclared` is already defined in this scope"
+type Redeclared = str
+```
+
+```py
+from random import random
+
+flag = random() > 0.5
+if flag:
+ type BranchRedeclared = int
+else:
+ # error: [invalid-type-alias] "Type alias `BranchRedeclared` is already defined in this scope"
+ type BranchRedeclared = str
```
## Generic type aliases
diff --git a/crates/ty_python_semantic/resources/mdtest/promotion.md b/crates/ty_python_semantic/resources/mdtest/promotion.md
index 5de65428207cd8..343373721cf0fe 100644
--- a/crates/ty_python_semantic/resources/mdtest/promotion.md
+++ b/crates/ty_python_semantic/resources/mdtest/promotion.md
@@ -511,9 +511,9 @@ def _(flag: bool):
reveal_type(promotable4 or unpromotable4) # revealed: Literal[True]
reveal_type([promotable4 or unpromotable4]) # revealed: list[Literal[True]]
-type X = Literal[b"bar"]
+type XBytes = Literal[b"bar"]
-def _(x1: X | None, x2: X):
+def _(x1: XBytes | None, x2: XBytes):
reveal_type([x1, x2]) # revealed: list[Literal[b"bar"] | None]
reveal_type([x1 or x2]) # revealed: list[Literal[b"bar"]]
```
diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/pep695_type_aliases.\342\200\246_-_PEP_695_type_aliases_-_Redeclared_aliases_(7e8460432a17eeae).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/pep695_type_aliases.\342\200\246_-_PEP_695_type_aliases_-_Redeclared_aliases_(7e8460432a17eeae).snap"
new file mode 100644
index 00000000000000..1170537a467083
--- /dev/null
+++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/pep695_type_aliases.\342\200\246_-_PEP_695_type_aliases_-_Redeclared_aliases_(7e8460432a17eeae).snap"
@@ -0,0 +1,59 @@
+---
+source: crates/ty_test/src/lib.rs
+expression: snapshot
+---
+
+---
+mdtest name: pep695_type_aliases.md - PEP 695 type aliases - Redeclared aliases
+mdtest path: crates/ty_python_semantic/resources/mdtest/pep695_type_aliases.md
+---
+
+# Python source files
+
+## mdtest_snippet.py
+
+```
+ 1 | type Redeclared = int
+ 2 | # error: [invalid-type-alias] "Type alias `Redeclared` is already defined in this scope"
+ 3 | type Redeclared = str
+ 4 | from random import random
+ 5 |
+ 6 | flag = random() > 0.5
+ 7 | if flag:
+ 8 | type BranchRedeclared = int
+ 9 | else:
+10 | # error: [invalid-type-alias] "Type alias `BranchRedeclared` is already defined in this scope"
+11 | type BranchRedeclared = str
+```
+
+# Diagnostics
+
+```
+error[invalid-type-alias]: Type alias `Redeclared` is already defined in this scope
+ --> src/mdtest_snippet.py:1:6
+ |
+1 | type Redeclared = int
+ | ---------- `Redeclared` previously defined here
+2 | # error: [invalid-type-alias] "Type alias `Redeclared` is already defined in this scope"
+3 | type Redeclared = str
+ | ^^^^^^^^^^
+4 | from random import random
+ |
+
+```
+
+```
+error[invalid-type-alias]: Type alias `BranchRedeclared` is already defined in this scope
+ --> src/mdtest_snippet.py:8:10
+ |
+ 6 | flag = random() > 0.5
+ 7 | if flag:
+ 8 | type BranchRedeclared = int
+ | ---------------- `BranchRedeclared` previously defined here
+ 9 | else:
+10 | # error: [invalid-type-alias] "Type alias `BranchRedeclared` is already defined in this scope"
+11 | type BranchRedeclared = str
+ | ^^^^^^^^^^^^^^^^
+ |
+
+```
diff --git a/crates/ty_python_semantic/src/semantic_index/builder.rs b/crates/ty_python_semantic/src/semantic_index/builder.rs
index ec11cf3ab045e6..7f4cc874b55fa6 100644
--- a/crates/ty_python_semantic/src/semantic_index/builder.rs
+++ b/crates/ty_python_semantic/src/semantic_index/builder.rs
@@ -1920,6 +1920,13 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
.map(|name| name.id.clone())
.unwrap_or("".into()),
);
+
+ if type_alias.name.as_name_expr().is_some() {
+ let use_id = self.current_ast_ids().record_use(&*type_alias.name);
+ self.current_use_def_map_mut()
+ .record_use(symbol.into(), use_id);
+ }
+
self.add_definition(symbol.into(), type_alias);
self.visit_expr(&type_alias.name);
diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs
index 3a87c9fbaae292..68a4d0f02fe05f 100644
--- a/crates/ty_python_semantic/src/types/diagnostic.rs
+++ b/crates/ty_python_semantic/src/types/diagnostic.rs
@@ -89,6 +89,7 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
registry.register_lint(&INVALID_GENERIC_CLASS);
registry.register_lint(&INVALID_LEGACY_TYPE_VARIABLE);
registry.register_lint(&INVALID_PARAMSPEC);
+ registry.register_lint(&INVALID_TYPE_ALIAS);
registry.register_lint(&INVALID_TYPE_ALIAS_TYPE);
registry.register_lint(&INVALID_NEWTYPE);
registry.register_lint(&INVALID_METACLASS);
@@ -1387,6 +1388,29 @@ declare_lint! {
}
}
+declare_lint! {
+ /// ## What it does
+ /// Checks for invalid PEP 695 `type` statements.
+ ///
+ /// ## Why is this bad?
+ /// A `type` statement is only valid in module and class scopes, and a type alias name should
+ /// not be redeclared in the same scope.
+ ///
+ /// ## Examples
+ /// ```python
+ /// type Alias = int
+ /// type Alias = str # error: type alias already defined
+ ///
+ /// def f():
+ /// type Local = int # error: type statements are not allowed in function scopes
+ /// ```
+ pub(crate) static INVALID_TYPE_ALIAS = {
+ summary: "detects invalid PEP 695 `type` statements",
+ status: LintStatus::stable("0.0.28"),
+ default_level: Level::Error,
+ }
+}
+
declare_lint! {
/// ## What it does
/// Checks for the creation of invalid `TypeAliasType`s
diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs
index 74bb3c7d394028..298faf2ac64280 100644
--- a/crates/ty_python_semantic/src/types/infer/builder.rs
+++ b/crates/ty_python_semantic/src/types/infer/builder.rs
@@ -64,18 +64,19 @@ use crate::types::diagnostic::{
self, CALL_NON_CALLABLE, CONFLICTING_DECLARATIONS, CYCLIC_TYPE_ALIAS_DEFINITION,
GeneratorMismatchKind, INEFFECTIVE_FINAL, INVALID_ARGUMENT_TYPE, INVALID_ASSIGNMENT,
INVALID_ATTRIBUTE_ACCESS, INVALID_DECLARATION, INVALID_ENUM_MEMBER_ANNOTATION,
- INVALID_LEGACY_TYPE_VARIABLE, INVALID_NEWTYPE, INVALID_PARAMSPEC, INVALID_TYPE_ALIAS_TYPE,
- INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL, INVALID_TYPE_VARIABLE_BOUND,
- INVALID_TYPE_VARIABLE_CONSTRAINTS, POSSIBLY_MISSING_IMPLICIT_CALL, POSSIBLY_MISSING_SUBMODULE,
- UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_GLOBAL, UNRESOLVED_REFERENCE,
- UNSUPPORTED_OPERATOR, UNUSED_AWAITABLE, hint_if_stdlib_attribute_exists_on_other_versions,
- report_attempted_protocol_instantiation, report_bad_dunder_set_call,
- report_call_to_abstract_method, report_cannot_pop_required_field_on_typed_dict,
- report_invalid_assignment, report_invalid_attribute_assignment,
- report_invalid_class_match_pattern, report_invalid_exception_caught,
- report_invalid_exception_cause, report_invalid_exception_raised,
- report_invalid_exception_tuple_caught, report_invalid_generator_yield_type,
- report_invalid_key_on_typed_dict, report_invalid_type_checking_constant,
+ INVALID_LEGACY_TYPE_VARIABLE, INVALID_NEWTYPE, INVALID_PARAMSPEC, INVALID_TYPE_ALIAS,
+ INVALID_TYPE_ALIAS_TYPE, INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL,
+ INVALID_TYPE_VARIABLE_BOUND, INVALID_TYPE_VARIABLE_CONSTRAINTS, POSSIBLY_MISSING_IMPLICIT_CALL,
+ POSSIBLY_MISSING_SUBMODULE, UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_GLOBAL,
+ UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR, UNUSED_AWAITABLE,
+ hint_if_stdlib_attribute_exists_on_other_versions, report_attempted_protocol_instantiation,
+ report_bad_dunder_set_call, report_call_to_abstract_method,
+ report_cannot_pop_required_field_on_typed_dict, report_invalid_assignment,
+ report_invalid_attribute_assignment, report_invalid_class_match_pattern,
+ report_invalid_exception_caught, report_invalid_exception_cause,
+ report_invalid_exception_raised, report_invalid_exception_tuple_caught,
+ report_invalid_generator_yield_type, report_invalid_key_on_typed_dict,
+ report_invalid_type_checking_constant,
report_match_pattern_against_non_runtime_checkable_protocol,
report_match_pattern_against_typed_dict, report_possibly_missing_attribute,
report_possibly_unresolved_reference, report_unsupported_augmented_assignment,
@@ -1455,6 +1456,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
type_alias: &ast::StmtTypeAlias,
definition: Definition<'db>,
) {
+ self.report_invalid_type_alias_scope(type_alias, definition);
+ self.report_redeclared_type_alias(type_alias, definition);
+
self.infer_expression(&type_alias.name, TypeContext::default());
// Check that no type parameter with a default follows a TypeVarTuple
@@ -1487,6 +1491,81 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
);
}
+ fn report_invalid_type_alias_scope(
+ &mut self,
+ type_alias: &ast::StmtTypeAlias,
+ definition: Definition<'db>,
+ ) {
+ let db = self.db();
+ if !definition
+ .scope(db)
+ .scope(db)
+ .kind()
+ .is_non_lambda_function()
+ {
+ return;
+ }
+
+ if let Some(builder) = self.context.report_lint(&INVALID_TYPE_ALIAS, type_alias) {
+ builder.into_diagnostic("`type` statements are not allowed in function scopes");
+ }
+ }
+
+ fn report_redeclared_type_alias(
+ &mut self,
+ type_alias: &ast::StmtTypeAlias,
+ definition: Definition<'db>,
+ ) {
+ let Some(type_alias_name) = type_alias.name.as_name_expr() else {
+ return;
+ };
+
+ let db = self.db();
+ let scope = definition.scope(db);
+ let use_def = self.index.use_def_map(scope.file_scope_id(db));
+
+ // Type alias redeclarations are a scope-level property, so we need all earlier
+ // definitions for this symbol in the scope, not just the bindings reachable here.
+ let Some(previous_definition) = use_def
+ .all_definitions_with_usage()
+ .filter_map(|(_, state, _)| state.definition())
+ .filter(|previous_definition| previous_definition.place(db) == definition.place(db))
+ .filter(|previous_definition| {
+ matches!(
+ previous_definition.kind(db),
+ DefinitionKind::TypeAlias(previous_type_alias)
+ if previous_type_alias
+ .node(self.module())
+ .node_index()
+ .load()
+ < type_alias.node_index().load()
+ )
+ })
+ .max_by_key(|definition| definition.focus_range(db, self.module()).start())
+ else {
+ return;
+ };
+
+ let Some(builder) = self
+ .context
+ .report_lint(&INVALID_TYPE_ALIAS, &*type_alias.name)
+ else {
+ return;
+ };
+
+ let mut diagnostic = builder.into_diagnostic(format_args!(
+ "Type alias `{}` is already defined in this scope",
+ type_alias_name.id
+ ));
+ diagnostic.annotate(
+ Annotation::secondary(previous_definition.focus_range(db, self.module()).into())
+ .message(format_args!(
+ "`{}` previously defined here",
+ type_alias_name.id
+ )),
+ );
+ }
+
fn infer_if_statement(&mut self, if_statement: &ast::StmtIf) {
let ast::StmtIf {
range: _,
diff --git a/ty.schema.json b/ty.schema.json
index ea736ca892a153..8def2774507271 100644
--- a/ty.schema.json
+++ b/ty.schema.json
@@ -964,6 +964,16 @@
}
]
},
+ "invalid-type-alias": {
+ "title": "detects invalid PEP 695 `type` statements",
+ "description": "## What it does\nChecks for invalid PEP 695 `type` statements.\n\n## Why is this bad?\nA `type` statement is only valid in module and class scopes, and a type alias name should\nnot be redeclared in the same scope.\n\n## Examples\n```python\ntype Alias = int\ntype Alias = str # error: type alias already defined\n\ndef f():\n type Local = int # error: type statements are not allowed in function scopes\n```",
+ "default": "error",
+ "oneOf": [
+ {
+ "$ref": "#/definitions/Level"
+ }
+ ]
+ },
"invalid-type-alias-type": {
"title": "detects invalid TypeAliasType definitions",
"description": "## What it does\nChecks for the creation of invalid `TypeAliasType`s\n\n## Why is this bad?\nThere are several requirements that you must follow when creating a `TypeAliasType`.\n\n## Examples\n```python\nfrom typing import TypeAliasType\n\nIntOrStr = TypeAliasType(\"IntOrStr\", int | str) # okay\nNewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name must be a string literal\n```",