TypeSpec 语言访问修饰符(Access Modifiers)完全指南:用 `internal` 控制库的公共 API 边界

TypeSpec 语言访问修饰符(Access Modifiers)完全指南:用 `internal` 控制库的公共 API 边界 TypeSpec 语言访问修饰符Access Modifiers完全指南用internal控制库的公共 API 边界【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespec访问修饰符是 TypeSpec 库作者控制哪些声明能被库的消费者引用的核心语言机制。本文以 access-modifiers.md 为主线完整讲解internal修饰符的适用声明类型、跨库访问规则、与extern的组合用法及其与可见性Visibility系统的本质区别并结合编译器源码checker.ts、binder.ts、modifiers.ts与测试用例internal.test.ts深入剖析其底层实现原理。读完本文你将能熟练使用internal区分库的公共 API 表面与内部实现细节并理解为什么访问控制是符号级而非类型级的。⚠️实验性功能警告internal访问修饰符目前是实验性特性未来版本可能更改或移除。编译器在代码使用访问修饰符时会发出警告参见 checker.ts 中internalDecoratorValidation等校验逻辑周边对实验性特性的处理方式。internal修饰符把声明锁进库内部internal修饰符将某个声明限制为只能在定义它的库或项目内部访问。库的消费者无法引用被标记为internal的声明从而帮助库作者隔离内部实现细节保证公共 API 的稳定性与整洁度。internal model MyInternalModel { secret: string; } model MyPublicModel { // OK同一个库内可以引用 internal 模型 details: MyInternalModel; }从编译器实现看internal的语义在**绑定阶段binder**就已经落地bindModelStatement等绑定函数会读取节点的modifierFlags若包含ModifierFlags.Internal则在声明符号上打上SymbolFlags.Internal标记见 binder.ts。SymbolFlags.Internal的定义为只能从同一包内的源文件引用的内部符号见 types.ts。后续所有跨库引用检查都以该标志为判断依据。支持的声明类型internal修饰符可以应用于以下声明类型以下用法均被测试用例 internal.test.ts 逐一验证声明示例modelinternal model Example {}scalarinternal scalar Example;interfaceinternal interface Example {}unioninternal union Example {}opinternal op example(): void;enuminternal enum Example {}aliasinternal alias Example string;constinternal const example 1;注意internal修饰符不能应用于namespace声明原因详见下文为什么不能用于 namespace小节。从源码层面看这一兼容性约束定义在 modifiers.ts 的SYNTAX_MODIFIERS表中所有声明语句OperationStatement、ModelStatement、ScalarStatement、InterfaceStatement、UnionStatement、EnumStatement、AliasStatement、ConstStatement使用DEFAULT_COMPATIBILITY其allowed集合就是ModifierFlags.Internal而NamespaceStatement使用NO_MODIFIERSallowed: ModifierFlags.None从语法层面直接禁止internal namespace。checkModifiers函数modifiers.ts在检查时会对超出allowed集合的修饰符报出invalid-modifier诊断——测试用例验证了internal namespace Foo {}与internal namespace Foo;都会得到 Modifier internal cannot be used on declarations of type namespace. 的报错internal.test.ts。访问规则符号级控制类型级穿透internal修饰符是符号symbol即引用类型的名字的属性而不是类型本身的属性。编译器阻止其他包按名字引用内部符号但并不阻止底层类型被间接使用。同一包内的公共声明可以自由引用内部声明消费者可以经由公共声明获得最终类型。当声明被标记为internal时编译器强制执行以下规则同一库或项目内同一库若不在库中则为同一项目中的代码可以正常引用内部声明。不同库不同库中试图按名字引用内部声明的代码会收到编译错误。下面用两个文件的示例演示这一边界文件路径沿用文档约定实际使用时请将my-lib替换为你的库包名namespace MyLib; internal model SecretHelper { key: string; } model PublicApi { data: SecretHelper; // ✅ OK同一库内 } // ✅ OK公共 alias 可以在同一包内引用内部符号。 // 消费者可以使用 ExposedHelper尽管它指向与 SecretHelper 相同的类型。 alias ExposedHelper SecretHelper;import my-lib; model Consumer { helper: MyLib.SecretHelper; // ❌ 错误SecretHelper 是 internal data: MyLib.PublicApi; // ✅ OKPublicApi 是公共的即使它引用了 SecretHelper exposed: MyLib.ExposedHelper; // ✅ OKExposedHelper 是公共 alias }直接引用内部符号时错误信息为Symbol SecretHelper is internal and can only be accessed from within its declaring package.这段错误文本定义在 messages.ts诊断码为invalid-ref。底层实现checkSymbolAccess访问检查的真正实现在 checker.ts 的checkSymbolAccess函数中。其判断逻辑如下通过symbol.flags同时包含SymbolFlags.Internal与SymbolFlags.Declaration判定这是一个内部声明如果引用点的来源位置sourceLocation是synthetic合成或compiler编译器标准库直接放行——这保证了编译器内部机制与标准库不受限制否则遍历该符号的所有声明检查是否存在一个兼容位置的声明如果引用点位于用户项目中而目标符号也声明在用户项目中则放行declLocation.type project直接返回 true如果引用点位于某个库中目标符号必须声明在同一个库通过引用相等declLocation sourceLocation判断才放行都不满足则抛出invalid-ref/internal诊断。这正对应了文档中同一库/同一项目可访问、不同库不可访问的规则并且精确解释了跨文件同项目访问为何合法internal.test.ts 中main.tsp与other.tsp属于同一项目可以互相引用 internal 模型。测试用例规则的完整验证矩阵internal.test.ts 将上述规则组织成了一张覆盖完整的验证矩阵可作为理解行为边界的权威参考跨库拒绝model、scalar、interface、union、op、enum、alias各自都有另一包引用 internal 声明报错的用例L100-L178命名空间内引用通过MyLib.Secret完整限定名引用、以及通过using MyLib;后直接引用都会被拒绝L180-L207继承边界从另一包extends一个 internal 模型同样被拒绝L209-L218同项目放行同项目内的 model/enum/op/scalar/alias 引用均无诊断L221-L280同库放行库内跨文件引用 internal 模型库外消费者使用其公共外壳无诊断L282-L299公共符号透传内部类型库内PublicModel的属性类型是 internal 模型消费者仍可正常使用PublicModelL301-L333——这正是类型级穿透的测试证据。此外internal作为普通标识符如模型属性名internal: string、union 变体名是合法的不会与修饰符语法冲突L336-L346。与extern组合内部装饰器签名internal修饰符可以与装饰器声明上的extern修饰符组合创建内部装饰器签名internal extern dec myInternalDecorator(target: unknown);这一组合在 modifiers.ts 中为DecoratorDeclarationStatement定义了独立的兼容性配置allowed: ModifierFlags.All即Extern | Internal | Autorequired: ModifierFlags.Extern | ModifierFlags.Auto且Extern与Auto互斥。测试 internal.test.ts 验证了internal extern dec myDec(target: unknown);的合法性与零诊断结果。标准库中已有实际使用案例packages/compiler/lib/prototypes.tsp中的internal extern dec getter(target: unknown);prototypes.tsp。测试用例还验证了用户代码直接引用标准库内部装饰器如TypeSpec.indexer、TypeSpec.docFromComment、TypeSpec.Prototypes.getter时会收到包含 internal 的invalid-ref错误internal.test.ts说明编译器自身也在用该机制保护标准库内部实现。为什么不能用于namespaceinternal修饰符不支持应用于命名空间因为 TypeSpec 中的命名空间是开放且合并open and merged的在一个文件中声明的命名空间可以在另一个文件中被扩展——甚至可能跨库边界。如果允许对命名空间施加internal会产生命名空间的哪些部分内部、哪些部分公共的歧义。因此正确的做法是对命名空间内的单个声明逐个标记internalnamespace MyLib; internal model InternalHelper {} // ✅ 标记单个声明 model PublicApi {}源码层面的依据同样清晰SYNTAX_MODIFIERS表中NamespaceStatement的allowed为空modifiers.ts语法上就不可能写出合法的internal namespace。命名空间的合并语义则由 binder 的符号合并机制承载getMergedSymbol等逻辑见 checker.ts。与可见性Visibility的关系互补的两套系统internal访问修饰符与 TypeSpec 的可见性系统是互补的需要注意区分维度访问修饰符internal可见性visibility、removeVisibility控制对象哪些声明模型、操作等可以被跨库引用哪些模型属性出现在不同的 API 操作上下文中如 create vs. read性质编译期强制执行的访问边界由 emitters 消费的元数据系统用于生成模型的不同视图生效时机编译期checkSymbolAccess报错运行期/发射期emitter 读取元数据生成输出可见性系统的详细文档见 visibility.md其核心是可见性仅作用于模型属性通过可见性类visibility class本质是 enum定义属性在哪些上下文中可见并支持默认可见性、生命周期可见性create/read/update/delete 等机制。两者可以作用于同一类型。例如你可以有一个公共模型其属性在不同操作中拥有不同的可见性也可以有一个仅用于库内部实现的 internal 模型。实际开发中internal决定消费者能否在源码里提到这个名字而可见性决定emitter 生成的代码里是否包含某个属性——理解这层区别是设计稳定、可维护的 TypeSpec 库 API 的关键。小结internal是 TypeSpec 实验性的访问控制机制用于隔离库的公共 API 与内部实现可应用于 model、scalar、interface、union、op、enum、alias、const 等八类声明并可与extern组合用于内部装饰器签名。它是符号级属性同一库/项目内可自由引用跨库按名引用报invalid-ref但类型可经由公共声明间接透出。它不能用于 namespace开放合并语义导致歧义应标记单个声明。它与可见性Visibility是互补系统一个管声明能否被引用一个管属性在不同操作上下文中的呈现。实现层面语法兼容性由 modifiers.ts 保证符号标记由 binder.ts 完成跨库访问检查由 checker.ts 的checkSymbolAccess强制执行完整行为矩阵见 internal.test.ts。【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespec创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考