Ruff ty 类型检查器 invalid-type-form 诊断全解析:无效类型表达式检测与错误恢复机制
导读
invalid-type-form 是 Ruff 内置类型检查器 ty(位于 crates/ty_python_semantic)用于检测非法类型表达式的核心 lint:凡是不能合法地作为类型表达式解读的 Python 语法,都会触发该诊断并给出可操作的修复建议。本文以 ty 的官方测试文档 invalid.md 为骨架,结合源码深入讲解 ty 如何判定非法类型形式、如何做错误恢复避免级联报错,以及针对集合字面量、tuple 特化、Literal 包装等常见场景提供的 unsafe fix 修复策略,帮助开发者理解 ty 的类型系统边界并写出可被类型检查器正确理解的注解。
一、什么是 invalid-type-form:从 lint 定义说起
invalid-type-form 是 ty 对"无法被合法解读为类型表达式的表达式"发出的错误诊断。在 lint 文档 中,其用途被描述为:
- 它做什么:检查那些被当作[类型表达式]使用、却无法合法地按类型表达式解读的表达式;
- 为什么是错误:这类表达式无法被 ty 理解,某些情况下还会在运行时抛错;
- 示例:
from typing import Annotated
# Int literals are not allowed in this context in type expressions
a: list[1] # error
# `Annotated` expects at least two arguments
b: Annotated[int] # error
从源码看,该 lint 在 diagnostic.rs 中通过 declare_lint! 宏声明,默认级别为 Level::Error,状态为 stable;并在 同文件第 127 行 注册进 lint 注册表。在 type inference 过程中,核心函数是 type_expression.rs 中的 infer_type_expression / infer_type_expression_no_store,其内部对非法节点调用 report_invalid_type_expression,后者直接上报 INVALID_TYPE_FORM 并附加上类型表达式规范的参考链接说明。
所有非法形式在类型系统中被建模为 InvalidTypeExpression 枚举的各类变体(见 types.rs),包括 InvalidType(普通非法类型)、InvalidBareTypeVarTuple、Deprecated、TypedDict、Concatenate、RequiresOneArgument、RequiresTwoArguments 等,由特殊形式推断(special_form.rs)和类型表达式推断共同产生。
二、第一类无效类型:把"变量"当类型用
2.1 为什么 type[int]、Literal[True] 不能直接作为注解
文档开篇给出了一组非常典型的反例:把运行期对象当作类型注解使用,ty 一律报 invalid-type-form:
import typing
from ty_extensions import AlwaysTruthy, AlwaysFalsy
from typing_extensions import Literal, Never
class A: ...
def _(
a: type[int], # 变量的类型是 `type[int]`
b: AlwaysTruthy, # 运行期总是为真的标记类型
c: AlwaysFalsy, # 运行期总是为假的标记类型
d: Literal[True], # 字面量类型对象
e: Literal["bar"],
f: Literal[b"foo"],
g: tuple[int, str],
h: Never,
i: int,
j: A,
):
def foo(): ...
def invalid(
a_: a, # error: [invalid-type-form] "Variable of type `type[int]` is not allowed in a parameter annotation"
b_: b, # error: [invalid-type-form]
...
k_: i, # error: [invalid-type-form] "Variable of type `int` is not allowed in a parameter annotation"
l_: j, # error: [invalid-type-form] "Variable of type `A` is not allowed in a parameter annotation"
):
...
这些例子揭示了 ty 的一条核心规则:注解位置只接受"类型表达式",不接受"求值结果为类型对象的表达式"。type[int] 是一个对象,Literal[True] 也是对象——它们可以作为值存在,但不能直接出现在参数注解里。有趣的是,ty 会为常见错误给出专门的子诊断(详见下文"模块字面量"一节),并让所有这类参数的类型都回退为 reveal_type(...) # revealed: Unknown。
2.2 推导式与生成器:容器构建表达式全部非法
受 Python typing 官方一致性测试套件(conformance test suite)启发,ty 明确拒绝将推导式或生成器表达式用作类型注解:
B = [x for x in range(42)]
C = {x for x in range(42)}
D = {x: y for x, y in enumerate(range(42))}
E = (x for x in range(42))
def _(
b: B, # error: [invalid-type-form]
c: C, # error: [invalid-type-form]
d: D, # error: [invalid-type-form]
e: E, # error: [invalid-type-form]
):
reveal_type(b) # revealed: Unknown
...
这些列表/集合/字典推导式与生成器表达式的求值结果是容器对象而非类型,因此一律视为无效类型形式。
三、第二类无效类型:非法的 AST 节点
3.1 字面量、运算、调用、yield/await 等一律禁止
ty 的文档给出了一组"百科全书式"的非法 AST 节点清单(均位于同步/异步函数的嵌套函数参数中,以规避无关的语法错误):
async def outer_async(): # avoid unrelated syntax errors on `yield` and `await`
def _(
a: 1, # "Int literals are not allowed in this context in a parameter annotation"
b: 2.3, # "Float literals are not allowed in parameter annotations"
c: 4j, # "Complex literals are not allowed in parameter annotations"
d: True, # "Boolean literals are not allowed in this context in a parameter annotation"
e: int | b"foo", # unsupported-operator + "Bytes literals are not allowed..."
f: 1 and 2, # "Boolean operations are not allowed in parameter annotations"
g: 1 or 2, # "Boolean operations are not allowed in parameter annotations"
h: (foo := 1), # "Named expressions are not allowed in parameter annotations"
i: not 1, # "Unary operations are not allowed in parameter annotations"
j: lambda: 1, # "`lambda` expressions are not allowed in parameter annotations"
k: 1 if True else 2, # "`if` expressions are not allowed in parameter annotations"
l: await baz(), # "`await` expressions are not allowed in parameter annotations"
m: (yield 1), # "`yield` expressions are not allowed in parameter annotations"
n: 1 < 2, # "Comparison expressions are not allowed in parameter annotations"
o: bar(), # "Function calls are not allowed in parameter annotations"
p: int | f"foo", # unsupported-operator + "F-strings are not allowed..."
q: [1, 2, 3][1:2], # "Only simple names and dotted names can be subscripted in parameter annotations"
r: list[T][int], # "Only simple names and dotted names can be subscripted in parameter annotations"
s: list[list[T][int]], # "Only simple names and dotted names can be subscripted in parameter annotations"
):
reveal_type(a) # revealed: Unknown
reveal_type(e) # revealed: int | Unknown
reveal_type(p) # revealed: int | Unknown
值得注意的细节:
- 错误恢复是部分进行的:
e: int | b"foo"中,b"foo"是非法的,但int一侧仍然推断成功,因此最终类型是int | Unknown——而不是整个注解都变成Unknown。p同理。 - 下标操作只能作用于简单名字和点分名字:
list[T][int]这种"对下标结果再次下标"的写法被拒绝,因为list[T]不是一个可继续下标的名称形式。
3.2 非法二元/一元运算符
文档还系统测试了所有非法的运算符形式,其中 +、-、*、@、/、%、**、//、<<、>>、^ 都报 "Invalid binary operator X in type annotation",而 5 & 3 与 ~3 由于同时是整数位运算,额外叠加报 "Int literals are not allowed...":
def invalid_binary_operators(
a: "1" + "2", # "Invalid binary operator `+` in type annotation"
b: 3 - 5.0, # "Invalid binary operator `-` in type annotation"
...
d: Mat(4) @ Mat(2), # "Invalid binary operator `@` in type annotation"(即使 Mat 重载了 __matmul__)
...
l: 5 & 3, # int literal 错误 ×2
m: ~3, # int literal 错误
):
...
这里的 Mat 类重载了 __matmul__ 并返回 int,但 ty 依然拒绝 @——说明类型表达式上下文中的运算符合法性由语法形式决定,与运算符重载结果无关。
四、错误恢复:避免级联诊断的精妙设计
文档用专门一节阐述了 ty 的错误恢复策略,这是该 lint 工程质量的关键体现。
4.1 外层节点非法时抑制内层
# error: [invalid-type-form] (只报这一个)
x: [[int]]
[[int]] 外层是列表字面量,内层还有一个非法列表字面量 [int]。ty 只报告外层的 invalid-type-form,忽略内层的非法列表,从而避免同一声明产生两条重叠的诊断。
4.2 运行期错误仍然上报
错误恢复并不豁免所有内层问题:如果非法 AST 节点内部存在真正的运行期错误(比如未定义名字),这些"比类型规范洁癖更严重"的问题仍然上报:
# error: [invalid-type-form] "List literals are not allowed in this context in a type expression"
# error: [unresolved-reference] "Name `foo` used when not defined"
x: [[foo]]
4.3 字符串注解内的未解析引用不再报
反过来说,如果检测到字符串注解本身就是非法类型形式,ty 会抑制其中未解析引用的诊断——因为字符串化的注解根本不会在运行期求值,报未定义名字只会增加噪音:
# error: [invalid-type-form] "List literals are not allowed in this context in a type expression"
x: "[[foo]]" # 不再额外报 "Name `foo` used when not defined"
从源码看,这与 ignore_runtime_errors 逻辑相关:当表达式处于延迟(deferred)状态或位于 stub 文件、TYPE_CHECKING 块中时,运行期错误被有意忽略(见 type_expression.rs)。
4.4 字符串注解中的非法下标操作数不得求值
一个更微妙的场景:字符串注解中的非法下标操作数不得被求值——特别是 lambda 默认值、函数式 TypedDict 参数不应产生级联的未解析引用诊断,赋值表达式(walrus)不应导致 panic。运行时文件与 stub 文件行为一致:
from typing_extensions import TypedDict
# error: [invalid-type-form] "Only simple names and dotted names can be subscripted in type expressions"
a: "(lambda value=missing: None)[int]"
# error: [invalid-type-form] "Only simple names and dotted names can be subscripted in type expressions"
b: "(lambda value=(name := int): None)[int]"
# error: [invalid-type-form] "Only simple names and dotted names can be subscripted in type expressions"
c: "TypedDict('T', {}, extra_items=missing)[int]"
即:虽然 missing、name 从未定义,但 ty 只报一条 invalid-type-form,绝不会叠加 unresolved-reference 或崩溃。
4.5 字符串注解中的非法下标参数
即使下标操作数是合法名字,当它不可特化(non-generic)时,ty 同样拒绝特化且不求值其参数,同时保留 type[...] 参数原有的回退类型;stub 文件使用相同的错误恢复:
from typing import Any, Tuple
# error: [invalid-type-form] "Non-generic class `int` cannot be specialized"
a: "int[(name := missing)]"
# error: [invalid-type-form] "Non-generic class `int` cannot be specialized"
b: "type[int[(name := missing)]]"
# error: [invalid-type-form] "Named expressions are not allowed"
c: "type[(name := missing)]"
# error: [invalid-type-form] "Named expressions are not allowed"
d: "type[Any[(name := missing)]]"
# error: [invalid-type-form] "`lambda` expressions are not allowed"
e: "type[Tuple[lambda default=(name := missing): None]]"
# error: [invalid-type-form] "`lambda` expressions are not allowed"
f: "type[lambda default=(name := missing): None]"
4.6 求值注解:完整上报
与字符串注解相反,对于会被求值的注解,ty 会把非法类型参数和求值过程中遇到的错误一并报告:
[environment]
python-version = "3.13"
# error: [invalid-type-form] "Non-generic class `int` cannot be specialized"
# error: [unresolved-reference] "Name `missing` used when not defined"
a: int[(name := missing)]
# error: [invalid-type-form] "Named expressions are not allowed"
# error: [unresolved-reference] "Name `other_missing` used when not defined"
b: type[(other := other_missing)]
# error: [invalid-type-form] "Function calls are not allowed"
# error: [unresolved-reference] "Name `missing_call` used when not defined"
c: type[missing_call()]
五、tuple 特化的两条专门规则
5.1 不允许出现多个解包的可变长元组
当一个 tuple 特化中出现两个及以上解包的变长(variadic)元组时,ty 报错——因为 *tuple[int, ...] 的长度不定,无法与另一个变长元组并列摆放:
[environment]
python-version = "3.11"
from typing import TypeVarTuple, Unpack
Ts = TypeVarTuple("Ts")
def f(
x: tuple[*tuple[int, ...], *tuple[str, ...]], # error: "Multiple unpacked variadic tuples are not allowed in a `tuple` specialization"
x2: tuple[Unpack[tuple[int, ...]], Unpack[tuple[str, ...]]], # error(Unpack 写法同样拒绝)
y: tuple[*tuple[int, ...], str, int, *tuple[str, ...]], # error
y2: tuple[Unpack[tuple[int, ...]], str, int, Unpack[tuple[str, ...]]], # error
# 只要解包的不是变长元组就没问题:
z: tuple[*tuple[int, ...], *tuple[str]], # OK
z2: tuple[Unpack[tuple[int, ...]], Unpack[tuple[str]]], # OK
):
reveal_type(x) # revealed: tuple[int | str, ...]
reveal_type(z) # revealed: tuple[*tuple[int, ...], str]
T1 = tuple[int, *Ts, str, *Ts] # error(同一 TypeVarTuple 出现两次也是多个变长解包)
def func3(t: tuple[*Ts]):
t5: tuple[*tuple[str], *Ts] # OK
t6: tuple[*tuple[str, ...], *Ts] # error
观察 revealed 类型可发现 ty 的合并策略:两个变长元组解包时报错,但如果错误恢复后仍要给出类型,则合并为 tuple[int | str, ...];而 z 这类合法写法被精确表示为 tuple[*tuple[int, ...], str]。
5.2 省略号 ... 只能出现在二元特化的第二位
tuple[int, ...] 是合法写法,但 ... 一旦出现在其他位置即报错:
t1: tuple[int, ...] # OK
t2: tuple[int, int, ...] # error: "`...` can only be used as the second element in a two-element `tuple` specialization"
t3: tuple[...] # error(同上)
t4: tuple[..., int] # error(同上)
t5: tuple[int, ..., int] # error(同上)
t6: tuple[*tuple[str], ...] # error: "`...` cannot be used after an unpacked element"
t7: tuple[*tuple[str, ...], ...] # error(同上)
def invalid_typevartuple_ellipsis(
starred: tuple[*Ts, ...], # error: "`...` cannot be used after an unpacked element"
unpacked: tuple[Unpack[Ts], ...], # error(同上)
) -> None: ...
六、字符串注解与集合字面量:同样的非法性,同样的恢复
文档确认:字符串(前向引用)注解中的非法 AST 节点同样会被拒绝,错误消息与直接注解完全一致,且错误恢复后类型回退为 Unknown:
async def outer_async():
def _(
a: "1", # "Int literals are not allowed in this context in a parameter annotation"
b: "2.3", # "Float literals are not allowed in parameter annotations"
e: "1 and 2", # "Boolean operations are not allowed in parameter annotations"
g: "(foo := 1)", # "Named expressions are not allowed in parameter annotations"
i: "lambda: 1", # "`lambda` expressions are not allowed in parameter annotations"
k: "await baz()",# "`await` expressions are not allowed in parameter annotations"
n: "bar()", # "Function calls are not allowed in parameter annotations"
o: "[1, 2, 3][1:2]", # "Only simple names and dotted names can be subscripted in parameter annotations"
p: list[int].append, # "Only simple names, dotted names and subscripts can be used in parameter annotations"
q: list[list[int].append], # 同上,但恢复类型为 list[Unknown]
):
reveal_type(p) # revealed: Unknown
reveal_type(q) # revealed: list[Unknown]
一个值得注意的差异:在字符串注解里访问属性 list[int].append 也是非法的,只能使用"简单名字、点分名字和下标",这与直接注解中"只能下标简单名字和点分名字"的措辞略有不同。
集合类 AST 节点(字典/集合/推导式/生成器)同样是非法参数注解,且 ty 会给出贴心提示:
[environment]
python-version = "3.12"
def _(
a: {1: 2}, # "Dict literals are not allowed in parameter annotations"
b: {1, 2}, # "Set literals are not allowed in parameter annotations"
c: {k: v for k, v in [(1, 2)]}, # "Dict comprehensions are not allowed in parameter annotations"
d: [k for k in [1, 2]], # "List comprehensions are not allowed in parameter annotations"
e: {k for k in [1, 2]}, # "Set comprehensions are not allowed in parameter annotations"
f: (k for k in [1, 2]), # "Generator expressions are not allowed in parameter annotations"
g: [int, str], # "List literals are not allowed in this context in a parameter annotation"
h: (int, str), # "Tuple literals are not allowed... Did you mean `tuple[int, str]`?"
i: (), # "Tuple literals are not allowed... Did you mean `tuple[()]`?"
):
reveal_type(a) # revealed: Unknown
...
# 类型参数默认值 / 类型别名默认值中的集合字面量同样非法
class name_0[name_2: [int]]: # "List literals are not allowed... Did you mean `list[int]`?"
pass
class name_4[name_1: [{}]]: # "List literals are not allowed in this context in a type expression"
pass
七、常见错误的专用诊断与 unsafe fix
除通用错误外,ty 还针对高频错误提供了专用子诊断与自动修复(unsafe fix)。
7.1 把模块当类型用
Python 里"本想用模块中的类,却写了模块名"非常常见。ty 为此给出友好的子诊断,在 foo.py、PIL/Image.py、bar.py 三个文件的组合中验证了直接 import 与 from PIL import Image 两种形态:
# foo.py
import datetime
def f(x: datetime): ... # error: [invalid-type-form]
# PIL/Image.py
class Image: ...
# bar.py
from PIL import Image
def g(x: Image): ... # error: [invalid-type-form]
7.2 集合字面量修复:[int] → list[int] 等
当集合字面量的意图类型清晰时,ty 不仅报错,还提供"替换为下标内建类型"的修复。以下行为全部有快照(snapshot)佐证:
| 非法写法 | 建议替换 | 说明 |
|---|---|---|
[int](单元素列表) |
list[int] |
参数注解与返回注解均提供修复 |
[int, str](多元素列表) |
无 | 意图有歧义,不提供建议 |
()(空元组) |
tuple[()] |
空元组的类型 |
(int,)(单元素元组) |
tuple[int] |
定长单元素元组 |
(int, str)(多元素元组) |
tuple[int, str] |
对应元素类型的定长元组 |
{int: str}(单条目字典) |
dict[int, str] |
|
{str}(单元素集合) |
set[str] |
以列表修复为例,快照中完整呈现了诊断、Did you mean \list[int]`? 提示、help: Replace with `list[...]`以及note: This is an unsafe fix and may change runtime behavior的完整输出(例如将x: [int]改写为x: list[int]`)。所有修复都标注为 unsafe fix,因为把容器字面量改写为下标表达式会改变运行期行为。
括号保留策略
重写元组字面量时,ty 会保留首尾元素的括号(包括嵌套括号和整个元组的外层括号):
# fmt: off
first: ((int), str) # → tuple[(int), str]
last: (int, (str)) # → tuple[int, (str)]
single: (((int)),) # → tuple[((int))]
outer: (((int), (str))) # → (tuple[(int), (str)])
字典的键与值、集合元素、列表元素同样保留括号;对于要求括号的表达式(如 yield),修复也会保留括号以避免产生语法错误:
def generator():
yielded_key: {(yield int): str} # → dict[(yield int), str]
yielded_value: {int: (yield str)} # → dict[int, (yield str)]
yielded_element: {(yield int)} # → set[(yield int)]
7.3 修复的版本与作用域限制
ty 对修复的可用性做了非常细致的约束,每一条都有专门的测试快照:
- Python 3.9 及以上才提供集合字面量修复:内建容器类型在 Python 3.8 上不可下标,因此在
python-version = "3.8"环境下只报错、不提供会把代码改成list[...]的修复。 - Python 3.10 时对 starred 元素不提供修复:
[*types]、(*types,)、{*types}在 3.11 之前无法改写为下标(下标内不支持 starred),此时仅提示Did you mean \list[tuple[Unknown, ...]]`?` 之类。 - 多行集合字面量不提供修复:多行字面量内部可能含有注释,替换分隔符会丢失注释,因此拒绝自动修复(
list.py、tuple.py、dict.py、set.py四个文件分别覆盖)。 - 类属性不遮蔽方法体中的内建名:
class Container: set = 42中的set在方法体内不可见,不影响value: {int}被改写为set[int]。 - 嵌套注解作用域中的类属性遮蔽内建:泛型类型别名的类型参数作用域可以访问外层类的属性,因此
class C: list = 42中type Alias[T] = [int]仍会提供list[int]修复(文档以 TODO 注明这其实是visible_ancestor_scopes跳过类作用域导致的一个已知误报)。 - 项目级
__builtins__.pyi覆盖:如果项目提供了list: object的__builtins__.pyi,则[int]不再提供修复(因为改写后的list不再指向内建),而未被覆盖的set仍可修复。 - 字符串注解不提供修复:从带引号注解解析出的集合字面量没有可直接重写的源范围,因此
"[int]"、"(int, str)"等只报错、不修复。
7.4 callable 特殊诊断
小写 callable(内建可调用对象类型)被用作类型表达式时,参数与返回注解各报一条 invalid-type-form:
def decorator(fn: callable) -> callable: # error: [invalid-type-form] ×2
return fn
八、只在 Literal[...] 里合法的节点:自动包裹修复
x: 42 是非法的——要把 x 限制为值 42,应写 x: Literal[42]。ty 对整数字面量、bytes 字面量、布尔字面量,以及无法解析为前向注解的字符串,提供把值包裹进 Literal[...] 的 unsafe fix(必要时自动导入 Literal)。此功能在 python-version = "3.8" 下验证(typing.Literal 自 3.8 起可用)。
8.1 修复形态
def bad(
a: 42, # → a: Literal[42]
b: b"42", # → b: Literal[b"42"]
c: True, # → c: Literal[True]
d: "invalid syntax", # invalid-syntax-in-forward-annotation → d: Literal["invalid syntax"]
): ...
注意 d 走的是另一个 lint invalid-syntax-in-forward-annotation(前向注解语法错误),其修复同样是把原始字符串包裹进 Literal。
8.2 保留字面量原始拼写
修复会保留数字的字面量拼写、bytes 的转义序列和字符串定界符,即使其语义值不同:
def bad(
integer: (0x_FF), # → (Literal[0x_FF]),提示文本中显示值 255
data: b"\x41", # → Literal[b"\x41"]
quoted: "False", # → "Literal[False]"(前向注解内包裹)
string: """can't parse this""", # → Literal["""can't parse this"""]
): ...
8.3 智能导入复用与遮蔽处理
-
复用已有别名:
from typing import Literal as L时修复写作L[True];import typing as t时写作t.Literal[42]。 -
Literal被遮蔽时改用限定名:def qualified(Literal: int):内修复插入import typing并写作typing.Literal[True]。 -
两个名字都被占用时放弃修复:
def unavailable(Literal: int, typing: int):内只报错、不提供修复。 -
Python 3.7 的特殊处理:
typing中没有Literal。仅凭 typeshed 里 vendored 的typing_extensionsstub 不足以支撑修复——修复不引入来自typing_extensions的导入,除非:typing_extensions被声明为直接依赖(通过[dependency-metadata]中的projects/distributions/module-owners配置);- 该模块确实存在于 site-packages(
.venv); - 安装的版本确实导出
Literal符号。
三个条件缺一不可:缺少运行期导出(空
typing_extensions.py)时不给修复;模块不在 site-packages 时不给修复;父项目声明了依赖而嵌套项目未声明时,嵌套项目同样不给修复。这些案例分别由/.venv/<path-to-site-packages>/typing_extensions.py与main.py/member/main.py的组合测试覆盖。
九、这些测试是怎么跑起来的:mdtest 框架
invalid.md 是 ty 的 mdtest 测试语料:crates/ty_python_semantic 目录下的 mdtest.py 负责提取 Markdown 中的 Python 代码块(以及 runtime.py / stub.pyi / main.py 等命名文件)并运行类型检查。代码中的三类断言语法值得关注:
# error: [invalid-type-form] "具体消息":断言此行触发invalid-type-form,且消息与双引号内容匹配;reveal_type(x) # revealed: Unknown:断言推断出的类型;# snapshot: invalid-type-form配合<!-- snapshot-diagnostics -->与独立的\``snapshot代码块:把完整诊断快照写入测试输出,验证Did you mean ...?、help:、note: This is an unsafe fix...` 等细节。
[environment] TOML 块用于声明测试的 python-version(3.7 到 3.13 均有覆盖)、虚拟环境路径与 [dependency-metadata] 依赖元数据——这正是文档中大量"按 Python 版本条件化"行为的来源。同目录下的 annotations/ 还包含 literal.md、union.md、string.md、generic_alias.md、callable.md 等 20 余个专题测试文件,invalid.md 是其中覆盖最广的一份。
十、总结:ty 对无效类型表达式的处理原则
纵观整份测试文档,可以提炼出 ty 处理 invalid-type-form 的四条设计原则:
- 语法形式优先:类型表达式只认语法形态(名字、下标、联合、可选等),不认求值结果——
type[int]、Literal[True]、推导式、调用、yield/await全都被拒; - 分级报错:外层非法时抑制内层诊断,但真正的运行期错误(未定义名字)仍上报;字符串注解内部的未解析引用则一律静默;
- 可操作修复:能确定意图的就提供 unsafe fix——集合字面量改写为
list[...]/tuple[...]/dict[...]/set[...],字面量值包裹为Literal[...],并充分考虑 Python 版本、名称遮蔽、依赖元数据等约束; - 优雅恢复:即使节点非法,也要给出尽可能精确的回退类型(如
int | Unknown、list[Unknown]),保证类型检查器不会因为一处注解崩坏而放弃整个文件。
对于开发者而言,这份文档的实用价值在于:它精确划定了 ty(以及 typing 规范)认可的"类型表达式语法"边界。当你看到 invalid-type-form 报错时,优先怀疑注解里混入了运行期值而非类型——并用 Did you mean 提示与 unsafe fix 快速修正即可。
相关文件索引
- 测试语料:crates/ty_python_semantic/resources/mdtest/annotations/invalid.md
- lint 说明文档:crates/ty_python_semantic/resources/lint_docs/invalid-type-form.md
- lint 声明与注册:crates/ty_python_semantic/src/types/diagnostic.rs
- 类型表达式推断实现:crates/ty_python_semantic/src/types/infer/builder/type_expression.rs
- 非法类型表达式枚举与回退逻辑:crates/ty_python_semantic/src/types.rs
- 特殊形式(
Annotated、TypedDict等)校验:crates/ty_python_semantic/src/types/special_form.rs - mdtest 驱动:crates/ty_python_semantic/mdtest.py 与 crates/mdtest 工具 crate
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust4.21 K637- DDeepSeek-V4.1-FlashDeepSeek-V4.1-Flash 是一个多模态混合专家(MoE)模型,拥有 5520 亿骨干参数,并支持最多一百万 token 的上下文长度。该模型原生支持图像和文本输入,并以自回归方式生成文本Python230
jforgamejforgame是一个一站式游戏服务器开发框架。包含游戏服务器开发所需要的各种组件,比如网关,socket服务端与客户端,自定义高效消息编解码,游戏热更新,游戏通用工具等等。包含游戏服,跨服,匹配服,后台管理系统等实现,同时提供大量业务案例以供学习。亦可用于其他socket应用,例如及时聊天等。Java291
fizz-gateway-nodeAn Aggregation API Gateway in Java . FizzGate 是一个基于 Java开发的微服务聚合网关,是拥有自主知识产权的应用网关国产化替代方案,能够实现热服务编排聚合、自动授权选择、线上服务脚本编码、在线测试、高性能路由、API审核管理、回调管理等目的,拥有强大的自定义插件系统可以自行扩展,并且提供友好的图形化配置界面,能够快速帮助企业进行API服务治理、减少中间层胶水代码以及降低编码投入、提高 API 服务的稳定性和安全性。Java210
certd开源SSL证书管理工具;全自动证书申请、更新、续期;通配符证书,泛域名证书申请;证书自动化部署到阿里云、腾讯云、主机、群晖、宝塔;https证书,pfx证书,der证书,TLS证书,nginx证书自动续签自动部署JavaScript190
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python300