)
TypeSpec 语言基础Union 联合类型完全指南表达式、具名联合与 extends 约束【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespec本文是 TypeSpec 语言基础系列中关于 Union联合类型的完整技术指南。Union 定义了一个类型必须恰好是若干候选变体variant中的一种TypeSpec 同时提供「联合表达式」union expression与「具名联合」named union两种声明形式并通过实验性的extends约束为多态建模提供编译期保障。读完本文你将掌握|运算符的用法、union关键字的具名声明、extends基类约束的启用与校验规则以及union关键字在表达式位置的实验特性并了解编译器在 checker.ts 中的底层校验实现。联合类型概述在 TypeSpec 中Union 描述的是一个必须恰好等于多个可能变体之一的类型。与交集类型intersection表示同时满足不同联合类型表达的是多选一的关系这与大多数编程语言中的sum type / tagged union概念一致。TypeSpec 提供两种联合类型声明方式联合表达式Union expressions未命名直接用|运算符组合各变体具名联合Named unions用union关键字声明可为联合本身及每个选项命名。两种形式在语义上等价但具名联合能为发射器emitter提供更丰富的结构化信息这一点将在下文展开。Union 表达式用|组合变体未命名的联合类型即联合表达式通过|运算符把多个变体拼合而成通常配合alias使用alias Breed Beagle | GermanShepherd | GoldenRetriever;在上面的例子中Breed可以是Beagle、GermanShepherd或GoldenRetriever三者之一。|运算符可以连接模型、标量scalar、枚举、字符串/数字字面量等任意类型表达式是快速内联表达或关系的最直接手段。具名联合union 关键字与具名变体具名联合允许为联合赋予名称并为每个变体提供显式的命名引用。在概念上具名联合与 枚举enums 有些相似但区别在于枚举的成员是string或numeric值而联合的变体是 记录模型record models。具名联合使用union关键字声明其名称必须是一个合法的identifierunion Breed { beagle: Beagle, shepherd: GermanShepherd, retriever: GoldenRetriever, }这个例子与前面用alias定义的Breed完全等价差异在于发射器可以把Breed识别为一个具名实体named entity并识别出beagle、shepherd、retriever这些选项的名称从而生成更友好的代码例如生成带标签的联合、判别联合或带枚举名的映射这种格式还允许对每个选项单独应用 装饰器decorators。从编译器内部看每个变体会被建模为独立的UnionVariant对象携带name、type和所属union引用见 types.ts。变体在源码中出现的顺序会被保留在variants映射中作为联合选项的稳定顺序。用 extends 约束联合实验特性启用方式与声明语法具名联合可以通过extends关键字声明一个基类型base type。联合中的每一个变体都必须可赋值assignable给该基类型否则编译器会在违规的变体上报告诊断错误。:::caution Union 的extends子句是实验特性需要显式启用union-extends编译器特性。在tspconfig.yaml中启用features: - union-extends:::启用后即可书写如下代码model Dog { name: string; } model Beagle extends Dog { huntingSkill: string; } model GermanShepherd extends Dog { guardingSkill: string; } union Breed extends Dog { beagle: Beagle, shepherd: GermanShepherd, }extends 的两大用途该约束服务于两个明确目的防止一类常见错误杜绝把不相关的类型误加入联合。例如在Breed extends Dog下若误写union Breed { beagle: Beagle, cat: Cat }编译器会立即在cat: Cat上报告可赋值性诊断在类型图中记录公共基类型extends声明的基类型会被记录在联合的baseType字段中发射器可以据此在原生不支持联合的语言中用多态基类型polymorphic base type来表达这个联合。需要特别强调的是基类型不会成为联合的一个变体。上面的Breed仍然恰好只有两个变体beagle与shepherdDog本身并不属于联合的取值集合。extends 是约束不是继承声明extends是可赋值性约束而非继承关系的声明。变体只需要可赋值给基类型即可不要求显式 extends 该基类型model Dog { name: string; } model Beagle { name: string; huntingSkill: string; } // Ok: Beagle 可赋值给 Dog即使它没有显式 extends Dog union Breed extends Dog { beagle: Beagle, }基表达式的合法类型extends的基表达式必须解析为以下四类类型之一model模型scalar标量enum枚举union联合这包括能够解析为上述数据类型的联合表达式、交集表达式、数组表达式和模板表达式。匿名模型表达式anonymous model expressions不能直接使用也不能通过 alias 间接使用。union OperationStatus extends string { Running, Succeeded, Failed, }上面这个例子展示了extends string的用法基类型是内建标量string各变体为字符串字面量。编译器源码 checker.ts 中的isUnionBaseType函数正是按Model | Scalar | Enum | Union四种类型进行判定同时 checker.ts 会专门拦截基类型为ModelExpression匿名模型表达式的情况并报告 Unions cannot extend model expressions. 诊断。extends 不等于可扩展:::cautionextends并不表示该联合是可扩展的extensible。union Foo extends string { a, b }与union Foo { a, b }描述的是完全相同的取值集合发射器应当对二者一视同仁。若希望允许额外的值需要显式地为它们添加一个变体union OperationStatus extends string { Running, Succeeded, Failed, string, // 显式允许其他任意字符串 }:::与 discriminator 的交互extends与discriminator装饰器没有任何交互。discriminator(kind)用于在模型属性上声明判别属性详见 built-in-decorators.md 中discriminator一节而extends只负责可赋值性约束与类型图记录两者职责互不重叠。编译器实现对此有明确注释baseType只是约束的记录——它不暗示子类化关系、不意味着联合可扩展、也与discriminator无交互并且发射器不应强制要求该字段存在拥有相同变体但无extends子句的联合应被同样处理见 types.ts。底层校验机制源码级在编译器 checker.ts 中checkUnion函数按以下顺序处理具名联合特性门控若联合带extends子句且未启用union-extends特性报告union-extends-disabled诊断checker.ts构建类型创建Union类型对象并依据节点是否为UnionDeclarationExpression设置expression标志解析基类型调用checkUnionBaseType解析extends表达式包括循环基类型检测circular-base-type与匿名模型表达式拦截校验变体checkUnionVariants遍历各变体检测重名变体union-duplicate诊断见 messages.ts 中的union-duplicate并对每个变体调用checkUnionVariantAgainstBaseType执行可赋值性检查注册命名空间非表达式形式的具名联合会被注册到所属命名空间namespace.unions.set(...)而表达式形式的联合不会注册。特性本身在 features.ts 中注册描述为 Enables experimental unionextendsclauses in project code.。isCompilerFeatureEnabledfeatures.ts会从拥有该源文件的包project 或 library的tspconfig.yaml中解析特性列表这意味着库可以在自己的配置中为自身代码启用该特性而无需影响使用方项目。此外模板化的联合带模板参数的union FooT extends ...在校验时会跳过未实例化模板声明中的变体验证改为在每次实例化时校验相关场景在 union.test.ts 中有大量测试覆盖包括union a extends a { x: string }自引用循环检测等。表达式位置的关键字联合实验特性:::warning 声明表达式declaration expressions是 TypeSpec 的实验特性默认关闭。除非在tspconfig.yaml的features列表中加入declaration-expressions否则在表达式位置使用model、enum、union或scalar声明都会被判定为错误kind: project features: - declaration-expressions:::启用后union关键字可以出现在任何期望类型表达式的位置——例如作为 alias 的值、属性的类型、装饰器或模板参数、元组元素等。与|运算符构建的联合表达式不同关键字形式可以携带名称和具名变体model Pet { // 表达式位置上的匿名关键字联合 breed: union { Beagle, GermanShepherd, }; // 表达式位置上的具名关键字联合 size: union Size { small: S, medium: M, large: L, }; }该特性在 features.ts 中注册描述为允许在表达式位置使用具名或匿名的 model、scalar、enum 和 union 声明。不注册命名空间、不参与扁平化关于表达式位置的关键字联合有三个容易被忽略的语义细节不注册到命名空间表达式位置的关键字联合会被标记为表达式即使赋予了名称也不会注册到所在命名空间。名称仅保留在结果类型上用于显示目的——无法在其他地方引用它不参与扁平化与|运算符不同作为操作数使用的关键字联合不会被扁平化进周围的联合。例如union { a, b } | c产生的是嵌套的union { a, b }与c的联合内层的具名变体被保留下来。这一点与普通联合表达式会被合并进外层联合的行为有本质区别同样支持 extends 约束表达式位置的联合也可以用extends约束其变体model Pet { breed: union extends Dog { Beagle, GermanShepherd, }; }此外表达式位置的关键字联合可以内联应用装饰器和文档注释并且可以通过导航引用navigation reference如::type对其进行增补augment。由于关键字联合与具名联合共用同一套checkUnion校验逻辑上述extends约束、特性门控与变体验证规则在表达式位置同样生效。小结与推荐阅读本文完整覆盖了 TypeSpec Union 的三种形态联合表达式|运算符轻量、内联适合快速表达多选一具名联合union关键字为联合与各变体命名供发射器生成更结构化、更友好的输出并支持逐变体应用装饰器extends 约束实验特性编译期强制所有变体可赋值给基类型防止无关类型混入并在类型图中记录公共基类型以支持多态表达基类型不会成为变体、不意味着可扩展、与discriminator无交互表达式位置的关键字联合declaration-expressions实验特性把联合直接嵌入属性、参数等类型表达式位置携带名称但不注册命名空间、不参与外层扁平化。相关源码与测试可继续深入编译器校验逻辑checker.tsUnion 类型定义与baseType语义types.ts实验特性注册与启用解析features.ts诊断消息定义messages.tsextends 约束与循环检测测试union.test.ts声明表达式特性测试declaration-expressions.test.ts建议继续阅读语言基础系列的 模型models、枚举enums 与 类型关系type-relations以理解本文中可赋值性与模型声明背后的完整类型系统装饰器机制可参考 decorators.md。【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespec创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考