协程(coroutine)在deepkit compiler中主要用于处理复杂的类型操作,特别是需要延迟执行或者分步骤处理的类型计算。
- 基本结构:
class CompilerProgram {
protected activeCoRoutines: { ops: ReflectionOp[] }[] = [];
protected coRoutines: { ops: ReflectionOp[] }[] = [];
}
主要用途: 协程主要用在以下几种场景:
a) 条件类型(Conditional Types):
protected extractPackStructOfType(node: Node, program: CompilerProgram): void {
case SyntaxKind.ConditionalType: {
const narrowed = node as ConditionalTypeNode;
program.pushCoRoutine();
this.extractPackStructOfType(narrowed.trueType, program);
const trueProgram = program.popCoRoutine();
program.pushCoRoutine();
this.extractPackStructOfType(narrowed.falseType, program);
const falseProgram = program.popCoRoutine();
program.pushOp(ReflectionOp.jumpCondition, trueProgram, falseProgram);
}
}
b) 映射类型(Mapped Types):
case SyntaxKind.MappedType: {
const narrowed = node as MappedTypeNode;
program.pushCoRoutine();
if (narrowed.type) {
this.extractPackStructOfType(narrowed.type, program);
}
const coRoutineIndex = program.popCoRoutine();
program.pushOp(ReflectionOp.mappedType, coRoutineIndex, modifier);
}
工作原理:
pushCoRoutine(): void {
this.pushFrame(true); // 协程有隐式栈帧
this.activeCoRoutines.push({ ops: [] });
}
popCoRoutine(): number {
const coRoutine = this.activeCoRoutines.pop();
if (!coRoutine) throw new Error('No active co routine found');
this.popFrameImplicit();
if (this.mainOffset === 0) {
this.mainOffset = 2; // 添加 JUMP + index 当构建程序时
}
const startIndex = this.mainOffset;
coRoutine.ops.push(ReflectionOp.return);
this.coRoutines.push(coRoutine);
this.mainOffset += coRoutine.ops.length;
return startIndex;
}
使用流程:
- 当遇到需要延迟执行的类型操作时,调用
pushCoRoutine()
- 在协程中收集操作码
- 调用
popCoRoutine()
结束协程并获取其起始索引 - 最终在
buildPackStruct()
中组装所有协程的操作码:
buildPackStruct() {
const ops: ReflectionOp[] = [...this.ops];
if (this.coRoutines.length) {
// 从后向前插入所有协程的操作码
for (let i = this.coRoutines.length - 1; i >= 0; i--) {
ops.unshift(...this.coRoutines[i].ops);
}
}
if (this.mainOffset) {
ops.unshift(ReflectionOp.jump, this.mainOffset);
}
return { ops, stack: this.stack };
}
协程的主要优势是:
- 延迟执行: 可以先收集类型操作,后续再执行
- 独立上下文: 每个协程有自己的操作码序列和栈帧
- 流程控制: 支持条件跳转等复杂控制流程
- 模块化: 将复杂的类型操作分解成小的、独立的单元
需要延迟执行的原因:
- 条件分支处理:
- 条件类型有两个分支(trueType 和 falseType)
- 只有在运行时根据实际类型才能确定走哪个分支
- 因此需要把两个分支的代码都准备好,但延迟到运行时才执行
- 分配式条件类型:
type DistributiveCheck<T> = T extends any[] ? T[number] : T;
type Test = DistributiveCheck<string | number[] | boolean[]>;
// 结果: string | number | boolean
- 需要对联合类型中的每个成员单独进行条件判断
- 协程允许我们把这个处理逻辑封装起来,在运行时重复使用
以条件类型为例:
type CheckNumber<T> = T extends number ? "Yes, it's number" : "No, it's not number";
在编译器中处理这种条件类型的代码:
case SyntaxKind.ConditionalType: {
const narrowed = node as ConditionalTypeNode;
// 分配式条件类型的特殊处理
const distributiveOverIdentifier = isTypeReferenceNode(narrowed.checkType)
&& isIdentifier(narrowed.checkType.typeName)
? narrowed.checkType.typeName : undefined;
if (distributiveOverIdentifier) {
program.pushFrame();
// 添加要分配的原始类型到栈
this.extractPackStructOfType(narrowed.checkType, program);
// 添加变量T作为分配的目标
program.pushVariable(getIdentifierName(distributiveOverIdentifier));
program.pushCoRoutine();
}
// 条件判断的框架
program.pushConditionalFrame();
// 1. 检查类型 (T)
this.extractPackStructOfType(narrowed.checkType, program);
// 2. extends 的目标类型 (number)
this.extractPackStructOfType(narrowed.extendsType, program);
// 3. 添加 extends 操作
program.pushOp(ReflectionOp.extends);
// 真值分支的协程
program.pushCoRoutine();
this.extractPackStructOfType(narrowed.trueType, program);
const trueProgram = program.popCoRoutine();
// 假值分支的协程
program.pushCoRoutine();
this.extractPackStructOfType(narrowed.falseType, program);
const falseProgram = program.popCoRoutine();
// 添加条件跳转
program.pushOp(ReflectionOp.jumpCondition, trueProgram, falseProgram);
}
生成的字节码大致是这样的结构:
[
// 主程序
ReflectionOp.extends, // 检查类型关系
ReflectionOp.jumpCondition, trueIndex, falseIndex, // 条件跳转
// 协程1: true分支的代码
[...trueTypeOps],
ReflectionOp.return,
// 协程2: false分支的代码
[...falseTypeOps],
ReflectionOp.return
]
运行时执行流程:
- 检查类型是否满足extends条件
- 根据检查结果跳转到对应的协程
- 执行相应分支的类型计算
- 返回计算结果