
深入理解 Ruff 类型检查器中的类型等价关系is_equivalent_to 全解析【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff导读is_equivalent_to是 Ruff 内置类型检查器ty中定义类型等价关系的核心判定函数它回答一个看似简单却极其关键的问题两个类型是否表示同一组值本文以 is_equivalent_to.md 这份测试规范文档为骨架系统讲解等价关系的数学定义全静态类型与渐进类型两种情形、它在字面量/枚举/协议/可调用对象/模块字面量等各类类型上的判定规则并结合 relation.rs 源码揭示其底层实现机制。读完本文你将掌握等价、子类型、materialization具体化三者之间的内在联系并能在自己的代码中用static_assert编写类型级测试来验证类型等价性。一、等价关系的定义从集合论到类型系统1.1 全静态类型双向子类型is_equivalent_to实现的是 PEP 484 / typing 规范 中所定义的等价关系。对于完全静态类型fully static types其定义非常直观两个类型A和B等价当且仅当A是B的子类型且B也是A的子类型——即这两个类型表示的是同一组值的集合。这一定义与 is_subtype_of.md 中描述的集合包含语义一脉相承子类型关系对应值集合的子集关系双向子类型自然对应集合相等。因此is_equivalent_to可以视为在子类型判定之上构建的更高层关系。1.2 渐进类型基于 materialization 的等价对于包含Any、Unknown等渐进类型gradual types的类型子类型判定本身不再基于集合包含因此等价判定需要借助materialization具体化概念两个渐进类型A和B等价当且仅当A的所有 materialization 也都是B的 materialization反之B的所有 materialization 也都是A的 materialization。什么是 materialization在 materialization.md 中定义得很清楚一个类型的 materialization 是将其中的Any/Unknown按位置替换后的结果——协变位置替换为object逆变位置替换为Never不变位置替换为未解析的类型变量。也就是说渐进类型代表的是一组可能的完全静态类型两个渐进类型等价意味着它们代表的这组可能类型完全重合。二、基础等价用例2.1 全静态类型的等价测试文档以ty_extensions._internal中的is_equivalent_to函数配合static_assert宏来验证判定结果。先看最基本的字面量类型from typing_extensions import Literal, LiteralString, Protocol, Never from ty_extensions import static_assert, AlwaysTruthy, AlwaysFalsy from ty_extensions._internal import Unknown, TypeOf, is_equivalent_to from enum import Enum static_assert(is_equivalent_to(Literal[1, 2], Literal[1, 2])) static_assert(is_equivalent_to(type[object], type)) static_assert(is_equivalent_to(type, type[object]))这里type与type[object]是等价的type元类的类型所代表的值集合与所有以object为实例的类型的集合是同一个集合因此双向子类型成立。不等价的情形同样清晰static_assert(not is_equivalent_to(Literal[1, 2], Literal[1, 0])) static_assert(not is_equivalent_to(Literal[1, 2], Literal[1, 2, 3]))Literal[1, 2]与Literal[1, 0]表示不同值的集合与Literal[1, 2, 3]则是子集关系而非相等关系自然不等价。2.2 枚举字面量与单成员枚举枚举成员作为字面量参与等价判定时判定是基于成员身份而非枚举类的其他属性class Answer(Enum): NO 0 YES 1 class Single(Enum): VALUE 1 static_assert(is_equivalent_to(Literal[Answer.YES], Literal[Answer.YES])) static_assert(is_equivalent_to(Literal[Answer.NO, Answer.YES], Answer)) static_assert(is_equivalent_to(Literal[Answer.YES, Answer.NO], Answer)) static_assert(not is_equivalent_to(Literal[Answer.YES], Literal[Answer.NO])) static_assert(not is_equivalent_to(Literal[Answer.YES], Answer)) static_assert(is_equivalent_to(Literal[Single.VALUE], Single)) static_assert(is_equivalent_to(Single, Literal[Single.VALUE]))注意Literal[Answer.NO, Answer.YES]与Answer等价因为枚举Answer恰好只有这两个成员而Literal[Answer.YES]只是Answer的一个真子集二者不等价。对于单成员枚举SingleLiteral[Single.VALUE]与Single完全等价。由枚举字面量还能验证等价关系与联合类型成员顺序无关static_assert(is_equivalent_to(tuple[Single] | int | str, str | int | tuple[Literal[Single.VALUE]]))2.3 协议、底部类型与特殊类型协议Protocol两个协议若其要求的成员类型等价则协议本身等价class Protocol1(Protocol): a: Single class Protocol2(Protocol): a: Literal[Single.VALUE] static_assert(is_equivalent_to(Protocol1, Protocol2))底部类型与特殊类型Never底部类型空集合、AlwaysTruthy、AlwaysFalsy、LiteralString、Literal[True]、Literal[False]与自身均等价static_assert(is_equivalent_to(Never, Never)) static_assert(is_equivalent_to(AlwaysTruthy, AlwaysTruthy)) static_assert(is_equivalent_to(AlwaysFalsy, AlwaysFalsy)) static_assert(is_equivalent_to(LiteralString, LiteralString)) static_assert(is_equivalent_to(Literal[True], Literal[True])) static_assert(is_equivalent_to(Literal[False], Literal[False])) static_assert(is_equivalent_to(type, type[object]))切片与字符串字面量类型TypeOf[0:1:2]与自身等价TypeOf[str]与自身等价。2.4 渐进类型的等价Any与Unknown都表示动态/未知类型因此在等价关系上它们是同一集合from typing import Any from typing_extensions import Literal, LiteralString, Never from ty_extensions import static_assert from ty_extensions._internal import Unknown, is_equivalent_to static_assert(is_equivalent_to(Any, Any)) static_assert(is_equivalent_to(Unknown, Unknown)) static_assert(is_equivalent_to(Any, Unknown)) static_assert(not is_equivalent_to(Any, None))注意Any与None不等价——即使Any可以表示None的所有 materialization反过来None的 materialization只有None本身却远少于Any的 materialization 集合。同理static_assert(not is_equivalent_to(type, type[Any])) static_assert(not is_equivalent_to(type[object], type[Any]))type[Any]的 materialization 集合覆盖type和type[object]的 materialization但反过来不成立因此不等价。三、有界渐进特化Bounded Gradual Specializations3.1 与展开别名的等价类型别名PEP 695type语句在使用渐进类型参数时带界的泛型以渐进类型别名特化后与其展开后的显式特化形式等价。文档通过[environment] python-version 3.13指定测试环境以启用相关语法from typing import Any from ty_extensions import static_assert from ty_extensions._internal import Unknown, is_equivalent_to type AnyTuple tuple[Any, ...] type UnknownTuple tuple[Unknown, ...] class BoundedCovariant[T: tuple[int, ...]]: def get(self) - T: raise NotImplementedError static_assert(is_equivalent_to(BoundedCovariant[AnyTuple], BoundedCovariant[tuple[Any, ...]])) static_assert(is_equivalent_to(BoundedCovariant[UnknownTuple], BoundedCovariant[AnyTuple]))对于协变的有界类型参数包含Any或Unknown的别名都适用此规则。对于不变的有界类型参数同样成立class BoundedInvariant[T: tuple[int, ...]]: value: T static_assert(is_equivalent_to(BoundedInvariant[AnyTuple], BoundedInvariant[tuple[Any, ...]]))3.2 泛型默认参数的等价泛型参数的默认值 Any、 Inner[Any]在特化时被展开后省略与显式写出默认值的两种特化形式等价class Inner[T: int Any]: def get(self) - T: raise NotImplementedError class Outer[T: int, U: Inner[Any] Inner[Any]]: def get(self) - U: raise NotImplementedError static_assert(is_equivalent_to(Outer[int, Inner[Any]], Outer[int, Inner])) static_assert(is_equivalent_to(Outer[int, Inner[Any]], Outer[int]))Outer[int, Inner]与Outer[int, Inner[Any]]等价因为Inner默认特化为AnyOuter[int]与显式写出Inner[Any]的版本等价因为Outer的第二个参数默认值就是Inner[Any]。3.3 有界渐进特化 ≠ 上界这是最容易混淆的一点以渐进类型参数特化的泛型并不等价于以上界特化的同一泛型。仍以T: tuple[int, ...]为例class BoundedCovariant[T: tuple[int, ...]]: def get(self) - T: raise NotImplementedError static_assert(not is_equivalent_to(BoundedCovariant[tuple[Any, ...]], BoundedCovariant[tuple[int, ...]]))tuple[Any, ...]的 materialization 集合覆盖tuple[int, ...]但反向不成立因此BoundedCovariant[tuple[Any, ...]]与BoundedCovariant[tuple[int, ...]]不等价。不变类型参数同理class BoundedInvariant[T: tuple[int, ...]]: value: T static_assert(not is_equivalent_to(BoundedInvariant[tuple[Any, ...]], BoundedInvariant[tuple[int, ...]]))四、联合与交叉类型4.1 联合类型的等价与顺序无关性联合类型的等价与成员顺序无关这与集合的交换律一致from typing import Any, Literal, TypeAlias from ty_extensions import static_assert from ty_extensions._internal import Unknown, is_equivalent_to from enum import Enum static_assert(is_equivalent_to(str | int, str | int)) static_assert(is_equivalent_to(str | int, int | str)) static_assert(is_equivalent_to(str | None, None | str))文档用 15 条断言穷举验证了P | Q | R的全部排列组合都彼此等价# 1到# 15充分说明联合类型的等价判定是完全顺序无关的。Any/Unknown在联合中具有吸收性static_assert(is_equivalent_to(str | int | Any, str | int | Unknown)) static_assert(is_equivalent_to(Unknown, Unknown | Any)) UnknownAndAny: TypeAlias Unknown Any static_assert(is_equivalent_to(Unknown, UnknownAndAny))Unknown | Any吸收为UnknownUnknown Any也吸收为UnknownAny与Unknown在同一联合/交叉中等价时互相吸收。4.2 交叉类型的等价与顺序无关性交叉类型同样顺序无关且~取反运算参与时依然保持交换性static_assert(is_equivalent_to(str int ~bytes ~None, int str ~None ~bytes)) static_assert(is_equivalent_to(P Q, Q P)) static_assert(is_equivalent_to(Q ~P, ~P Q)) static_assert(is_equivalent_to(Q R ~P, ~P R Q)) static_assert(is_equivalent_to((Q | R) ~(P | S), ~(S | P) (R | Q)))联合与交叉混用时同样成立static_assert(is_equivalent_to((str | int) ~type[Any], (int | str) ~type[Unknown])) static_assert(is_equivalent_to(P | Q | Single, Literal[Single.VALUE] | Q | P))4.3 不等价与吸收规则不等价的情形包括成员集合不同以及缺失类型参数这类诊断性场景static_assert(not is_equivalent_to(str | int, int | str | bytes)) static_assert(not is_equivalent_to(str | int | bytes, int | str | dict)) # error: [missing-type-argument]Any在联合中的吸收还体现在这些组合上static_assert(is_equivalent_to(Any, Any | Any str)) static_assert(is_equivalent_to(Any, str Any | Any)) static_assert(is_equivalent_to(Any, Any | Any ~None)) static_assert(is_equivalent_to(Any, ~None Any | Any))Any | (Any str)中Any str是Any的成员整个联合吸收为Any。Unknown参与时同理static_assert(is_equivalent_to(Any, Unknown | Unknown str)) static_assert(is_equivalent_to(Any, str Unknown | Unknown)) static_assert(is_equivalent_to(Any, Unknown | Unknown ~None)) static_assert(is_equivalent_to(Any, ~None Unknown | Unknown))五、元组Tuples的等价元组等价要求逐元素等价且长度相同元素位置敏感from ty_extensions import static_assert from ty_extensions._internal import Unknown, is_equivalent_to from typing import Any static_assert(is_equivalent_to(tuple[str, Any], tuple[str, Unknown])) static_assert(not is_equivalent_to(tuple[str, int], tuple[str, int, bytes])) static_assert(not is_equivalent_to(tuple[str, int], tuple[int, str]))tuple[str, Any]与tuple[str, Unknown]等价元素渐进等价长度不同或元素顺序不同都不等价。5.1 元组内联合/交叉的顺序无关性向下传播元组元素内部联合/交叉的不同排列依然等价from ty_extensions import static_assert from ty_extensions._internal import TypeOf, is_equivalent_to from typing import Literal class P: ... class Q: ... class R: ... class S: ... static_assert(is_equivalent_to(tuple[P | Q], tuple[Q | P])) static_assert(is_equivalent_to(tuple[P | None], tuple[None | P])) static_assert(is_equivalent_to(tuple[P Q | R ~S], tuple[~S R | Q P]))5.2 多层嵌套元组等价判定具有递归性可以穿透任意层嵌套class P: ... class Q: ... static_assert( is_equivalent_to( tuple[tuple[tuple[P | Q]]] | P, tuple[tuple[tuple[Q | P]]] | P, ) ) static_assert( is_equivalent_to( tuple[tuple[tuple[tuple[tuple[P Q]]]]], tuple[tuple[tuple[tuple[tuple[Q P]]]]], ) )5.3 交叉中的嵌套元组class R: ... static_assert(is_equivalent_to(tuple[P | Q] R, tuple[Q | P] R))5.4 以联合参数化的泛型实例在python-version 3.12环境下泛型实例以联合类型参数化时也保持等价from ty_extensions import static_assert from ty_extensions._internal import is_equivalent_to class A: ... class B: ... class Foo[T]: ... static_assert(is_equivalent_to(A | Foo[A | B], Foo[B | A] | A))六、可调用类型Callable的等价6.1 等价的判定要素对于可调用类型等价判定并非要求签名逐字相同。文档归纳了以下规则1. 默认值不需要相同但是否有默认值必须一致from ty_extensions import static_assert from ty_extensions._internal import RegularCallableTypeOf, is_equivalent_to from typing import Callable def f1(a: int 1) - None: ... def f2(a: int 2) - None: ... static_assert(is_equivalent_to(RegularCallableTypeOf[f1], RegularCallableTypeOf[f2])) static_assert( is_equivalent_to( RegularCallableTypeOf[f1] | bool | RegularCallableTypeOf[f2], RegularCallableTypeOf[f2] | bool | RegularCallableTypeOf[f1] ) )2. 仅限位置参数、*args、**kwargs的参数名可以不同def f3(a1: int, /, *args1: int, **kwargs2: int) - None: ... def f4(a2: int, /, *args2: int, **kwargs1: int) - None: ... static_assert(is_equivalent_to(RegularCallableTypeOf[f3], RegularCallableTypeOf[f4]))3. 综合场景以下两个签名完全不同的函数参数名不同、默认值不同、变参名不同依然等价def f5(a1: int, /, b: float, c: bool False, *args1: int, d: int 1, e: str, **kwargs1: float) - None: ... def f6(a2: int, /, b: float, c: bool True, *args2: int, d: int 2, e: str, **kwargs2: float) - None: ... static_assert(is_equivalent_to(RegularCallableTypeOf[f5], RegularCallableTypeOf[f6]))6.2 不等价的多种情形文档逐一枚举了可调用类型不等价的情况参数数量不同def f1(a: int) - None: ... def f2(a: int, b: int) - None: ... static_assert(not is_equivalent_to(RegularCallableTypeOf[f1], RegularCallableTypeOf[f2]))返回类型不等价无注解函数f3的返回类型是Unknown与f4 - None不等价但f3与自身等价def f3(): ... def f4() - None: ... static_assert(not is_equivalent_to(Callable[[], int], Callable[[], None])) static_assert(is_equivalent_to(RegularCallableTypeOf[f3], RegularCallableTypeOf[f3])) static_assert(not is_equivalent_to(RegularCallableTypeOf[f3], RegularCallableTypeOf[f4])) static_assert(not is_equivalent_to(RegularCallableTypeOf[f4], RegularCallableTypeOf[f3]))关键字参数的参数名不同def f5(a: int) - None: ... def f6(b: int) - None: ... static_assert(not is_equivalent_to(RegularCallableTypeOf[f5], RegularCallableTypeOf[f6]))只有一方有参数名裸Callable[[int], None]无参数名与具名签名不等价static_assert(not is_equivalent_to(RegularCallableTypeOf[f5], Callable[[int], None]))参数种类kind不同/仅限位置参数与普通参数不同def f7(a: int, /) - None: ... def f8(a: int) - None: ... static_assert(not is_equivalent_to(RegularCallableTypeOf[f7], RegularCallableTypeOf[f8]))参数注解类型不等价或缺失无注解参数视为Unknown与int、str均不等价但f11与自身等价def f9(a: int) - None: ... def f10(a: str) - None: ... def f11(a) - None: ... static_assert(not is_equivalent_to(RegularCallableTypeOf[f9], RegularCallableTypeOf[f10])) static_assert(not is_equivalent_to(RegularCallableTypeOf[f10], RegularCallableTypeOf[f11])) static_assert(is_equivalent_to(RegularCallableTypeOf[f11], RegularCallableTypeOf[f11]))默认值只在一边出现def f12(a: int) - None: ... def f13(a: int 2) - None: ... static_assert(not is_equivalent_to(RegularCallableTypeOf[f12], RegularCallableTypeOf[f13])) static_assert(not is_equivalent_to(RegularCallableTypeOf[f13], RegularCallableTypeOf[f12]))6.3 联合中的可调用类型包含不同Callable的联合只要Callable本身等价联合整体就等价且顺序无关from ty_extensions import static_assert from ty_extensions._internal import Unknown, RegularCallableTypeOf, is_equivalent_to def f(x): ... def g(x: Unknown): ... static_assert(is_equivalent_to(RegularCallableTypeOf[f] | int | str, str | int | RegularCallableTypeOf[g]))Callable内部参数的联合不同排列也不影响等价from typing import Callable from ty_extensions import static_assert from ty_extensions._internal import is_equivalent_to static_assert(is_equivalent_to(int | Callable[[int | str], None], Callable[[str | int], None] | int))6.4 重载Overload与等价单侧重载一个仅有一个重载签名的函数与参数类型取父类型的普通函数等价。例如overloaded.pyi中overloaded(a: Child)、overloaded(a: Parent)、overloaded(a: Grandparent)三个重载等价于单个接受Grandparent的函数def grandparent(a: Grandparent) - None: ... static_assert(is_equivalent_to(RegularCallableTypeOf[grandparent], RegularCallableTypeOf[overloaded])) static_assert(is_equivalent_to(RegularCallableTypeOf[overloaded], RegularCallableTypeOf[grandparent]))双方重载pg(a: Parent)、pg(a: Grandparent)与cpg(a: Child)、cpg(a: Parent)、cpg(a: Grandparent)的重载集等价static_assert(is_equivalent_to(RegularCallableTypeOf[pg], RegularCallableTypeOf[cpg])) static_assert(is_equivalent_to(RegularCallableTypeOf[cpg], RegularCallableTypeOf[pg]))6.5 函数字面量类型与绑定方法类型函数字面量类型与绑定方法类型总是与自身等价def f(): ... static_assert(is_equivalent_to(TypeOf[f], TypeOf[f])) class A: def method(self) - int: return 42 static_assert(is_equivalent_to(TypeOf[A.method], TypeOf[A.method])) type X TypeOf[A.method] static_assert(is_equivalent_to(X, X))6.6 非完全静态渐进可调用类型Callable[..., int]这类省略号参数签名以及带Any/Unknown参数的签名遵循渐进等价规则from ty_extensions import static_assert from ty_extensions._internal import Unknown, CallableTypeOf, RegularCallableTypeOf, TypeOf, is_equivalent_to from typing import Any, Callable static_assert(is_equivalent_to(Callable[..., int], Callable[..., int])) static_assert(is_equivalent_to(Callable[..., Any], Callable[..., Unknown])) static_assert(is_equivalent_to(Callable[[int, Any], None], Callable[[int, Unknown], None])) static_assert(not is_equivalent_to(Callable[[int, Any], None], Callable[[Any, int], None])) static_assert(not is_equivalent_to(Callable[[int, str], None], Callable[[int, str, bytes], None])) static_assert(not is_equivalent_to(Callable[..., None], Callable[[], None]))无注解返回与Any返回的渐进等价def f1(): return def f1_equivalent() - Any: return static_assert(is_equivalent_to(RegularCallableTypeOf[f1], RegularCallableTypeOf[f1_equivalent]))无注解参数与Any参数的渐进等价def f2(a, b, /) - None: return def f2_equivalent(a: Any, b: Any, /) - None: return static_assert(is_equivalent_to(RegularCallableTypeOf[f2], RegularCallableTypeOf[f2_equivalent]))同时具有*args与**kwargs无注解或Any注解时签名会被揭示为(...) - Unknown/(...) - Any但注意其类型形式仍是函数字面量类型而非Callable[..., Any]def variadic_without_annotation(*args, **kwargs): return def variadic_with_annotation(*args: Any, **kwargs: Any) - Any: return def _( signature_variadic_without_annotation: CallableTypeOf[variadic_without_annotation], signature_variadic_with_annotation: CallableTypeOf[variadic_with_annotation], ) - None: # revealed: (...) - Unknown reveal_type(signature_variadic_without_annotation) # revealed: (...) - Any reveal_type(signature_variadic_with_annotation) static_assert(not is_equivalent_to(CallableTypeOf[variadic_without_annotation], Callable[..., Any])) static_assert(not is_equivalent_to(CallableTypeOf[variadic_with_annotation], Callable[..., Any]))只具有*args或只具有**kwargs时也不等价于Callable[..., Any]def variadic_args(*args): return def variadic_kwargs(**kwargs): return def _( signature_variadic_args: RegularCallableTypeOf[variadic_args], signature_variadic_kwargs: RegularCallableTypeOf[variadic_kwargs], ) - None: # revealed: (*args) - Unknown reveal_type(signature_variadic_args) # revealed: (**kwargs) - Unknown reveal_type(signature_variadic_kwargs) static_assert(not is_equivalent_to(RegularCallableTypeOf[variadic_args], Callable[..., Any])) static_assert(not is_equivalent_to(RegularCallableTypeOf[variadic_kwargs], Callable[..., Any]))渐进可调用类型的参数名、默认值、参数种类同样参与判定def f1(a): ... def f2(b): ... static_assert(not is_equivalent_to(RegularCallableTypeOf[f1], RegularCallableTypeOf[f2])) def f3(a1): ... def f4(a2): ... def f5(a): ... static_assert(is_equivalent_to(RegularCallableTypeOf[f3], RegularCallableTypeOf[f4])) static_assert(not is_equivalent_to(RegularCallableTypeOf[f3], RegularCallableTypeOf[f5])) def f6(a, /): ... static_assert(not is_equivalent_to(RegularCallableTypeOf[f1], RegularCallableTypeOf[f6]))七、模块字面量类型Module-literal Types7.1 单文件模块的副本等价同一单文件模块的两个副本分别在不同模块中导入被视为等价类型module.pyimport typingmain.pyimport typing from module import typing as other_typing from ty_extensions import static_assert from ty_extensions._internal import TypeOf, is_equivalent_to static_assert(is_equivalent_to(TypeOf[typing], TypeOf[other_typing])) static_assert(is_equivalent_to(TypeOf[typing] | int | str, str | int | TypeOf[other_typing]))7.2 包模块的副本不等价若底层模块是包且副本来自不同导入模块则当前实现不认为两者等价。原因是子模块是否作为属性可见取决于原导入模块是否显式导入了子模块module2.pyimport imported import imported.abcimported/__init__.pyi空文件imported/abc.pyi空文件main2.pyimport imported from module2 import imported as other_imported from ty_extensions import static_assert from ty_extensions._internal import TypeOf, is_equivalent_to # error: [possibly-missing-submodule] reveal_type(imported.abc) # revealed: Unknown reveal_type(other_imported.abc) # revealed: module imported.abc static_assert(not is_equivalent_to(TypeOf[imported], TypeOf[other_imported]))imported副本上没有abc子模块属性产生possibly-missing-submodule诊断而other_imported副本上有二者成员不同故不等价。八、Bound-super 类型两个 bound-super 类型super(Foo, a)的表达类型在枢轴类pivot class与实例都等价时等价。即使实例的类型是不同排列的联合只要联合等价bound-super 也等价class Foo[T]: x: T def bar(a: Foo[int | str], b: Foo[str | int]): static_assert(is_equivalent_to(TypeOf[super(Foo, a)], TypeOf[super(Foo, b)]))九、源码级实现原理9.1 入口与核心实现is_equivalent_to的运行时实现在 relation.rs 中。入口方法is_equivalent_to将判定委托给when_equivalent_to返回约束集合并通过约束总是被满足来判断等价成立pub(crate) fn is_equivalent_to( self, db: db dyn Db, env: ProgramEnvironmentdb, other: Typedb, ) - bool { self.when_equivalent_to(db, env, other, ConstraintSetBuilder::new()) .is_always_satisfied(db, env) }从源码结构看等价判定的核心链路是when_equivalent_to→can_be_constraint_set_equivalent_to_impl其中when_equivalent_to会先创建一个ApplyTypeMappingVisitor即 materialization 访问器然后通过HasRelationToVisitor递归遍历类型对而can_be_constraint_set_equivalent_to_impl则是一个 salsa 跟踪tracked函数带有cycle_initial|_, _, _| true的递归环兜底——这意味着当两个类型完全相同时直接短路返回trueif self other { return true; }递归环则保守地视为等价成立从而保证判定过程在循环类型如递归泛型上仍能终止。这与文档中materialization 集合包含的数学定义一一对应渐进类型通过 materialization 访问器转换为完全静态类型后比较。9.2 与子类型、materialization 的联动子类型关系is_subtype_of定义在 is_subtype_of.md 与同一relation.rs中等价判定正是建立在双向子类型之上materialization 的替换规则协变→object、逆变→Never、不变→未解析类型变量定义在 materialization.md同一目录下的 is_assignable_to.md、is_disjoint_from.md、implies_subtype_of.md 等文档共同构成 ty 类型关系的完整测试矩阵。9.3 测试基础设施文档中的测试代码属于 ty 的类型检查器 mdtest 体系ty_extensions模块提供static_assert、AlwaysTruthy、AlwaysFalsy等测试专用类型ty_extensions._internal提供is_equivalent_to、Unknown、TypeOf、RegularCallableTypeOf、CallableTypeOf等内部 API。这些测试分别以.py、.pyi、.toml[environment]指定python-version多文件形式组织覆盖了等价关系在字面量、枚举、协议、联合/交叉、元组、可调用、重载、模块字面量、bound-super 等全部类型形态上的行为是理解 ty 类型系统语义的一手规范文档。结语is_equivalent_to是 ty 类型系统中仅次于子类型关系的核心判定它用双向子类型和materialization 集合包含两种方式把两个类型表示同一组值这一集合论直觉精确地翻译成可判定的算法。通过本文梳理的全静态类型、渐进类型、有界特化、联合/交叉、元组、可调用、重载、模块字面量与 bound-super 九大场景配合 relation.rs 的源码实现开发者既能理解类型等价在泛型、重载消歧、可达性分析等场景中的作用也能借助static_assert在类型层面为自定义类型系统行为编写可验证的断言测试。【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考