Carbon 语言设计精解:类型是 `type` 类型的值——p002360 提案的语义模型、成员访问规则与工具链实现
Carbon 语言设计精解类型是type类型的值——p002360 提案的语义模型、成员访问规则与工具链实现【免费下载链接】carbon-langCarbon Languages main repository: documents, design, implementation, and related tools. (NOTE: Carbon Language is experimental; see README)项目地址: https://gitcode.com/GitHub_Trending/ca/carbon-lang导读本篇文章围绕 Carbon Language 设计提案 p002360「Types are values of typetype」展开系统讲解该提案如何在 Carbon 中统一类型的形式定义一个类型就是一个类型为type的值所有需要类型的上下文通过隐式转换把()、(i32, i32)、{}、facet 值等转换为type。文章既完整继承了提案中关于术语体系、type关键字、复合成员访问compound member access、元组/结构体字面量、约束类型名字查找与诊断输出的全部细节也结合当前仓库中toolchain/sem_ir的源码与docs/design/generics的设计文档给出工具链实现层面的佐证。读完你可以掌握为什么(i32, i32)不再是一个类型而是一个可隐式转换为类型的值x.(Iface.Method)()与T.(Iface.Static)()分别走哪条 impl 查找路径以及 facet、facet type、generic type 等术语的精确含义与实现对应物。背景提案的动机与定位两个被暴露的不一致问题提案在 Problem 一节中指出了旧模型中两个核心的不一致元组行为不一致。旧模型中(i32, i32)的类型是(Type, Type)而(Type, Type)的类型也是(Type, Type)即元组与空结构体{}在类型的类型链上无法收敛到同一个终结点Type导致实现复杂、语义含糊甚至 explorer 容易把元组值与元组类型混用而出错。更关键的是这种自指能力是内置类型独有的用户无法设计一个类类型表现出元组类型那样的行为。约束成员的限定访问不一致。对表达式x.(Interface.Function)当Function是实例方法时行为取决于x是否是类型非类型x要求T is Interface并做实例绑定而x是类型时要求符号值阶段并产生未绑定成员名。这带来模板依赖性问题、模板与 checked generic 之间的行为断裂以及impl Type as ...无法被直接调用的问题。提案的核心主张提案的答案是不再把类型似值当作类型而是定义一个类型为类型是type的值见 Abstract。语言语义的实际冲击被刻意保持在较小范围其主要价值在于确立更清晰、更有原则的术语与设计概念并修复少数一致性缺陷。背景方面提案引用 p000989「Member access expressions」引入了当时的复合成员访问规则并提及 Issue #495元组隐式转换为Type、Issue #508((), (), ())是否自定型与 Issue #2113预定义名如type的拼写与行为准则。提案核心一个类型是type类型的值形式定义与直接推论在 Proposal 中提案给出如下核心推论()是类型为() as type的值而不是类型为()的值后者根本不是类型() as type的类型才是type。同理(i32, i32)的类型是(type, type) as type其类型为type{}的类型是{} as type其类型为type。推广结论任何类型的类型都是type即类型链必然收敛到type这个终结点消除了旧模型中元组/空结构体的特殊自指分支。对T:! HashableT形式上不是类型它的类型不是type对type进而对所有类型做成员查找什么都找不到而对T做成员查找能找到Hashable的成员。需要类型的上下文如:、:!绑定的右侧、函数返回类型执行到type的隐式转换。这意味着用户自定义类型可以实现ImplicitAs(type)用未来确定的可编译期使用注解标注使其值像()与{}一样可当作类型使用。不再有类型的类型type-of-type的单例概念。仍可设计一个唯一值为i32的类型此时i32形式上不是类型其类型不是type但只要实现impl IntLiteral(n) as ImplicitAs(type)整数字面量值就可以隐式转换为类型从而支持let v: 0 0;。不过对i32或整数字面量的这类改动不属于本提案范围。术语体系type、facet type 与 facetTerminology 一节是提案对语言描述体系的重大更新type类型一个类型为type的值。facet type方面类型类型为type中某些子集的类型由一组约束决定。接口类型与命名约束类型是 facet type运算与where表达式产生的结果也是 facet typetype本身是约束集为空的 facet type。旧称type-of-type类型的类型不再准确因为任何类型的类型现在都定义上是type。facet方面facet type 的值例如i32 as Hashable是一个 facetHashable是一个 facet type。所有类型都是 facet。从实现视角看facet 值可视为一个可能为符号的类型标识 证明该类型满足约束所需的见证witness的元组类型则是没有见证的 facet 值。generic type泛型类型由:!绑定引入的类型或 facet如泛型参数、关联常量派生出generic type parameter、associated generic type等术语。注意 generic type 不一定是类型它可能是 facet其类型可能是除type外的 facet type。提案对备选术语constraint type / constrained type / constraint / kind / archetype / type-like / kinded / witness / impl 等逐一列出并给出否决理由详见 Alternative terminology。type与Core.Type提案引入type作为关键字类型字面量取代临时名Type指代约束集为空的 facet type见typeandCore.Type。尽管 Issue #2113 允许这类功能作为 prelude 中某名字如Core.Type的别名本提案不引入对应 prelude 名facet typetype是编译器提供的原语而非别名。同时依据 泛型设计文档「Named constraints」constraint MyVersionOfType {}这类声明会引入与type等价的 facet type。因为二者是同一个 facet typeT:! MyVersionOfType在本提案下仍是类型这是否是正确行为仍留有 leads 的开放 Issue#2409。实现层面的印证在toolchain/sem_ir中类型系统使用TypeType这一内建单例类型表示类型的类型。TypeStore::IsFacetType()的判定就是type_id TypeType::TypeId || IsFacetType(type_id)见 toolchain/sem_ir/type.h并把 facet 值排除在类型值之外——GetTypeIdForTypeConstantId的注释明确指出facet 值虽与类型具有相同的类型性typishness但本身不是类型必须通过as type转换即FacetAccessType指令才能得到类型为TypeType的值见 toolchain/sem_ir/type.h。在toolchain/sem_ir/typed_insts.h中可以进一步看到该模型的指令级表达FacetValuefacet 值的表示由一个类型与一组满足 FacetType 所需接口的见证组成该指令永远不是类型但转换为类型时会求值为其中的type_inst_id见 toolchain/sem_ir/typed_insts.h。FacetAccessType访问 facet 值中类型字段的指令其类型恒为内建TypeType与facet 必须经as type才成为类型的设计一一对应见 toolchain/sem_ir/typed_insts.h。IdentifiedFacetType中的RequiredImplself_facet_value实现了specific_interface的需求与facet 值是类型加见证的元组的描述相吻合见 toolchain/sem_ir/identified_facet_type.h。测试用例也直接验证了该模型例如toolchain/check/testdata/facet/convert_facet_value_to_facet_value.carbon中反复出现的((C as Y) as type) as Y链以及toolchain/check/testdata/impl/lookup/canonical_query_self.carbon中的(t as (((((((T as I) as type) as J) as type) as I) as type) as J)).JJ()展示了 facet 与type之间可逆但语义不同的往返转换。复合成员访问Compound member access规则重写实例绑定规则在 Instance binding 中提案重新定义了成员访问的语义对语法x.y若x是有成员名的实体如命名空间或类型则在x内查找y不做实例绑定否则在x的类型内查找y若找到实例成员则执行实例绑定。对语法x.(Y)其中Y命名一个实例成员总是执行实例绑定。于是对合适的DebugPrintable1.(DebugPrintable.Print)()仍然打印1行为不变i32.(DebugPrintable.Print)()现在打印i32而不是构成一个成员名1.(i32.(DebugPrintable.Print))()现在是错误而不是打印1。要获得旧行为x.(MyType.(MyInterface.InstanceMember))()现在可以写x.((MyType as MyInterface).InstanceMember)()。这与下面函数的成员访问行为完全相同因为二者本质相同fn F(MyType:! MyInterface, x: MyType) - auto { return x.(MyType.InstanceMember)(); }原因在于MyType as InstanceMember是一个 facet大致对应一个已完全实例化的impl对 facet 的查找会找到对应impl的成员若MyType声明为MyType:! MyInterface则MyType与MyType as MyInterface等价。impl 查找规则Impl lookup 一节对依赖左操作数是否为类型的旧规则做了统一化改造简单成员访问a.bb命名接口I的成员若该接口成员是在非 facet type 的作用域如类或 adapter中找到的则执行T as I的 impl 查找其中T是被搜索的作用域MyClass.AliasForInterfaceMember找到impl MyClass as I的成员而非接口成员my_value.AliasForInterfaceMembermy_value: MyClass同样找到impl MyClass as I的成员并在成员为实例成员时做实例绑定若成员是在基类中找到的T是被搜索的派生类而不是声明该名的基类。否则不执行 impl 查找MyInterface.AliasForInterfaceMember直接在接口MyInterface中找成员。复合成员访问a.(b)b命名接口I的成员执行T as I的 impl 查找其中若b是实例成员T是a的类型my_value.(Interface.InstanceInterfaceMember)()my_value类型为MyClass使用MyClass as InterfaceMyClass.(Interface.InstanceInterfaceMember)()使用type as Interface因为此时MyClass被当作type的值。否则a被隐式转换为IT是转换结果符号求值的产物MyClass.(Interface.NonInstanceInterfaceMember)()使用MyClass as Interfacemy_value.(Interface.NonInstanceInterfaceMember)()在my_value不能隐式转换为类型时报错。完整示例接口与继承场景提案在 Examples 中给出了覆盖接口、基类、派生类三种场景的完整代码以下为原文完整继承含注释含义说明interface Iface { fn Static(); fn Method[me: Self](); } impl type as Iface { ... } fn FT:! Iface { // ✅ Uses T as Iface. x.Static(); T.Static(); // ❌ Uses T as Iface, but method call is missing an instance. T.Method(); // ✅ OK, instance is provided. Uses T as Iface. x.Method(); x.(T.Method)(); // ❌ Would use (x as Iface).Static. // Error because x is not a symbolic constant. x.(Iface.Static)(); // ✅ Uses (T as Iface).Static. T.(Iface.Static)(); // ✅ Uses (type as Iface).Static. type.(Iface.Static)(); // ✅ Uses (T as Iface).Method, with me x. x.(Iface.Method)(); // ✅ Uses (type as Iface).Method, with me T. T.(Iface.Method)(); } base class Base { fn Static(); fn Method[me: Self](); alias IfaceStatic Iface.Static; alias IfaceMethod Iface.Method; } external impl Base as Iface { ... } fn G(b: Base) { // ✅ OK b.Static(); Base.Static(); // ❌ Uses Base as Iface, but method call is missing an instance. Base.Method(); // ✅ OK b.Method(); b.(Base.Method)(); // ✅ OK, same as (Base as Iface).Static(). b.IfaceStatic(); Base.IfaceStatic(); // ❌ Uses Base as Iface, but method call is missing an instance. Base.IfaceMethod(); // ✅ OK, uses Base as Iface. b.IfaceMethod(); b.(Base.IfaceMethod)(); b.((Base as Iface).Method)(); b.(Iface.Method)(); } class Derived extends Base {} external impl Derived as Iface { ... } fn H(d: Derived) { // ✅ OK, calls Base.Static. d.Static(); Derived.Static(); // ❌ Uses Derived as Iface, but method call is missing an instance. Derived.Method(); // ✅ OK, calls Base.Method. d.Method(); d.(Base.Method)(); d.(Derived.Method)(); // ✅ OK, same as (Derived as Iface).Static(). d.IfaceStatic(); Derived.IfaceStatic(); // ❌ Uses Derived as Iface, but method call is missing an instance. Derived.IfaceMethod(); // ✅ OK, uses Derived as Iface. d.IfaceMethod(); d.(Derived.IfaceMethod)(); d.((Derived as Iface).Method)(); d.(Iface.Method)(); // ✅ OK, uses Base as Iface. d.(Base.IfaceMethod)(); d.((Base as Iface).Method)(); }要点总结MyClass.(Interface.InstanceInterfaceMember)()与type.(Iface.Method)()这类形式利用MyClass、T是类型即type的值这一新模型把 impl 查找统一到type as Interface上类内alias G A.F;这类别名仍能像类的成员一样被使用见下文备选方案对 alias 的讨论派生类场景中d.(Base.IfaceMethod)()走Base as Iface而d.IfaceMethod()走Derived as Iface验证了被搜索的作用域决定T的规则。这些行为与 docs/design/generics/details.md 中关于限定成员名与复合成员访问的设计描述一致泛型设计中还指出检查泛型绑定时T与最终绑定的 facet 值解耦T作为 archetype 处理其成员与成员访问由 facet type 的名字决定见 docs/design/generics/details.md。元组与结构体字面量Tuple and struct literals 一节澄清了元组/结构体类型与值的边界元组字面量(1, 2, 3)产生一个元组值其类型不能直接用字面量写出——(i32, i32, i32)同样是元组值而非类型值。但该类型很容易表达它就是(i32, i32, i32)隐式转换为类型的结果因此(i32, i32, i32) as type求值为(1, 2, 3)的类型。不存在从元组类型到元组值的转换因为元组类型是类型type的值而type不会隐式转换为元组类型。对元编程把元组类型转成类型元组需要变参机制提案给出了如下可行写法// Takes a tuple of values. Returns a tuple containing the types of those values. fn TupleTypeToTupleOfTypesTypes:! type,...) - auto { return (Types,...); }关于是否用与元组不同的语法书写元组类型如TupleType(i32, i32)或(|i32, i32|)提案在 Write tuple types differently than tuples 中给出了否决理由多字符定界符成本高、(i32, i32)作为合法值表达式不能被剥夺类型用法、模式语法与类型语法不一致会令人困惑、以及会阻碍对元组值与元组类型的泛型统一操作如需要重复实现TupleCat与TupleTypeCat。约束类型值的名字查找Name lookup into values with constrained types 一节说明给定泛型函数中的泛型类型参数对参数的使用会隐式转换为类型type。例如fn FT:! Printable { x.Print(); }等价于fn FT:! Printable { x.Print(); }由此不能再把x的查找描述为查找 type-of-type即x:后表达式的类型因为隐式转换后该表达式类型为type。新的查找路径是查找x的类型符号求值为type中对应T的值再基于该值决定继续查找的位置由于该值符号上就是泛型类型参数的名字最终会在T的类型即Printable中查找。这正是facets 可以用于类型上下文、其成员由 facet type 决定在名字查找上的体现。诊断输出中的类型呈现Types in diagnostics 回应了as type会频繁出现在诊断信息中的担忧。当类型出现在诊断中时通常有两种形式一是对程序员所写源码语法的直接反映二是编译器生成的类型描述字符串。两种情况下var v: (i32, i32)中变量的类型都会显示为(i32, i32)而非(i32, i32) as type除非上下文需要as type后缀来消歧或程序员在源码中写了它。这与整数字面量的处理类似描述i32的值1时类型通常作为假定上下文被省略诊断只说1而不说1 as i32C 编译器通常也会避免在诊断中打印(short)5这类对没有字面量语法的类型值。提案依据与教学性考量Rationale提案在 Rationale 中对照 项目目标 论证了价值语言工具与生态元组与空结构体行为向其他类型靠拢更利于工具链处理。软件与语言演进复合成员访问不再依赖左操作数是否为类型模板与 generics 之间的过渡更平滑。易读、易懂、易写所有类型的类型的类型都收敛于type更易理解但(i32, i32)从二元整数对的类型变为可隐式转换为二元整数对的类型则可能更难理解。两股力量哪个占主导尚不明确但新行为更一致。TeachabilityTeachability 坦承该模型对新手包括有 C 经验的开发者较有挑战并建议教学时聚焦目标而非实现细节You can usevar n: (i32, i32)to declare thatnis a pair ofi32s无需提及(i32, i32)实际类型是(type, type)且发生了隐式转换正如可以说n (1, 2);把1、2赋给两个元素而无须提及隐式转换。若教学性长期出现问题应考虑更大改动如为元组类型与空结构体引入独立语法。备选方案分析备选成员访问规则Alternative member access rules 记录了考虑过的规则T.(Interface.Member)要么总是(T as Interface).Member要么总是(typeof(T) as Interface).Member。两种方案都使 alias 无法按预期工作——提案希望定义在类内的接口方法或类函数别名能作为该类的成员方法或类函数使用interface A { fn F[me: Self](); fn S(); } class B { external impl as A; alias G A.F; fn H[me: Self](); alias T A.S; fn U(); }期望B.G像B.H一样是B的方法B.T像B.U一样是B的静态类函数。虽然也可以通过alias G (Self as A).F实现但那样写令人意外。最终方案是让T.(Interface.Member)的解释取决于Member是否为带隐式me参数的实例成员。相关讨论发生于 2022-11-07 的 #syntax Discord 频道与 2022-11-10 的开放讨论。Core.Type作为空命名约束Core.Typeis an empty named constraint 记录了将type定义为 prelude 中空类型约束Core.Type别名的可能性最终否决若type是命名约束由于它无处不在prelude 本身的类型检查会更具挑战性若type是Core.Type的别名实现要么必须保证其不引入任何成员名与约束要么每次查询type的属性都去忠实查找Core.Type增加编译期开销或实现复杂度。既然当前没有给type添加成员名或约束的诉求把它定义在 Carbon 库中并无收益。若未来要让type默认隐含Sized、Movable等约束应重新审视该决定。术语备选方案Alternative terminology 完整记录了术语选择的权衡facet type的备选constraint type歧义可能被理解为同时是 foo 的类型、constrained type更贴合 facet 而非 facet type、constraint易被当作布尔谓词T is C、kind文献中多指类型构造子的元数而非对类型集合的分类。generic type的备选archetype与既有用法直觉不符且类有 archetype 参数的说法别扭、constrained type variable、type-like 与 kinded派生态如 generic type-like parameter、associated kinded constant 过于拗口。facet的备选type无法区分i32 as Sortable与i32 as Hashable且转换到type会擦除 facet type的语义若也称类型会造成概念混乱、implfacet 可对应多接口约束从而跨多个impl一个impl也可被 facet 只引用一部分非一一对应、archetype不适合i32 as Hashable这类非泛型 facet、witnessfacet 更像类型 该类型满足 facet type 约束的见证这一对。结语提案对 Carbon 语义模型的意义p002360 用一个简洁定义——类型是类型为type的值——统一了元组、空结构体、facet、泛型参数等所有类型似值的处理它们不再自指为类型而是统一通过到type的隐式转换进入类型上下文复合成员访问与 impl 查找也不再依赖左操作数是否为类型。这一语义模型在toolchain/sem_ir中落地为TypeType、FacetType、FacetValue、FacetAccessType等指令与IsFacetType()等判定逻辑在 泛型设计文档 与 术语表 中形成了与之一致的术语体系。如需深入可以继续阅读提案原文 proposals/p002360-types-are-values-of-type-type.md、成员访问设计 docs/design/expressions/member_access.md、泛型细节 docs/design/generics/details.md以及工具链实现中的 toolchain/sem_ir/type.h、toolchain/sem_ir/typed_insts.h 与 facet 相关的测试用例如 toolchain/check/testdata/facet 目录。【免费下载链接】carbon-langCarbon Languages main repository: documents, design, implementation, and related tools. (NOTE: Carbon Language is experimental; see README)项目地址: https://gitcode.com/GitHub_Trending/ca/carbon-lang创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考