From 0258db221068f096537540fa0e791a32861c3128 Mon Sep 17 00:00:00 2001 From: Orta Date: Wed, 29 Apr 2020 10:35:33 -0400 Subject: [PATCH] Adds support for looking up past Blocks in expando objects (#38031) * Adds support for looking up past Blocks in expando objects * Adds JS tests to validate the JS parsing also works * Get the top level block expando tests green --- src/compiler/binder.ts | 4 +- src/compiler/utilities.ts | 2 +- .../reference/topLevelBlockExpando.symbols | 100 ++++++++++++++ .../reference/topLevelBlockExpando.types | 123 ++++++++++++++++++ tests/cases/compiler/topLevelBlockExpando.ts | 49 +++++++ 5 files changed, 275 insertions(+), 3 deletions(-) create mode 100644 tests/baselines/reference/topLevelBlockExpando.symbols create mode 100644 tests/baselines/reference/topLevelBlockExpando.types create mode 100644 tests/cases/compiler/topLevelBlockExpando.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 7d3e4af676..3ea8df0a83 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2964,7 +2964,7 @@ namespace ts { function bindSpecialPropertyAssignment(node: BindablePropertyAssignmentExpression) { // Class declarations in Typescript do not allow property declarations - const parentSymbol = lookupSymbolForPropertyAccess(node.left.expression); + const parentSymbol = lookupSymbolForPropertyAccess(node.left.expression, container) || lookupSymbolForPropertyAccess(node.left.expression, blockScopeContainer) ; if (!isInJSFile(node) && !isFunctionSymbol(parentSymbol)) { return; } @@ -3073,7 +3073,7 @@ namespace ts { } function bindPropertyAssignment(name: BindableStaticNameExpression, propertyAccess: BindableStaticAccessExpression, isPrototypeProperty: boolean, containerIsClass: boolean) { - let namespaceSymbol = lookupSymbolForPropertyAccess(name); + let namespaceSymbol = lookupSymbolForPropertyAccess(name, container) || lookupSymbolForPropertyAccess(name, blockScopeContainer); const isToplevel = isTopLevelNamespaceAssignment(propertyAccess); namespaceSymbol = bindPotentiallyMissingNamespaces(namespaceSymbol, propertyAccess.expression, isToplevel, isPrototypeProperty, containerIsClass); bindPotentiallyNewExpandoMemberToNamespace(propertyAccess, namespaceSymbol, isPrototypeProperty); diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 97231acf60..9452910fee 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2016,7 +2016,7 @@ namespace ts { /** * Get the assignment 'initializer' -- the righthand side-- when the initializer is container-like (See getExpandoInitializer). - * We treat the right hand side of assignments with container-like initalizers as declarations. + * We treat the right hand side of assignments with container-like initializers as declarations. */ export function getAssignedExpandoInitializer(node: Node | undefined): Expression | undefined { if (node && node.parent && isBinaryExpression(node.parent) && node.parent.operatorToken.kind === SyntaxKind.EqualsToken) { diff --git a/tests/baselines/reference/topLevelBlockExpando.symbols b/tests/baselines/reference/topLevelBlockExpando.symbols new file mode 100644 index 0000000000..f9f2944cc9 --- /dev/null +++ b/tests/baselines/reference/topLevelBlockExpando.symbols @@ -0,0 +1,100 @@ +=== tests/cases/compiler/check.ts === +// https://github.com/microsoft/TypeScript/issues/31972 + + + +// https://github.com/microsoft/TypeScript/issues/31972 +interface Person { +>Person : Symbol(Person, Decl(check.ts, 0, 0)) + + first: string; +>first : Symbol(Person.first, Decl(check.ts, 5, 18)) + + last: string; +>last : Symbol(Person.last, Decl(check.ts, 6, 16)) +} + +{ + const dice = () => Math.floor(Math.random() * 6); +>dice : Symbol(dice, Decl(check.ts, 11, 7)) +>Math.floor : Symbol(Math.floor, Decl(lib.es5.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>floor : Symbol(Math.floor, Decl(lib.es5.d.ts, --, --)) +>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) + + dice.first = 'Rando'; +>dice.first : Symbol(dice.first, Decl(check.ts, 11, 51)) +>dice : Symbol(dice, Decl(check.ts, 11, 7)) +>first : Symbol(dice.first, Decl(check.ts, 11, 51)) + + dice.last = 'Calrissian'; +>dice.last : Symbol(dice.last, Decl(check.ts, 12, 23)) +>dice : Symbol(dice, Decl(check.ts, 11, 7)) +>last : Symbol(dice.last, Decl(check.ts, 12, 23)) + + const diceP: Person = dice; +>diceP : Symbol(diceP, Decl(check.ts, 14, 7)) +>Person : Symbol(Person, Decl(check.ts, 0, 0)) +>dice : Symbol(dice, Decl(check.ts, 11, 7)) +} + +=== tests/cases/compiler/check.js === +// Creates a type { first:string, last: string } +/** + * @typedef {Object} Human - creates a new type named 'SpecialType' + * @property {string} first - a string property of SpecialType + * @property {string} last - a number property of SpecialType + */ + +/** + * @param {Human} param used as a validation tool + */ +function doHumanThings(param) {} +>doHumanThings : Symbol(doHumanThings, Decl(check.js, 0, 0)) +>param : Symbol(param, Decl(check.js, 10, 23)) + +const dice1 = () => Math.floor(Math.random() * 6); +>dice1 : Symbol(dice1, Decl(check.js, 12, 5), Decl(check.js, 12, 50)) +>Math.floor : Symbol(Math.floor, Decl(lib.es5.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>floor : Symbol(Math.floor, Decl(lib.es5.d.ts, --, --)) +>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) + +// dice1.first = 'Rando'; +dice1.last = 'Calrissian'; +>dice1.last : Symbol(dice1.last, Decl(check.js, 12, 50)) +>dice1 : Symbol(dice1, Decl(check.js, 12, 5), Decl(check.js, 12, 50)) +>last : Symbol(dice1.last, Decl(check.js, 12, 50)) + +// doHumanThings(dice) + +// but inside a block... you can't call a human +{ + const dice2 = () => Math.floor(Math.random() * 6); +>dice2 : Symbol(dice2, Decl(check.js, 20, 7)) +>Math.floor : Symbol(Math.floor, Decl(lib.es5.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>floor : Symbol(Math.floor, Decl(lib.es5.d.ts, --, --)) +>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) + + dice2.first = 'Rando'; +>dice2.first : Symbol(dice2.first, Decl(check.js, 20, 52)) +>dice2 : Symbol(dice2, Decl(check.js, 20, 7)) +>first : Symbol(dice2.first, Decl(check.js, 20, 52)) + + dice2.last = 'Calrissian'; +>dice2.last : Symbol(dice2.last, Decl(check.js, 21, 24)) +>dice2 : Symbol(dice2, Decl(check.js, 20, 7)) +>last : Symbol(dice2.last, Decl(check.js, 21, 24)) + + doHumanThings(dice2) +>doHumanThings : Symbol(doHumanThings, Decl(check.js, 0, 0)) +>dice2 : Symbol(dice2, Decl(check.js, 20, 7)) +} + diff --git a/tests/baselines/reference/topLevelBlockExpando.types b/tests/baselines/reference/topLevelBlockExpando.types new file mode 100644 index 0000000000..a8bd928ef2 --- /dev/null +++ b/tests/baselines/reference/topLevelBlockExpando.types @@ -0,0 +1,123 @@ +=== tests/cases/compiler/check.ts === +// https://github.com/microsoft/TypeScript/issues/31972 + + + +// https://github.com/microsoft/TypeScript/issues/31972 +interface Person { + first: string; +>first : string + + last: string; +>last : string +} + +{ + const dice = () => Math.floor(Math.random() * 6); +>dice : { (): number; first: string; last: string; } +>() => Math.floor(Math.random() * 6) : { (): number; first: string; last: string; } +>Math.floor(Math.random() * 6) : number +>Math.floor : (x: number) => number +>Math : Math +>floor : (x: number) => number +>Math.random() * 6 : number +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number +>6 : 6 + + dice.first = 'Rando'; +>dice.first = 'Rando' : "Rando" +>dice.first : string +>dice : { (): number; first: string; last: string; } +>first : string +>'Rando' : "Rando" + + dice.last = 'Calrissian'; +>dice.last = 'Calrissian' : "Calrissian" +>dice.last : string +>dice : { (): number; first: string; last: string; } +>last : string +>'Calrissian' : "Calrissian" + + const diceP: Person = dice; +>diceP : Person +>dice : { (): number; first: string; last: string; } +} + +=== tests/cases/compiler/check.js === +// Creates a type { first:string, last: string } +/** + * @typedef {Object} Human - creates a new type named 'SpecialType' + * @property {string} first - a string property of SpecialType + * @property {string} last - a number property of SpecialType + */ + +/** + * @param {Human} param used as a validation tool + */ +function doHumanThings(param) {} +>doHumanThings : (param: Human) => void +>param : Human + +const dice1 = () => Math.floor(Math.random() * 6); +>dice1 : { (): number; last: string; } +>() => Math.floor(Math.random() * 6) : { (): number; last: string; } +>Math.floor(Math.random() * 6) : number +>Math.floor : (x: number) => number +>Math : Math +>floor : (x: number) => number +>Math.random() * 6 : number +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number +>6 : 6 + +// dice1.first = 'Rando'; +dice1.last = 'Calrissian'; +>dice1.last = 'Calrissian' : "Calrissian" +>dice1.last : string +>dice1 : { (): number; last: string; } +>last : string +>'Calrissian' : "Calrissian" + +// doHumanThings(dice) + +// but inside a block... you can't call a human +{ + const dice2 = () => Math.floor(Math.random() * 6); +>dice2 : { (): number; first: string; last: string; } +>() => Math.floor(Math.random() * 6) : { (): number; first: string; last: string; } +>Math.floor(Math.random() * 6) : number +>Math.floor : (x: number) => number +>Math : Math +>floor : (x: number) => number +>Math.random() * 6 : number +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number +>6 : 6 + + dice2.first = 'Rando'; +>dice2.first = 'Rando' : "Rando" +>dice2.first : string +>dice2 : { (): number; first: string; last: string; } +>first : string +>'Rando' : "Rando" + + dice2.last = 'Calrissian'; +>dice2.last = 'Calrissian' : "Calrissian" +>dice2.last : string +>dice2 : { (): number; first: string; last: string; } +>last : string +>'Calrissian' : "Calrissian" + + doHumanThings(dice2) +>doHumanThings(dice2) : void +>doHumanThings : (param: Human) => void +>dice2 : { (): number; first: string; last: string; } +} + diff --git a/tests/cases/compiler/topLevelBlockExpando.ts b/tests/cases/compiler/topLevelBlockExpando.ts new file mode 100644 index 0000000000..35f758d040 --- /dev/null +++ b/tests/cases/compiler/topLevelBlockExpando.ts @@ -0,0 +1,49 @@ +// https://github.com/microsoft/TypeScript/issues/31972 + +// @allowJs: true +// @noEmit: true +// @checkJs: true + +// @filename: check.ts + +// https://github.com/microsoft/TypeScript/issues/31972 +interface Person { + first: string; + last: string; +} + +{ + const dice = () => Math.floor(Math.random() * 6); + dice.first = 'Rando'; + dice.last = 'Calrissian'; + const diceP: Person = dice; +} + +// @filename: check.js + +// Creates a type { first:string, last: string } +/** + * @typedef {Object} Human - creates a new type named 'SpecialType' + * @property {string} first - a string property of SpecialType + * @property {string} last - a number property of SpecialType + */ + +/** + * @param {Human} param used as a validation tool + */ +function doHumanThings(param) {} + +const dice1 = () => Math.floor(Math.random() * 6); +// dice1.first = 'Rando'; +dice1.last = 'Calrissian'; + +// doHumanThings(dice) + +// but inside a block... you can't call a human +{ + const dice2 = () => Math.floor(Math.random() * 6); + dice2.first = 'Rando'; + dice2.last = 'Calrissian'; + + doHumanThings(dice2) +}