Add missing MetaProperty stuffs

Add missing parts in the binder and the checker to enable CFA +
narrowing of `import.meta` values.

Fixes #41468
This commit is contained in:
Eli Barzilay 2021-02-18 18:32:17 -05:00
parent 87d10eb055
commit d495957065
13 changed files with 179 additions and 9 deletions

View file

@ -882,11 +882,11 @@ namespace ts {
}
function isNarrowableReference(expr: Expression): boolean {
return expr.kind === SyntaxKind.Identifier || expr.kind === SyntaxKind.PrivateIdentifier || expr.kind === SyntaxKind.ThisKeyword || expr.kind === SyntaxKind.SuperKeyword ||
(isPropertyAccessExpression(expr) || isNonNullExpression(expr) || isParenthesizedExpression(expr)) && isNarrowableReference(expr.expression) ||
isBinaryExpression(expr) && expr.operatorToken.kind === SyntaxKind.CommaToken && isNarrowableReference(expr.right) ||
isElementAccessExpression(expr) && isStringOrNumericLiteralLike(expr.argumentExpression) && isNarrowableReference(expr.expression) ||
isAssignmentExpression(expr) && isNarrowableReference(expr.left);
return isDottedName(expr)
|| (isPropertyAccessExpression(expr) || isNonNullExpression(expr) || isParenthesizedExpression(expr)) && isNarrowableReference(expr.expression)
|| isBinaryExpression(expr) && expr.operatorToken.kind === SyntaxKind.CommaToken && isNarrowableReference(expr.right)
|| isElementAccessExpression(expr) && isStringOrNumericLiteralLike(expr.argumentExpression) && isNarrowableReference(expr.expression)
|| isAssignmentExpression(expr) && isNarrowableReference(expr.left);
}
function containsNarrowableReference(expr: Expression): boolean {
@ -1369,7 +1369,7 @@ namespace ts {
// is potentially an assertion and is therefore included in the control flow.
if (node.kind === SyntaxKind.CallExpression) {
const call = <CallExpression>node;
if (isDottedName(call.expression) && call.expression.kind !== SyntaxKind.SuperKeyword) {
if (call.expression.kind !== SyntaxKind.SuperKeyword && isDottedName(call.expression)) {
currentFlow = createFlowCall(currentFlow, call);
}
}
@ -2542,6 +2542,7 @@ namespace ts {
node.flowNode = currentFlow;
}
break;
case SyntaxKind.MetaProperty:
case SyntaxKind.SuperKeyword:
node.flowNode = currentFlow;
break;

View file

@ -21320,6 +21320,12 @@ namespace ts {
(isBinaryExpression(target) && target.operatorToken.kind === SyntaxKind.CommaToken && isMatchingReference(source, target.right));
}
switch (source.kind) {
case SyntaxKind.MetaProperty:
return target.kind === SyntaxKind.MetaProperty
&& (source as MetaProperty).keywordToken === SyntaxKind.ImportKeyword
&& (target as MetaProperty).keywordToken === SyntaxKind.ImportKeyword
&& (source as MetaProperty).name.escapedText === "meta"
&& (target as MetaProperty).name.escapedText === "meta";
case SyntaxKind.Identifier:
case SyntaxKind.PrivateIdentifier:
return target.kind === SyntaxKind.Identifier && getResolvedSymbol(<Identifier>source) === getResolvedSymbol(<Identifier>target) ||

View file

@ -4836,9 +4836,12 @@ namespace ts {
}
export function isDottedName(node: Expression): boolean {
return node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.ThisKeyword || node.kind === SyntaxKind.SuperKeyword ||
node.kind === SyntaxKind.PropertyAccessExpression && isDottedName((<PropertyAccessExpression>node).expression) ||
node.kind === SyntaxKind.ParenthesizedExpression && isDottedName((<ParenthesizedExpression>node).expression);
return node.kind === SyntaxKind.Identifier
|| node.kind === SyntaxKind.ThisKeyword
|| node.kind === SyntaxKind.SuperKeyword
|| node.kind === SyntaxKind.MetaProperty
|| node.kind === SyntaxKind.PropertyAccessExpression && isDottedName((<PropertyAccessExpression>node).expression)
|| node.kind === SyntaxKind.ParenthesizedExpression && isDottedName((<ParenthesizedExpression>node).expression);
}
export function isPropertyAccessEntityNameExpression(node: Node): node is PropertyAccessEntityNameExpression {

View file

@ -0,0 +1,14 @@
//// [importMetaNarrowing.ts]
declare global { interface ImportMeta {foo?: () => void} };
if (import.meta.foo) {
import.meta.foo();
}
//// [importMetaNarrowing.js]
;
if (import.meta.foo) {
import.meta.foo();
}
export {};

View file

@ -0,0 +1,15 @@
=== tests/cases/conformance/es2019/importMeta/importMetaNarrowing.ts ===
declare global { interface ImportMeta {foo?: () => void} };
>global : Symbol(global, Decl(importMetaNarrowing.ts, 0, 0))
>ImportMeta : Symbol(ImportMeta, Decl(lib.es5.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(importMetaNarrowing.ts, 0, 16))
>foo : Symbol(ImportMeta.foo, Decl(importMetaNarrowing.ts, 0, 39))
if (import.meta.foo) {
>import.meta.foo : Symbol(ImportMeta.foo, Decl(importMetaNarrowing.ts, 0, 39))
>foo : Symbol(ImportMeta.foo, Decl(importMetaNarrowing.ts, 0, 39))
import.meta.foo();
>import.meta.foo : Symbol(ImportMeta.foo, Decl(importMetaNarrowing.ts, 0, 39))
>foo : Symbol(ImportMeta.foo, Decl(importMetaNarrowing.ts, 0, 39))
}

View file

@ -0,0 +1,19 @@
=== tests/cases/conformance/es2019/importMeta/importMetaNarrowing.ts ===
declare global { interface ImportMeta {foo?: () => void} };
>global : any
>foo : (() => void) | undefined
if (import.meta.foo) {
>import.meta.foo : (() => void) | undefined
>import.meta : ImportMeta
>meta : any
>foo : (() => void) | undefined
import.meta.foo();
>import.meta.foo() : void
>import.meta.foo : () => void
>import.meta : ImportMeta
>meta : any
>foo : () => void
}

View file

@ -0,0 +1,14 @@
//// [importMetaNarrowing.ts]
declare global { interface ImportMeta {foo?: () => void} };
if (import.meta.foo) {
import.meta.foo();
}
//// [importMetaNarrowing.js]
;
if (import.meta.foo) {
import.meta.foo();
}
export {};

View file

@ -0,0 +1,15 @@
=== tests/cases/conformance/es2019/importMeta/importMetaNarrowing.ts ===
declare global { interface ImportMeta {foo?: () => void} };
>global : Symbol(global, Decl(importMetaNarrowing.ts, 0, 0))
>ImportMeta : Symbol(ImportMeta, Decl(lib.es5.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(importMetaNarrowing.ts, 0, 16))
>foo : Symbol(ImportMeta.foo, Decl(importMetaNarrowing.ts, 0, 39))
if (import.meta.foo) {
>import.meta.foo : Symbol(ImportMeta.foo, Decl(importMetaNarrowing.ts, 0, 39))
>foo : Symbol(ImportMeta.foo, Decl(importMetaNarrowing.ts, 0, 39))
import.meta.foo();
>import.meta.foo : Symbol(ImportMeta.foo, Decl(importMetaNarrowing.ts, 0, 39))
>foo : Symbol(ImportMeta.foo, Decl(importMetaNarrowing.ts, 0, 39))
}

View file

@ -0,0 +1,19 @@
=== tests/cases/conformance/es2019/importMeta/importMetaNarrowing.ts ===
declare global { interface ImportMeta {foo?: () => void} };
>global : any
>foo : (() => void) | undefined
if (import.meta.foo) {
>import.meta.foo : (() => void) | undefined
>import.meta : ImportMeta
>meta : any
>foo : (() => void) | undefined
import.meta.foo();
>import.meta.foo() : void
>import.meta.foo : () => void
>import.meta : ImportMeta
>meta : any
>foo : () => void
}

View file

@ -0,0 +1,22 @@
//// [importMetaNarrowing.ts]
declare global { interface ImportMeta {foo?: () => void} };
if (import.meta.foo) {
import.meta.foo();
}
//// [importMetaNarrowing.js]
System.register([], function (exports_1, context_1) {
"use strict";
var __moduleName = context_1 && context_1.id;
return {
setters: [],
execute: function () {
;
if (context_1.meta.foo) {
context_1.meta.foo();
}
}
};
});

View file

@ -0,0 +1,15 @@
=== tests/cases/conformance/es2019/importMeta/importMetaNarrowing.ts ===
declare global { interface ImportMeta {foo?: () => void} };
>global : Symbol(global, Decl(importMetaNarrowing.ts, 0, 0))
>ImportMeta : Symbol(ImportMeta, Decl(lib.es5.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(importMetaNarrowing.ts, 0, 16))
>foo : Symbol(ImportMeta.foo, Decl(importMetaNarrowing.ts, 0, 39))
if (import.meta.foo) {
>import.meta.foo : Symbol(ImportMeta.foo, Decl(importMetaNarrowing.ts, 0, 39))
>foo : Symbol(ImportMeta.foo, Decl(importMetaNarrowing.ts, 0, 39))
import.meta.foo();
>import.meta.foo : Symbol(ImportMeta.foo, Decl(importMetaNarrowing.ts, 0, 39))
>foo : Symbol(ImportMeta.foo, Decl(importMetaNarrowing.ts, 0, 39))
}

View file

@ -0,0 +1,19 @@
=== tests/cases/conformance/es2019/importMeta/importMetaNarrowing.ts ===
declare global { interface ImportMeta {foo?: () => void} };
>global : any
>foo : (() => void) | undefined
if (import.meta.foo) {
>import.meta.foo : (() => void) | undefined
>import.meta : ImportMeta
>meta : any
>foo : (() => void) | undefined
import.meta.foo();
>import.meta.foo() : void
>import.meta.foo : () => void
>import.meta : ImportMeta
>meta : any
>foo : () => void
}

View file

@ -0,0 +1,8 @@
// @module: esnext,system,es2020
// @strict: true
declare global { interface ImportMeta {foo?: () => void} };
if (import.meta.foo) {
import.meta.foo();
}