
Disjointness of two types implies that neither is a subtype of the other【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruffThis is a regression test for https://github.com/astral-sh/ty/issues/2236.[environment] python-version 3.11from types import FunctionType from ty_extensions import Not, AlwaysTruthy, static_assert from ty_extensions._internal import is_subtype_of, is_disjoint_from class Meta(type): ... class F(metaclassMeta): ... static_assert(not is_subtype_of(tuple[FunctionType, type[F]], Not[tuple[*tuple[AlwaysTruthy, ...], Meta]])) static_assert(not is_subtype_of(Not[tuple[*tuple[AlwaysTruthy, ...], Meta]], tuple[FunctionType, type[F]])) static_assert(is_disjoint_from(tuple[FunctionType, type[F]], Not[tuple[*tuple[AlwaysTruthy, ...], Meta]]))这份测试虽短却同时覆盖了 ty 类型系统中的多个关键特性 - [environment] 中的 python-version 3.11变长元组语法 tuple[*tuple[...]] 依赖 PEP 6463.11 引入测试必须在 3.11 环境下运行 - class Meta(type) 与 class F(metaclassMeta)构造一个元类及其使用者用于考验类型检查器对元类关系的推理 - 三个 static_assert分别验证正向非子类型反向非子类型不相交三个结论且三者必须同时成立。 ## 二、三个断言逐一拆解 ### 2.1 测试中的两个复杂类型 先定义两个被比较的类型 python S tuple[FunctionType, type[F]] # 第一个元组类型 T Not[tuple[*tuple[AlwaysTruthy, ...], Meta]] # 第二个是否定类型 - S tuple[FunctionType, type[F]]一个二元组第一元素是任意函数对象FunctionType第二元素是 type[F]——即类对象 F 及其子类对象的类型 - T Not[tuple[*tuple[AlwaysTruthy, ...], Meta]]Not[...] 表示补集见下文 4.3 节被取反的内部类型是 tuple[*tuple[AlwaysTruthy, ...], Meta]——一个以零个或多个 AlwaysTruthy 元素开头、并以一个 Meta 元素收尾的变长元组。 ### 2.2 三条断言的含义 python static_assert(not is_subtype_of(tuple[FunctionType, type[F]], Not[tuple[*tuple[AlwaysTruthy, ...], Meta]])) static_assert(not is_subtype_of(Not[tuple[*tuple[AlwaysTruthy, ...], Meta]], tuple[FunctionType, type[F]])) static_assert(is_disjoint_from(tuple[FunctionType, type[F]], Not[tuple[*tuple[AlwaysTruthy, ...], Meta]])) | 断言 | 表达 | 验证的性质 | | --- | --- | --- | | 第 1 条 | S 不是 T 的子类型 | 不相交 ⇒ S ⊄ T | | 第 2 条 | T 不是 S 的子类型 | 不相交 ⇒ T ⊄ S | | 第 3 条 | S 与 T 不相交 | 两个类型确实无公共居民 | static_assert 是 [crates/ty_python_semantic/resources/mdtest/ty_extensions.md](https://link.gitcode.com/i/bb395401c0b0718becd1ea659d489e6b) 中描述的测试原语它接收任意表达式若表达式在静态层面已知为真则通过否则产生 static-assert-error 诊断。因此前两条断言写成 not is_subtype_of(...)相当于要求子类型判定必须返回否第三条要求不相交判定必须返回是。三者合起来即回归测试标题所述性质**disjointness 蕴含两个方向上的非子类型关系**。 ## 三、核心类型学原理为什么不相交必然互不为子类型 ### 3.1 不相交性的定义 在 ty 的类型系统中两个类型 S 与 T 不相交当且仅当它们的交集为空等价于 Never Two types S and T are disjoint if they have no overlap; that is, their intersection S T is empty (equivalent to Never). 这段定义直接出自 [crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md](https://link.gitcode.com/i/9bf91d1977e570d0ae664a4874ac04f6) 的开篇也是本文回归测试所依赖的公理基础。 ### 3.2 不相交 ⇒ 互不为子类型 该结论可由子类型的定义直接推出 - 若 S ⊆ TS 是 T 的子类型则 S 的所有居民都是 T 的居民S T S 非空 - 若 T ⊆ S同理可得 S T T 非空 - 因此一旦 S T ∅不相交S ⊆ T 与 T ⊆ S 必然都不成立。 回归测试正是针对这一逻辑关系编写只要第 3 条断言不相交成立前两条断言双向非子类型就必须同时成立若类型检查器在子类型或不相交判定上出现偏差测试即失败。issue #2236 即是历史上暴露出的此类判定不一致问题。 ### 3.3 逆命题不成立 需要强调的是该性质是**单向蕴含**不相交一定互不为子类型但互不为子类型并不代表不相交。例如两个独立的普通类 A 与 B无继承关系互不为子类型却可能存在同时继承二者的子类 C因此 A 与 B 并不 disjoint。这一点在 [crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md](https://link.gitcode.com/i/9bf91d1977e570d0ae664a4874ac04f6) 的 Class hierarchies 一节有大量测试佐证 python class A: ... class B1(A): ... class B2(A): ... # B1 和 B2 都是 A 的子类故不与 A disjoint static_assert(not is_disjoint_from(A, B1)) static_assert(not is_disjoint_from(A, B2)) # B1 与 B2 也不 disjoint因为可能存在共同的子类 class C(B1, B2): ... static_assert(is_subtype_of(C, B1 B2)) 只有当类被 final 修饰不可再被继承或元类互不相容时类层次才会产生 disjoint 关系。 ## 四、测试中四个关键类型构造的原理 ### 4.1 FunctionType 与 AlwaysTruthy函数对象恒为真 AlwaysTruthy 与 AlwaysFalsy 是 ty 中描述真值性恒真 / 恒假的特殊类型见 [crates/ty_python_semantic/resources/mdtest/ty_extensions.md](https://link.gitcode.com/i/bb395401c0b0718becd1ea659d489e6b) AlwaysTruthy and AlwaysFalsy represent the sets of all possible objects whose truthiness is always truthy or falsy, respectively. Python 中的函数对象永远为真因此 FunctionType 是 AlwaysTruthy 的子类型。这是回归测试中 S 的第一个元素能够落入被取反元组内部类型的原因之一。 ### 4.2 type[F] 与元类 Meta类对象是其元类的实例 元类是类的类。class F(metaclassMeta) 声明 F 的元类是 Meta意味着类对象 F 本身是 Meta 的一个实例。由于元类会被子类继承type[F]F 及其所有子类的类对象类型中的每个居民也都是 Meta 的实例即 python type[F] 是 Meta 的子类型 这正是回归测试中 S 的第二个元素能够与内部元组类型的收尾元素 Meta 匹配的关键。ty 需要有能力穿透元类 → 实例这层关系来完成子类型判定本测试即是对该能力的回归保护。仓库中 [crates/ty_python_semantic/resources/mdtest/metaclass.md](https://link.gitcode.com/i/bf05ee84bd27a97c9c328ec9b81752b6) 与 [crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md](https://link.gitcode.com/i/9bf91d1977e570d0ae664a4874ac04f6) 的 Instance types versus type[T] types 一节还提供了更多元类参与 disjoint 判定的用例。 ### 4.3 Not[...]否定类型补集 Not[T] 是 ty_extensions 提供的特殊形式表示 T 的补集。之所以需要它是因为 Python 语言本身无法直接表达交集否定等类型层面的运算。仓库中的说明如下 The ty_extensions module provides the Intersection and Not type constructors (special forms) which allow us to construct these types directly. 在该测试中 python T Not[tuple[*tuple[AlwaysTruthy, ...], Meta]] T 就是所有**不**属于 tuple[*tuple[AlwaysTruthy, ...], Meta] 的类型。 ### 4.4 变长元组 tuple[*tuple[AlwaysTruthy, ...], Meta] tuple[*tuple[AlwaysTruthy, ...], Meta] 是 PEP 646 变长元组零个或多个 AlwaysTruthy 元素紧跟着一个 Meta 元素。即它包含 tuple[Meta]、tuple[AlwaysTruthy, Meta]、tuple[AlwaysTruthy, AlwaysTruthy, Meta]……等所有形状。 综合 4.1 与 4.2S tuple[FunctionType, type[F]] 恰好是 (AlwaysTruthy, Meta) 形状因而 python tuple[FunctionType, type[F]] ⊆ tuple[*tuple[AlwaysTruthy, ...], Meta] 即 S 完全落在被取反的内部类型之中。由于 Not[...] 是内部类型的补集S 与 T Not[...] 的交集必然为空——**这就是两条类型 disjoint 的根本原因**也正是第 3 条断言成立的理论依据。 ## 五、底层实现DisjointnessChecker 与 is_disjoint_from ### 5.1 公开 API 层 is_disjoint_from 与 is_subtype_of 定义在 [crates/ty_vendored/ty_extensions/_internal.pyi](https://link.gitcode.com/i/d05e44a6e1cacb6bcb7ec2c3d788aa7f) 中是面向类型系统测试的内部原语 python def is_subtype_of(ty: TypeForm[object], of: TypeForm[object]) - ConstraintSet: Returns a constraint set that is satisfied when ty is a subtype of of. def is_disjoint_from( type_a: TypeForm[object], type_b: TypeForm[object] ) - ConstraintSet: Returns a constraint set that is satisfied when type_a and type_b are disjoint types. Two types are disjoint if they have no inhabitants in common. 二者的返回类型都是 ConstraintSet约束集由 static_assert 求值后产生 Literal[True] / Literal[False]。注释中的 Two types are disjoint if they have no inhabitants in common 与 3.1 节的文档定义完全一致。 ### 5.2 核心判定逻辑 在 [crates/ty_python_semantic/src/types/relation.rs](https://link.gitcode.com/i/42e1608cdf8f64f57a37dde2154c8f59) 中Type 实现了 is_disjoint_from约第 870 行其语义注释揭示了实现思路 Return true if self other should simplify to Never: if the intersection of the two types could never be inhabited by any possible runtime value. Our implementation of disjointness for non-fully-static types only returns true if the *top materialization* of self has no overlap with the *top materialization* of other. For example, list[int] is disjoint from list[str]: the two types have no overlap. But list[Any] is not disjoint from list[str]: there exists a fully static materialization of list[Any] (list[str]) that is a subtype of list[str]. 这里的两个要点 1. **交集简化为 Never**is_disjoint_from 的判定目标就是self other 是否为空与文档定义互为表里 2. **materialization物化策略**对含 Any 等渐进类型gradual type的类型判定只在其顶层物化无交集时才返回真。list[Any] 与 list[str] 不 disjoint因为 list[Any] 存在一个完全静态的物化 list[str] 与后者重叠。 实现上is_disjoint_from 会调用 when_disjoint_from构造一个 DisjointnessChecker 并注入四类访问器visitor - HasRelationToVisitor关系遍历负责子类型等关系 - IsDisjointVisitor不相交性遍历负责具体类型的 disjoint 规则 - SignatureRelationVisitor签名关系遍历 - ApplyTypeMappingVisitor类型物化/映射。 随后调用 checker.check_type_pair(db, self, other) 完成成对检查最后通过 is_always_satisfied 判断约束集是否恒成立。这条调用链正是第 2.2 节三个断言在执行时真正触达的代码路径。 ## 六、元组不相交性规则的完整图景 回归测试中的 S 是一个元组类型而 ty 对元组何时 disjoint有完整的规则体系集中记录在 [crates/ty_python_semantic/resources/mdtest/type_compendium/tuple.md](https://link.gitcode.com/i/535c3d1247920ee767aa3674203941a7) 的 Disjointness 一节 **规则 1最小长度不兼容的元组必 disjoint** Two tuples with incompatible minimum lengths are always disjoint, regardless of their element types. (The lengths are incompatible if the minimum length of one tuple is larger than the maximum length of the other.) python static_assert(is_disjoint_from(tuple[()], tuple[int])) static_assert(not is_disjoint_from(tuple[()], tuple[int, ...])) static_assert(not is_disjoint_from(tuple[str, ...], tuple[int, ...])) **规则 2对应位置元素 disjoint 则元组 disjoint** A tuple that is required to contain elements P1, P2 is disjoint from a tuple that is required to contain elements Q1, Q2 if either P1 is disjoint from Q1 or if P2 is disjoint from Q2. python final class F1: ... final class F2: ... static_assert(is_disjoint_from(tuple[F1, F2], tuple[F2, F1])) static_assert(not is_disjoint_from(tuple[N1, N2], tuple[N2, N1])) **规则 3变长部分永不导致 disjoint** The variable-length portion of a tuple can never cause the tuples to be disjoint, since all variable-length tuple types contain the empty tuple. python static_assert(not is_disjoint_from(tuple[F1, ...], tuple[F2, ...])) **规则 4元组类型不与任意实例类型 disjoint** 由于元组可被子类化ty 刻意放宽了这一判定同时为了自洽禁止两个不同特化的异构元组出现在同一条 MRO 中 python class C: ... static_assert(not is_disjoint_from(tuple[int, str], C)) class I1(tuple[F1, F2]): ... class I2(tuple[F2, F1]): ... class CommonSubtypeOfTuples(I1, I2): ... # error: [invalid-generic-class] 而 [crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md](https://link.gitcode.com/i/9bf91d1977e570d0ae664a4874ac04f6) 的 Tuple types 一节进一步补充了元组与字面量、变长元组之间的 disjoint 用例 python static_assert(is_disjoint_from(tuple[()], TypeOf[object])) static_assert(is_disjoint_from(tuple[None], None)) static_assert(is_disjoint_from(tuple[Literal[1]], tuple[Literal[2]])) static_assert(is_disjoint_from(tuple[Literal[1], Literal[2]], tuple[Literal[1], Literal[3]])) static_assert(not is_disjoint_from(tuple[Literal[1], Literal[2]], tuple[int, ...])) 可以看出本文的回归测试是这条规则链上一个高难度的组合用例它同时混合了元组、否定类型、变长元组、真值性类型与元类专门用于防止某个子规则改动时引入回归。 ## 七、否定类型与交集视角下的 disjoint 从实现角度S 与 Not[U] 的 disjoint 判定等价于检查 S Not[U] ∅。在 [crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md](https://link.gitcode.com/i/9bf91d1977e570d0ae664a4874ac04f6) 的 Intersections 一节可以找到同款推理模式的正向与负向用例 python # 一侧是正元素、另一侧是该元素的否定时二者 disjoint static_assert(is_disjoint_from(int, ~int)) static_assert(is_disjoint_from(X ~Literal[1], Literal[1])) # 但父类与子类的否定并不总是 disjoint class Parent: ... class Child(Parent): ... static_assert(not is_disjoint_from(Parent, ~Child)) static_assert(is_disjoint_from(~Parent, Child))【免费下载链接】ruffAn extremely fast Python linter and code formatter, written in Rust.项目地址: https://gitcode.com/GitHub_Trending/ru/ruff创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考