fix(compiler-cli): add support for partially evaluating types (#41661)
Add support to the partial evaluator for evaluating literal types and tuples. resolves #41338 PR Close #41661
This commit is contained in:
parent
45d24d28a6
commit
37a740c659
|
@ -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,22 @@ export const enum DynamicValueReason {
|
|||
*/
|
||||
COMPLEX_FUNCTION_CALL,
|
||||
|
||||
/**
|
||||
* A value that could not be determined because it contains type information that cannot be
|
||||
* statically evaluated. This happens when producing a value from type information, but the value
|
||||
* of the given type cannot be determined statically.
|
||||
*
|
||||
* E.g. evaluating a tuple.
|
||||
*
|
||||
* `declare const foo: [string];`
|
||||
*
|
||||
* Evaluating `foo` gives a DynamicValue wrapped in an array with a reason of DYNAMIC_TYPE. This
|
||||
* is because the static evaluator has a `string` type for the first element of this tuple, and
|
||||
* the value of that string cannot be determined statically. The type `string` permits it to be
|
||||
* 'foo', 'bar' or any arbitrary string, so we evaluate it to a DynamicValue.
|
||||
*/
|
||||
DYNAMIC_TYPE,
|
||||
|
||||
/**
|
||||
* A value could not be determined statically for any reason other the above.
|
||||
*/
|
||||
|
@ -104,6 +120,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 +156,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 +182,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 +198,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,28 @@ 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 the declaration has a literal type that can be statically reduced to a value, resolve to
|
||||
// that value. If not, the historical behavior for variable declarations is to return a
|
||||
// `Reference` to the variable, as the consumer could use it in a context where knowing its
|
||||
// static value is not necessary.
|
||||
//
|
||||
// Arguably, since the value cannot be statically determined, we should return a
|
||||
// `DynamicValue`. This returns a `Reference` because it's the same behavior as before
|
||||
// `visitType` was introduced.
|
||||
//
|
||||
// TODO(zarend): investigate switching to a `DynamicValue` and verify this won't break any
|
||||
// use cases, especially in ngcc
|
||||
if (node.type !== undefined) {
|
||||
const evaluatedType = this.visitType(node.type, context);
|
||||
if (!(evaluatedType instanceof DynamicValue)) {
|
||||
return evaluatedType;
|
||||
}
|
||||
}
|
||||
return this.getReference(node, context);
|
||||
} else {
|
||||
return undefined;
|
||||
|
@ -681,6 +697,28 @@ 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.visitExpression(node.literal, 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 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,30 @@ 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`) as [string, DynamicValue];
|
||||
|
||||
expect(Array.isArray(value)).toBeTrue();
|
||||
expect(value[0]).toEqual('foo');
|
||||
expect(value[1] instanceof DynamicValue).toBeTrue();
|
||||
expect(value[1].isFromDynamicType()).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
it('imports work', () => {
|
||||
const {program} = makeProgram([
|
||||
{name: _('/second.ts'), contents: 'export function foo(bar) { return bar; }'},
|
||||
|
|
Loading…
Reference in New Issue