TypeSpec 自定义标量(Scalar)完全指南:定义、扩展、构造函数与表达式位置 TypeSpec 自定义标量Scalar完全指南定义、扩展、构造函数与表达式位置【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespec本篇指南围绕 TypeSpec 语言基础中的标量Scalar类型展开系统讲解如何用scalar关键字声明自定义标量、通过extends扩展现有类型、为标量声明带模板参数的变体以及利用构造函数init在编译期固化特定值如fromISO、now()等日期时间构造器。读完本文你将能够为你的 TypeSpec 规范定义语义清晰的领域类型理解标量构造函数与 emitter 之间的协作机制并掌握在表达式位置使用标量的实验性特性。什么是 Scalar标量Scalar是 TypeSpec 中最简单的类型形式——它们不包含任何字段。内建标量包括string、int32、boolean等它们在 内建类型定义 与 标准库类型定义 中声明。在 TypeSpec 中标量用来表达某个基本值例如整数、字符串、布尔值以及可以进一步细化的领域概念如 URL、密码、IPv4 地址。标量与model的关键区别在于模型由命名字段构成而标量本身没有字段结构。声明一个标量使用scalar关键字即可声明名称必须是合法的 标识符identifierscalar ternary;这是最简形式——声明了一个名为ternary的独立标量它不继承任何现有类型。这种裸标量在语义上等价于一个全新的基础类型具体如何映射到目标语言由 emitter 决定。标准库中的标量层次在深入自定义之前先看标准库如何组织标量。以 intrinsics.tsp 为例数字类型形成了一条继承链scalar numeric; scalar integer extends numeric; scalar float extends numeric; scalar int8 extends integer; scalar int16 extends integer; scalar int32 extends integer; scalar int64 extends integer; scalar uint8 extends integer; scalar uint16 extends integer; scalar uint32 extends integer; scalar uint64 extends integer; scalar safeint extends integer; scalar float32 extends float; scalar float64 extends float; scalar decimal extends numeric; scalar decimal128 extends decimal;同样地标准库类型定义 中的url直接继承了stringscalar url extends string;而unixTimestamp32则通过encode装饰器在utcDateTime的基础上指定了底层编码方式encode(unixTimestamp, int32) scalar unixTimestamp32 extends utcDateTime;这条继承链展示了 TypeSpec 的核心理念尽量复用内建标量在其上叠加领域语义这样 emitter 就能依据继承关系推断出最合适的宿主语言类型。扩展一个标量Extending a Scalar通过extends关键字可以基于现有标量创建新标量这是最常用的自定义方式scalar Password extends string;Password继承了string的所有语义与编码方式但作为独立类型出现在类型系统中。这在实践中非常有用语义表达更清晰接口中Password与string是不同类型阅读者能直接理解字段的业务含义为后续添加装饰器如format、minLength留出挂载点emitter 可以针对Password单独定制输出例如生成带掩码的序列化逻辑。扩展的标量也支持级联继承例如可以继续scalar StrongPassword extends Password;从而构建立体的领域类型体系。带模板参数的标量Scalars with Template Parameters标量同样支持模板参数泛型这些模板参数主要用于配合装饰器使用doc(Type) scalar UnrealType extends valueof string;这里Type是一个模板参数约束为valueof string即必须是字符串值而不是字符串类型。doc(Type)表示文档内容由传入的值动态决定例如scalar SpecialType extends valueof string;使用时会传入具体的值scalar MySpecial extends Specialcustom;或直接作为字段类型使用。这类带参数的标量适合描述同一族的基础类型通过参数区分语义。标量构造函数Scalar Constructors标量可以声明构造函数用于基于其他值创建特定标量值。语法使用init关键字例如自定义 IPv4 标量scalar ipv4 extends string { init fromInt(value: uint32); } const homeIp ipv4.fromInt(2130706433);这里ipv4声明了一个名为fromInt的构造函数接受一个uint32参数。随后可以用ipv4.fromInt(2130706433)在编译期构造出homeIp常量。构造函数没有运行时实现文档明确指出构造函数不携带任何运行时代码。它们的作用仅仅是记录调用了哪个构造函数、传入了什么参数构造器名称与参数列表从而让 emitter 在需要时自行构造出正确的运行值。也就是说TypeSpec 编译器不会为fromInt生成任何具体实现逻辑如何把2130706433变成127.0.0.1是 emitter 的职责。从编译器源码看构造函数被建模为独立的ScalarConstructor类型节点checker.ts 中的checkScalarConstructor会为每个init成员创建kind: ScalarConstructor的类型对象并在标量类型上维护一个构造器映射constructors: Mapstring, ScalarConstructor。因此从源码结构可以推断ipv4.fromInt(...)这类表达式在语义检查阶段被解析为对ScalarConstructor的调用其参数会被记录为常量值。日期/时间构造函数标准库为日期与时间标量提供了两组开箱即用的构造函数其真实定义见 intrinsics.tsp标量语义构造函数plainDate无时区的日历日期如 April 10thinit fromISO(value: string)、init now()plainTime无时区的时钟时间如 3:00 aminit fromISO(value: string)、init now()utcDateTimeUTC 时刻init fromISO(value: string)、init now()offsetDateTime带时区偏移的日期时间init fromISO(value: string)、init now()duration时长/时间段如 5s、10hinit fromISO(value: string)fromISO从 ISO 8601 字符串创建const date plainDate.fromISO(2024-05-06); const time plainTime.fromISO(12:34); const timestamp utcDateTime.fromISO(2024-05-06T12:20:00Z); const offsetTime offsetDateTime.fromISO(2024-05-06T12:20:00-07:00); const period duration.fromISO(P1Y1D);fromISO接受标准的 ISO 8601 字符串涵盖日期、时间、UTC 时刻、带偏移时刻与时长五种形态是声明常量值最直观的方式。now()当前日期/时间now()构造函数表示应使用当前日期或时间。emitter 会将其解释为适当的运行时值——例如数据库的CURRENT_TIMESTAMP、JavaScript 的Date.now()等。常见的用法是为模型字段提供默认值model Record { createdAt: utcDateTime utcDateTime.now(); updatedAt: utcDateTime utcDateTime.now(); } model Event { date: plainDate plainDate.now(); time: plainTime plainTime.now(); }测试验证编译器测试 scalar-values.test.ts 专门验证了now()构造器的行为。测试通过compileValue(utcDateTime.now())等方式编译表达式并断言结果满足value.valueKind ScalarValuevalue.type.name与value.scalar.name均为对应标量名如utcDateTime构造器名称为now且value.value.args为空数组。这从侧面印证了文档的表述now()是一个无参数构造函数语义检查阶段只记录构造器名称与空参数列表真正的当前时间取值完全交由 emitter 在运行时决定。在表达式位置使用标量实验性:::warning 声明表达式declaration expressions是 TypeSpec 的实验性特性默认关闭。除非在tspconfig.yaml的features列表中显式启用declaration-expressions否则在表达式位置使用model、enum、union或scalar声明都会被编译器视为错误kind: project features: - declaration-expressions:::启用后scalar关键字可以出现在任何期望类型表达式的位置——例如作为别名alias的值、属性类型、装饰器或模板参数、元组元素。看下面的例子model Measurement { // anonymous scalar in expression position temperature: scalar extends float64; // named scalar in expression position distance: scalar Meters extends float64; }temperature的属性类型是一个匿名标量直接内联扩展float64distance的属性类型是具名标量Meters同样扩展float64。从特性机制看该开关由编译器核心的 features.ts 管理declaration-expressions是已注册的实验特性之一checker.ts 在遇到表达式位置的声明时会调用isCompilerFeatureEnabled(program, declaration-expressions, node)进行检查未启用时抛出消息 Declaration expressions require the declaration-expressions feature to be enabled...见 messages.ts。表达式位置标量的作用域规则一个关键语义表达式位置的标量会被标记为表达式并且不会注册到外层命名空间。即使给它命名如上面的Meters该名称也仅仅保留在结果类型上用于展示不能在别处引用。因此在model Measurement之外无法再通过Meters来引用这个类型——它是匿名的、局部的。内联装饰器、文档注释与导航引用表达式位置的标量声明可以内联应用装饰器与文档注释也可以通过::type这样的导航引用来 augment扩充它。例如model Temperature { value: scalar extends float64 minValue(0) maxValue(100); }装饰器直接挂在内联标量上为 emitter 提供进一步的约束信息。小结标量是 TypeSpec 中无字段的最简类型用scalar关键字声明名称须为合法标识符用extends基于内建标量构建领域类型如Password extends string、标准库中的url extends string、unixTimestamp32 extends utcDateTime标量支持模板参数常配合装饰器使用init构造函数只记录构造器名 参数不包含运行时代码实际值由 emitter 生成标准库为plainDate、plainTime、utcDateTime、offsetDateTime、duration提供了fromISO与now()构造器在tspconfig.yaml中启用declaration-expressions实验特性后scalar可用于表达式位置但具名标量不会注册进命名空间仅供展示。若希望深入学习可以继续阅读 TypeSpec 的 标识符规则、装饰器指南或直接查看 内建标量定义 与 标准库类型定义 的源码以及 scalar-values.test.ts 中关于构造函数的完整测试用例。【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespec创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考