Compare commits

...

1 Commits

Author SHA1 Message Date
Zach Arend c270c88dfd fix(compiler-cli): add support for partially evaluating types
Add support to the partial evaluator for evaluating literal types and
tuples.

resolves #41338
2021-04-21 10:15:39 -07:00
4 changed files with 96 additions and 1 deletions
@@ -127,6 +127,10 @@ class TraceDynamicValueVisitor implements DynamicValueVisitor<ts.DiagnosticRelat
return [makeRelatedInformation(value.node, 'Unknown reference.')];
}
visitDynamicType(value: DynamicValue): ts.DiagnosticRelatedInformation[] {
return [makeRelatedInformation(value.node, 'Dynamic type.')];
}
visitUnsupportedSyntax(value: DynamicValue): ts.DiagnosticRelatedInformation[] {
return [makeRelatedInformation(value.node, 'This syntax is not supported.')];
}
@@ -61,6 +61,11 @@ export const enum DynamicValueReason {
*/
COMPLEX_FUNCTION_CALL,
/**
* A type that could not be statically evaluated.
*/
DYNAMIC_TYPE,
/**
* A value could not be determined statically for any reason other the above.
*/
@@ -104,6 +109,10 @@ export class DynamicValue<R = unknown> {
return new DynamicValue(node, fn, DynamicValueReason.COMPLEX_FUNCTION_CALL);
}
static fromDynamicType(node: ts.TypeNode): DynamicValue {
return new DynamicValue(node, undefined, DynamicValueReason.DYNAMIC_TYPE);
}
static fromUnknown(node: ts.Node): DynamicValue {
return new DynamicValue(node, undefined, DynamicValueReason.UNKNOWN);
}
@@ -136,6 +145,10 @@ export class DynamicValue<R = unknown> {
return this.code === DynamicValueReason.COMPLEX_FUNCTION_CALL;
}
isFromDynamicType(this: DynamicValue<R>): this is DynamicValue {
return this.code === DynamicValueReason.DYNAMIC_TYPE;
}
isFromUnknown(this: DynamicValue<R>): this is DynamicValue {
return this.code === DynamicValueReason.UNKNOWN;
}
@@ -158,6 +171,8 @@ export class DynamicValue<R = unknown> {
case DynamicValueReason.COMPLEX_FUNCTION_CALL:
return visitor.visitComplexFunctionCall(
this as unknown as DynamicValue<FunctionDefinition>);
case DynamicValueReason.DYNAMIC_TYPE:
return visitor.visitDynamicType(this);
case DynamicValueReason.UNKNOWN:
return visitor.visitUnknown(this);
}
@@ -172,5 +187,6 @@ export interface DynamicValueVisitor<R> {
visitUnknownIdentifier(value: DynamicValue): R;
visitInvalidExpressionType(value: DynamicValue): R;
visitComplexFunctionCall(value: DynamicValue<FunctionDefinition>): R;
visitDynamicType(value: DynamicValue): R;
visitUnknown(value: DynamicValue): R;
}
@@ -276,12 +276,19 @@ export class StaticInterpreter {
return this.getReference(node, context);
}
}
private visitVariableDeclaration(node: ts.VariableDeclaration, context: Context): ResolvedValue {
const value = this.host.getVariableValue(node);
if (value !== null) {
return this.visitExpression(value, context);
} else if (isVariableDeclarationDeclared(node)) {
if (node.type !== undefined) {
const evaluatedType = this.visitType(node.type, context);
if (!(evaluatedType instanceof DynamicValue)) {
return evaluatedType;
}
}
// fallback to returning a reference if this variable declaration does not have a type or we
// are unable to evaluate its type
return this.getReference(node, context);
} else {
return undefined;
@@ -681,6 +688,45 @@ export class StaticInterpreter {
private getReference<T extends DeclarationNode>(node: T, context: Context): Reference<T> {
return new Reference(node, owningModule(context));
}
private visitType(node: ts.TypeNode, context: Context): ResolvedValue {
if (ts.isLiteralTypeNode(node)) {
return this.visitLiteralType(node, context);
} else if (ts.isTupleTypeNode(node)) {
return this.visitTupleType(node, context);
} else if (ts.isNamedTupleMember(node)) {
return this.visitType(node.type, context);
}
return DynamicValue.fromDynamicType(node);
}
private visitLiteralType(node: ts.LiteralTypeNode, context: Context): ResolvedValue {
if (ts.isLiteralExpression(node.literal)) {
return this.visitExpression(node.literal, context);
}
switch (node.literal.kind) {
case ts.SyntaxKind.TrueKeyword:
return true;
case ts.SyntaxKind.FalseKeyword:
return false;
case ts.SyntaxKind.NullKeyword:
return null;
case ts.SyntaxKind.PrefixUnaryExpression:
return this.visitExpression(node.literal, context);
}
}
private visitTupleType(node: ts.TupleTypeNode, context: Context): ResolvedValueArray {
const res: ResolvedValueArray = [];
for (const elem of node.elements) {
res.push(this.visitType(elem, context));
}
return res;
}
}
function isFunctionOrMethodReference(ref: Reference<ts.Node>):
@@ -357,6 +357,35 @@ runInEachFileSystem(() => {
expect(value.reason.node.getText()).toEqual('window: any');
});
it('supports declarations of primitive constant types', () => {
expect(evaluate(`declare const x: 'foo';`, `x`)).toEqual('foo');
expect(evaluate(`declare const x: 42;`, `x`)).toEqual(42);
expect(evaluate(`declare const x: null;`, `x`)).toEqual(null);
expect(evaluate(`declare const x: true;`, `x`)).toEqual(true);
});
it('supports declarations of tuples', () => {
expect(evaluate(`declare const x: ['foo', 42, null, true];`, `x`)).toEqual([
'foo', 42, null, true
]);
expect(evaluate(`declare const x: ['bar'];`, `[...x]`)).toEqual(['bar']);
});
it('evaluates tuple elements it cannot understand to DynamicValue', () => {
const value = evaluate(`declare const x: ['foo', string];`, `x`);
if (!Array.isArray(value)) {
return fail('expected an array');
}
expect(value[0]).toEqual('foo');
if (!(value[1] instanceof DynamicValue)) {
return fail('expected a DynamicValue when evaluating string element');
}
expect(value[1].isFromDynamicType()).toBe(true);
});
it('imports work', () => {
const {program} = makeProgram([
{name: _('/second.ts'), contents: 'export function foo(bar) { return bar; }'},