Slang 语言 struct 类型规范一致性测试指南:从语言参考文档到 44 项可验证声明
【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang
本文档深入剖析 Slang(Shader Language)编译器中struct类型特性的一致性测试套件。该套件位于 docs/generated/tests/conformance/types-struct/,以官方语言参考文档 docs/language-reference/types-struct.md 为唯一规范来源,将其中每一条结构体相关的规范性描述(claim)拆解为 44 项可执行验证,并配套 31 个.slang测试文件在 HLSL、GLSL、SPIR-V、Metal、WGSL、CUDA、CPP 与 CPU 等多后端上逐一验证。读完本文,你将理解 Slang 编译器如何"用测试钉住语言规范":包括结构体的四种声明形式、六类成员、构造与聚合初始化、成员函数可改性([mutating])、属性(property)、下标(__subscript)、函数调用运算符(operator ())、内存布局规则,以及规范中尚未被覆盖或与编译器实现存在偏差的部分。
测试包定位:规范与实现之间的"契约"
types-struct一致性测试包是一个自动生成的规范跟踪清单。其 README 的 front-matter 明确记录了生成元数据:
source_doc: docs/language-reference/types-struct.md—— 唯一的规范来源;generated_at与source_commit—— 记录生成时间与对应源码提交,防止测试漂移;warning: "Auto-generated. May drift from source. Do not edit by hand."—— 提示该清单由工具链自动生成,人工修改会被覆盖。
测试包的组织方式体现了"每条规范声明至少对应一个功能测试"的策略(见 README.md 的 Intent 一节):
Coverage strategy: one functional test (INTERPRET or emit) per claim, expanded along documented dimensions (member kinds, visibility, constructor overloads, accessor forms, subscript arities, call-operator overloads, memory layout, generic structs, interface conformance, nested types, and
Thistype keyword).
即:每个声明一条功能测试(解释执行或代码发射),并沿文档定义的维度展开——成员种类、可见性、构造函数重载、访问器形式、下标参数数量、调用运算符重载、内存布局、泛型结构体、接口一致性、嵌套类型与This类型关键字。负面测试(negative test)覆盖"向只读成员写入"与"构造函数中初始化 const 成员"两类编译错误。静态数据成员因为 slangi VM 字节码发射器不支持模块级全局指令,只能通过 HLSL 发射测试验证。
规范声明全解:44 项可验证声明
规范声明按文档章节组织为 13 个主题,以下逐项展开(声明编号 C1–C44 与 README.md 完全对应)。
语法与声明形式(C1–C5)
语法部分对应语言参考的 Syntax 章节,定义了四种声明产生式:
- C1 无体声明(no-body form):
struct Identifier;仅声明结构类型的存在,使其可以在完整定义之前用于类型表达式; - C2 带成员声明(with-members form):
struct Identifier { ... }定义带布局与非布局成员列表的结构类型; - C3 具名/匿名:结构名作为标识符使用;省略标识符时结构为匿名,编译器分配一个未指定的唯一名称;
- C4 匿名结构的内联用法:
struct { int a; } obj;—— 匿名结构只在 with-members 形式中有意义,主要用于内联类型定义表达式; - C5 链接期声明:
extern struct与export struct用于跨模块的链接期类型定义。
语法产生式细节(摘自 types-struct.md):
struct-decl (no-body) = [modifier-list] 'struct' [identifier] [generic-params-decl] [':' bases-clause] ['=' simple-type-spec] ';' struct-decl (with-members) = [modifier-list] 'struct' [identifier] [generic-params-decl] [':' bases-clause] ('where' where-clause)* '{' member-list '}' struct-decl (extern) = [modifier-list] 'extern' 'struct' [identifier] [generic-params-decl] [':' bases-clause] ['=' simple-type-spec] ';' struct-decl (export) = [modifier-list] 'export' 'struct' [identifier] [generic-params-decl] [':' bases-clause] '=' simple-type-spec ';'member-list允许七类成员:变量声明(var-decl)、嵌套类型(type-decl)、成员函数(function-decl)、构造函数(constructor-decl)、属性(property-decl)、下标运算符(subscript-op-decl)与函数调用运算符(function-call-op-decl)。
对应的功能测试包括:
- struct-no-body-decl.slang:验证 C1,并在 hlsl/glsl/spirv-asm/metal/wgsl 五个目标上钉住诊断输出;
- struct-basic-fields-functional.slang:验证 C3 与 C6;
- struct-anonymous.slang:验证 C4,其发射检查显示匿名结构在所有文本发射后端统一命名为
SLANG_anonymous。
成员种类(C6–C12)
对应语言参考的 Description 章节:
- C6 非静态数据成员:以不带
static的变量声明,存储随每个实例分配在结构体内; - C7 静态数据成员:以
static声明,存储分配在全局存储中。测试 struct-static-member-functional.slang 验证其在 HLSL 中发射为模块级static int b = int(5),在 WGSL 中为var<private>,在-O0 -g2的 SPIR-V 中为Private存储类的OpVariable; - C8
static const数据成员:必须带默认初始化器。测试 struct-static-const-initializer.slang 验证可通过类型名访问并折叠为常量值; - C9 嵌套类型:通过外层结构名访问(
TestStruct.NestedStruct); - C10 非静态数据成员顺序分配:按声明顺序在结构体内顺序分配;
- C11 接口一致性:结构体可实现一个或多个接口,测试 struct-interface-conformance.slang 演示了
struct MyStruct : IHasValue满足泛型约束readValue<T : IHasValue>; - C12 访问控制:成员可用
public、internal、private修饰,默认为internal。
从语言参考还可补充两点访问控制细节(types-struct.md):自语言版本 2026 起,无显式说明符的成员继承其外层struct的可见性(public结构体的未修饰成员为public);嵌套struct的成员可访问外层struct的private成员。此外,参考文档明确警告:struct 继承 struct 在 Slang 2025 及更早版本中不稳定,已在 2026 中移除,应改用组合(将结构体作为成员)代替继承。
对象(C13–C14)
对应语言参考的 Objects 章节:
- C13 实例组成:对象(实例)由结构体内所有非静态数据成员组成;
- C14 初始化方式:数据成员可通过初始化列表或构造函数初始化。
构造函数(C15–C21)
对应语言参考的 Constructors 章节。构造函数语法为__init(参数列表) { 函数体 },无返回类型,可有任意数量参数:
- C15 用户构造函数执行:定义
__init后,对象实例化时执行之; - C16 构造函数重载:可定义多个
__init,按重载决议选择最合适者。测试 struct-constructor-overload.slang 定义了无参、单参__init(int)、双参__init(int,int)三个重载,验证TestClass obj2 = { 42 };正确命中__init(int); - C17 无参构造:未提供初始化器时调用无参构造函数;
- C18 未初始化成员状态:未被构造函数初始化的非静态数据成员处于未定义状态(无法断言,见"未测试声明"一节);
- C19 const 成员禁止在构造函数中初始化:测试 struct-constructor-no-const-member.slang 为负面测试,断言在
__init内对const成员赋值触发编译错误 E30011; - C20 聚合初始化:无用户构造函数时执行聚合初始化,初始化列表按声明顺序填充;
- C21 部分初始化:列表值不足时,剩余成员默认初始化。测试 struct-aggregate-init.slang 验证
Plain p2 = { 5, 6 };后p2.z被默认初始化为 0。
语言参考还给出一个值得注意的 Remark(types-struct.md):无用户构造函数的类在无初始化列表时实例化,其初始状态未定义——即使其成员类型自带__init(如内嵌TestField f且TestField有__init()),外层对象的该字段仍处于未定义状态;访问未定义状态的数据成员属于未定义行为。
默认初始化器(C22–C23)
- C22 初始化列表场景:初始化列表未提供某成员值时,该成员的默认初始化器提供其值;
- C23 构造函数场景:构造函数可覆盖非
const成员的默认初始化器。
测试 struct-default-field-initializer.slang 与 struct-default-initializer-constructor-override.slang 分别验证这两种场景。
静态成员函数(C24–C25)
对应语言参考的 Static Member Functions 章节:
- C24 无对象调用:静态成员函数调用不要求对象;
- C25 静态成员访问限制:静态成员函数只能访问静态数据成员。
测试 struct-static-member-function.slang 验证静态成员函数在 HLSL 中作为自由函数发射。注意:修改静态数据成员不需要[mutating]属性(见语言参考中incrementAndReturnB的示例注释)。
非静态成员函数与this(C26–C29)
对应语言参考的 Non-static Member Functions 章节:
- C26 隐藏的
this参数:非静态成员函数带隐藏参数this指向对象;this.member与裸member访问同一字段。测试 struct-this-implicit-optional.slang 验证二者等价; - C27 默认只读:默认情况下成员函数对非静态数据成员只读。测试 struct-member-function-readonly.slang 验证无
[mutating]时读取字段并正确返回值; - C28
[mutating]授权写:[mutating]属性授予对非静态数据成员的写权限。测试 struct-mutating-member-function.slang 展示了后端发射细节:HLSL/GLSL 中以inout TestStruct this_形参发射,Metal 中为thread*指针,WGSL 中为ptr<function, TestStruct>; - C29 无属性写入报错:非
[mutating]函数内写入非静态成员触发编译错误 E30011。测试 struct-mutating-rejected-without-attribute.slang 为负面测试,通过//DIAGNOSTIC_TEST:SIMPLE指令断言error、E30011、l-value同时出现。
用 C++ 术语类比(语言参考原话):成员函数默认为const,[mutating]使其成为非const成员函数。
属性(C30–C33)
对应语言参考的 Properties 章节:
- C30 读写属性:带
get与set的property将读取路由到get、写入路由到set。测试 struct-property-get-set.slang 复刻了语言参考中的someProp示例——set同时更新m_val与自动维护的m_valIsPositive; - C31 只读属性:只有
get的属性为只读。测试 struct-property-readonly.slang 验证 getter 在 HLSL 中正确发射; - C32 隐式
newValue:省略set参数时,set体内自动提供与属性同类型的newValue参数; - C33 显式参数名:可在
set访问器声明中显式给出参数名(如set(float newVal))。测试 struct-property-explicit-param.slang 验证显式参数在 set 体中使用。
属性语法同时支持现代形式(property someProp : float { get {...} set {...} })与传统形式(property float someProp { ... });无访问器的属性声明形式(如property someProp : float;)仅用于接口声明。语言参考还提示ref访问器是 Slang 内部特性,可能变更、不保证可用,因此测试包明确不覆盖ref。
成员与嵌套类型访问(C34–C35)
对应语言参考的 Accessing Members and Nested Types 章节:
- C34 点运算符:静态/非静态成员与嵌套类型统一通过
.访问。测试 struct-dot-member-access.slang 与 struct-members-full-example-emission.slang 验证; - C35
::弃用:C++ 风格作用域解析运算符::已弃用,静态成员访问推荐使用.。语言参考示例中TestStruct::incrementAndReturnB()与TestStruct.incrementAndReturnB()等价,但后者被推荐。
下标运算符(C36–C38)
对应语言参考的 Subscript operator 章节:
- C36
__subscript读写路由:__subscript与property概念类似,但作用于结构体实例本身(而非成员)且接受参数;[]读取调用get、写入调用set。测试 struct-subscript-op-functional.slang 验证x[i] = v写入与float v = x[i]读取; - C37 多重重载:允许定义多个签名不同的
__subscript,重载决议与函数调用一致; - C38 零参数下标:
__subscript () -> float合法。语言参考示例展示了x[]、x[id.y]、x[id.x, id.y]三种用法(0/1/2 参数)。测试 struct-subscript-multi-param.slang 验证按参数数量分派。
函数调用运算符(C39–C40)
对应语言参考的 Function call operator 章节:
- C39 对象即函数:
operator ()允许对结构体实例应用参数,使其像函数一样被调用; - C40 多重重载:签名不同的多个
operator ()按重载决议选择。
测试 struct-call-op-functional.slang 验证obj()、obj(id.y)、obj(id.z, id.z*2)三种调用形式。
内存布局(C41–C43)
对应语言参考的 Memory Layout 章节,定义了三种布局:
- C41 自然布局顺序:非静态数据成员按声明顺序发射。测试 struct-natural-layout-emission.slang 用混合字段
int x; float y; int z;验证:HLSL 中三个字段按序出现,SPIR-V 中OpMemberName索引 0/1/2 及OpTypeStruct %int %float %int均保持声明顺序(测试以-O0固定,避免优化消除结构体); - C42 无尾填充:自然布局下结构体大小不进行尾填充(C 风格布局才将大小向上取整到对齐值);
- C43 D3D 常量缓冲布局:最小对齐为 16,且遵循"improper straddle"规则——数据成员跨越 16 字节边界且偏移未按 16 对齐时,偏移向上取整到下一个 16 的倍数。
自然布局的完整算法(摘自 types-struct.md):
- 初始化
size = 0,alignment = 1; - 对结构体的每个字段
f:alignment更新为alignment与f对齐值的最大值;- 将
size取为不小于size的alignment的最小倍数; - 字段
f的偏移设为size; - 将
f的大小加到size。
完成后size与alignment即结构体的大小与对齐。语言参考特别注明:大多数目标平台并不直接使用自然布局,但它为其他布局提供了基线——任何布局都必须保证对齐不小于自然布局。
This类型关键字(C44)
- C44
This指代外层结构体:在结构体体内,This指代外层结构体类型自身。测试 struct-This-type-keyword.slang 中,成员函数This withValue(int v)同时把This用作返回类型与局部变量声明类型。在接口体内,This指代符合该接口的具体类型(即this的类型)。
测试方法论:指令、目标与断言机制
所有测试文件均为带//TEST指令注释的.slang源文件,由 Slang 测试框架驱动。从测试文件可以总结出三类核心指令(示例取自 struct-basic-fields-functional.slang):
//TEST:COMPARE_COMPUTE(filecheck-buffer=CHECK):-cpu -output-using-type //TEST:SIMPLE(filecheck=HLSL):-target hlsl -entry computeMain -stage compute //TEST:SIMPLE(filecheck=GLSL):-target glsl -entry computeMain -stage compute //TEST:SIMPLE(filecheck=SPV):-target spirv-asm -entry main -stage compute -O0 -g2 //DIAGNOSTIC_TEST:SIMPLE(filecheck=CHECK):COMPARE_COMPUTE(功能测试):在-cpu目标上解释执行着色器,通过//TEST_INPUT指定的RWStructuredBuffer输出缓冲区与// CHECK: 3形式的逐值断言比对运行结果;SIMPLE(发射测试):对hlsl、glsl、spirv-asm、metal、wgsl、cuda、cpp等目标执行代码发射,用// HLSL: struct Point_{{[0-9_]*}}形式的 filecheck 正则断言发射产物结构。测试包对不同后端采用不同策略:SPIR-V 发射测试常附加-O0(甚至-O0 -g2)防止优化消除待断言的结构体;DIAGNOSTIC_TEST(负面测试):断言编译错误输出,例如struct-mutating-rejected-without-attribute.slang中的// CHECK-DAG: E30011。
每个测试文件头部的//META:注释记录了生成信息:doc_ref(对应的规范锚点,如docs/language-reference/types-struct.md#description)、doc_section_digest(规范章节摘要,用于漂移检测)、intent(functional / negative / emit)、pipeline_stage(runtime / check / emit)。这套元数据使规范与测试的双向追踪成为可能——规范改动会触发摘要变化,进而暴露测试漂移。
功能覆盖矩阵:声明到测试的完整映射
以下完整覆盖矩阵(摘自 README.md)展示了每条声明对应的测试文件与规范锚点:
| 声明 | 意图 | 规范锚点 | 测试文件 |
|---|---|---|---|
| C1 — 无体声明可在完整定义前用于类型表达式 | functional | Description | struct-no-body-decl.slang |
C3 — 具名结构体struct Foo { ... }以Foo为类型名,字段经点访问 | functional | Description | struct-basic-fields-functional.slang |
C4 — 匿名结构体struct { int a; } obj;允许点访问字段 | functional | Description | struct-anonymous.slang |
| C6 — 非静态字段经实例点运算符读写 | functional | Description | struct-basic-fields-functional.slang、struct-basic-fields-emission.slang |
| C7 — 静态数据成员分配于全局存储;HLSL 中发射为模块级静态变量 | functional | Description | struct-static-member-functional.slang |
C8 — 带默认初始化器的static const成员经类型名访问并折叠为常量 | functional | Description | struct-static-const-initializer.slang |
| C9 — 嵌套类型经外层结构名点访问 | functional | Description | struct-nested-type.slang |
| C10 — 非静态数据成员顺序分配;HLSL 按声明顺序发射 | functional | Natural Layout | struct-natural-layout-emission.slang |
| C11 — 实现接口的结构体可传入以该接口为约束的泛型 | functional | Description | struct-interface-conformance.slang |
C12 —public成员可访问;结构体声明为public以允许public成员 | functional | Description | struct-access-visibility-default-internal.slang |
| C13/C14 — 实例字段按对象分配;初始化列表在构造时设置字段值 | functional | Objects | struct-aggregate-init.slang |
C15 — 用户__init()在实例化时执行;字段为构造函数赋值结果 | functional | Constructors | struct-constructor-no-param.slang |
C16/C17 — 多个__init构造函数;按参数个数重载决议 | functional | Constructors | struct-constructor-overload.slang |
C19 —__init内对const成员赋值是编译错误(E30011) | negative | Constructors | struct-constructor-no-const-member.slang |
| C20/C21 — 聚合初始化按序填充字段;未指定的尾随字段默认初始化 | functional | Constructors | struct-aggregate-init.slang |
| C22 — 初始化列表未提供字段时,默认初始化器提供其值 | functional | Description | struct-default-field-initializer.slang |
C23 — 构造函数可在体内显式赋值覆盖非const字段的默认初始化器 | functional | Description | struct-default-initializer-constructor-override.slang |
| C24/C25 — 静态成员函数无对象调用;HLSL 中发射为自由函数 | functional | Static Member Functions | struct-static-member-function.slang |
C26 —this.member与裸member在非静态成员函数内访问同一字段 | functional | Non-static Member Functions | struct-this-implicit-optional.slang |
C27 — 无[mutating]的成员函数读取字段;返回值正确 | functional | Non-static Member Functions | struct-member-function-readonly.slang |
C28 —[mutating]成员函数写入非静态字段;变异在实例上可观察 | functional | Non-static Member Functions | struct-mutating-member-function.slang |
C29 — 非[mutating]函数内写非静态成员报错 E30011(非左值) | negative | Non-static Member Functions | struct-mutating-rejected-without-attribute.slang |
C30 — 带get/set的property:读取走get、写入走set(隐式newValue) | functional | Properties | struct-property-get-set.slang |
C31 — 只有get的property为只读;getter 在 HLSL 中正确发射 | functional | Properties | struct-property-readonly.slang |
C33 —set访问器显式参数名在 set 体内使用而非newValue | functional | Properties | struct-property-explicit-param.slang |
C34 — 静态/非静态成员、嵌套类型与成员函数统一经.访问 | functional | Accessing Members and Nested Types | struct-dot-member-access.slang |
C36 — 带get/set的__subscript使[]读写可用 | functional | Subscript Operator | struct-subscript-op-functional.slang |
C37/C38 — 多个__subscript重载(含零参数形式);按元数分派 | functional | Subscript Operator | struct-subscript-multi-param.slang |
C39/C40 —operator ()使对象可被当作函数调用;多重重载按参数个数决议 | functional | Function Call Operator | struct-call-op-functional.slang |
| C41 — 自然布局:非静态字段按声明顺序发射到 HLSL 结构体 | functional | Natural Layout | struct-natural-layout-emission.slang |
C44 — 结构体体内This指代外层结构体类型;可用作成员函数返回类型 | functional | D3D Constant Buffer Layout | struct-This-type-keyword.slang |
泛型结构体<T>在 int 与 float 上实例化;字段以 T 为类型 | functional | Syntax | struct-generic.slang |
| 文档完整示例(静态/非静态成员、函数、嵌套结构体)发射出合法 HLSL | functional | Accessing Members and Nested Types | struct-members-full-example-emission.slang |
其中struct-members-full-example-emission.slang值得特别关注:它原样复刻了语言参考 "Accessing Members and Nested Types" 一节中的完整TestStruct示例(含普通字段、静态字段、静态常量字段、嵌套结构体、只读成员函数、[mutating]成员函数与两个静态成员函数),并验证其在全部后端可发射。这保证了文档示例本身始终与实现保持同步。
未被覆盖的声明:边界与原因
测试包如实记录了 8 项无法(或不宜)通过当前测试框架验证的声明(README.md 的 Untested claims 一节):
| 声明 | 原因分类 | 说明 |
|---|---|---|
C2 —extern struct链接期声明 | needs-multi-file-test | 需要两个.slang文件模块链接与-module输出,单文件//TEST指令无法测试 |
C5 —export struct导出类型别名 | needs-multi-file-test | 需要多文件模块编译与链接 |
| C18 — 构造函数未初始化的成员处于未定义状态 | (unclassified) | 未定义行为无法断言;编译器允许任意值,无可测试声明 |
| C7 — CUDA 目标上静态数据成员作为具名模块级变量 | implementation-detail | CUDA 将静态成员直接折叠为内核体内的int(5),无名变量;SPIR-V 在-O0 -g2下会发射%b = OpVariable %_ptr_Private_int Private,测试已钉住该行为 |
C32 —set参数可选、省略时提供隐式newValue | out-of-bundle | 已被使用隐式newValue的 struct-property-get-set.slang 覆盖 |
C35 —::已弃用,编译器应警告或接受 | deprecated | 文档仅警告弃用,未规定警告/接受的具体行为,无可测试的错误码 |
| C42 — 自然布局无尾填充、C 风格布局向上取整 | needs-unit-test | 从 slangc CLI 不可观察,需反射 API 查询结构体大小与对齐 |
| C43 — D3D 常量缓冲布局最小对齐 16 与 improper-straddle 规则 | needs-unit-test | 无反射的 CLI 下不可观察,测试着色器中缺少 cbuffer 布局查询或sizeof运算符 |
这组记录的价值在于:它显式区分"规范写了但无法测"与"规范写了但编译器行为与之不同",并为后续测试基建(多文件模块测试、反射 API 单测)指明了演进方向。
文档缺口观察:规范与实现的分歧
测试包最具工程价值的产出之一是"Doc gaps observed"——测试过程中发现的规范文档问题:
- drift-from-source(C1 无体声明的二义性):文档将无体声明描述为"指定结构类型的存在……使其可在没有成员声明的情况下用于类型表达式",这读起来像"前向声明,后续 with-members 声明补全"。但编译器不会合并二者:
struct ForwardDeclared;后跟struct ForwardDeclared { int x; }会在 hlsl、glsl、spirv-asm、metal、wgsl 五个目标上一致地报告error[E30200] conflicting declaration与error[E39999] ambiguous reference。而单独使用无体声明(struct Fwd;后直接Fwd f;)则可以编译。该分歧被记录为_meta/findings/types-struct-no-body-forward-decl-conflicting-declaration.yaml,并建议文档明确:无体声明是否可由同模块内同名的 with-members 声明补全;若不可,应在 no-body 条目中说明,并给出可接受的用法(声明后在类型表达式中使用,定义由另一模块经extern struct提供); - missing-surface(
This关键字缺少可引用锚点):文档末尾的 "This Type" 小节没有显式标题锚点(其前一个主标题是# Memory Layout),导致无法用稳定的#this-type锚点引用,lint 工具也无法校验doc_ref锚点片段。建议为 "This Type" 小节添加显式{#this-type}锚点; - missing-example(未定义状态缺少最小示例):文档 Remark 1 称"无用户构造函数的类在无初始化列表时实例化为未定义状态",但未给出"外层结构体无构造函数、内层字段类型自带
__init"的独立最小示例。建议补一段隔离该场景的代码片段并加注释说明。
这些观察直接回馈给语言参考文档维护者,形成了"测试 → 发现问题 → 修正规范"的闭环,正是该测试包作为规范质量基础设施的核心价值。
结语:把语言规范"钉"在可执行测试上
types-struct一致性测试包展示了 Slang 项目管理语言特性的工程范式:以 语言参考文档 为唯一规范源,把每一句"应当/必须/会"拆解为带编号的声明(C1–C44),逐条配套功能测试、发射测试或负面测试,横跨 CPU 解释执行与 HLSL/GLSL/SPIR-V/Metal/WGSL/CUDA/CPP 全部文本发射后端;对无法覆盖的声明如实标注原因,对测试中发现的规范缺口按"漂移/缺失表面/缺失示例"分类回写文档。如果你正在为 Slang 的某个语言特性编写一致性测试,或希望理解struct的完整语义边界,这个目录(docs/generated/tests/conformance/types-struct/)连同其规范锚点 types-struct.md 是最值得精读的第一手资料。
【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考