Skip to content

Commit

Permalink
Dont look for properties of Object and Function type when looking to …
Browse files Browse the repository at this point in the history
…resolve named import from module with `export=` (#37964)

* Add tests

* Dont look at object or function type when looking for members of `export=` type to be resolved by named imports
Fixes #37165

* Create separate cache when skipping function and object property augmentation

* Lookup in both cache if not skipObjectFunctionPropertyAugment
  • Loading branch information
sheetalkamat authored Nov 3, 2020
1 parent b00870e commit 9c60d5a
Show file tree
Hide file tree
Showing 11 changed files with 291 additions and 12 deletions.
26 changes: 15 additions & 11 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2689,7 +2689,7 @@ namespace ts {
let symbolFromVariable: Symbol | undefined;
// First check if module was specified with "export=". If so, get the member from the resolved type
if (moduleSymbol && moduleSymbol.exports && moduleSymbol.exports.get(InternalSymbolName.ExportEquals)) {
symbolFromVariable = getPropertyOfType(getTypeOfSymbol(targetSymbol), name.escapedText);
symbolFromVariable = getPropertyOfType(getTypeOfSymbol(targetSymbol), name.escapedText, /*skipObjectFunctionPropertyAugment*/ true);
}
else {
symbolFromVariable = getPropertyOfVariable(targetSymbol, name.escapedText);
Expand Down Expand Up @@ -11147,7 +11147,7 @@ namespace ts {
return getReducedType(getApparentType(getReducedType(type)));
}

function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: __String): Symbol | undefined {
function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: __String, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined {
let singleProp: Symbol | undefined;
let propSet: ESMap<SymbolId, Symbol> | undefined;
let indexTypes: Type[] | undefined;
Expand All @@ -11159,7 +11159,7 @@ namespace ts {
for (const current of containingType.types) {
const type = getApparentType(current);
if (!(type === errorType || type.flags & TypeFlags.Never)) {
const prop = getPropertyOfType(type, name);
const prop = getPropertyOfType(type, name, skipObjectFunctionPropertyAugment);
const modifiers = prop ? getDeclarationModifierFlagsFromSymbol(prop) : 0;
if (prop) {
if (isUnion) {
Expand Down Expand Up @@ -11276,20 +11276,23 @@ namespace ts {
// constituents, in which case the isPartial flag is set when the containing type is union type. We need
// these partial properties when identifying discriminant properties, but otherwise they are filtered out
// and do not appear to be present in the union type.
function getUnionOrIntersectionProperty(type: UnionOrIntersectionType, name: __String): Symbol | undefined {
const properties = type.propertyCache || (type.propertyCache = createSymbolTable());
let property = properties.get(name);
function getUnionOrIntersectionProperty(type: UnionOrIntersectionType, name: __String, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined {
let property = type.propertyCacheWithoutObjectFunctionPropertyAugment?.get(name) ||
!skipObjectFunctionPropertyAugment ? type.propertyCache?.get(name) : undefined;
if (!property) {
property = createUnionOrIntersectionProperty(type, name);
property = createUnionOrIntersectionProperty(type, name, skipObjectFunctionPropertyAugment);
if (property) {
const properties = skipObjectFunctionPropertyAugment ?
type.propertyCacheWithoutObjectFunctionPropertyAugment ||= createSymbolTable() :
type.propertyCache ||= createSymbolTable();
properties.set(name, property);
}
}
return property;
}

function getPropertyOfUnionOrIntersectionType(type: UnionOrIntersectionType, name: __String): Symbol | undefined {
const property = getUnionOrIntersectionProperty(type, name);
function getPropertyOfUnionOrIntersectionType(type: UnionOrIntersectionType, name: __String, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined {
const property = getUnionOrIntersectionProperty(type, name, skipObjectFunctionPropertyAugment);
// We need to filter out partial properties in union types
return property && !(getCheckFlags(property) & CheckFlags.ReadPartial) ? property : undefined;
}
Expand Down Expand Up @@ -11367,14 +11370,15 @@ namespace ts {
* @param type a type to look up property from
* @param name a name of property to look up in a given type
*/
function getPropertyOfType(type: Type, name: __String): Symbol | undefined {
function getPropertyOfType(type: Type, name: __String, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined {
type = getReducedApparentType(type);
if (type.flags & TypeFlags.Object) {
const resolved = resolveStructuredTypeMembers(<ObjectType>type);
const symbol = resolved.members.get(name);
if (symbol && symbolIsValue(symbol)) {
return symbol;
}
if (skipObjectFunctionPropertyAugment) return undefined;
const functionType = resolved === anyFunctionType ? globalFunctionType :
resolved.callSignatures.length ? globalCallableFunctionType :
resolved.constructSignatures.length ? globalNewableFunctionType :
Expand All @@ -11388,7 +11392,7 @@ namespace ts {
return getPropertyOfObjectType(globalObjectType, name);
}
if (type.flags & TypeFlags.UnionOrIntersection) {
return getPropertyOfUnionOrIntersectionType(<UnionOrIntersectionType>type, name);
return getPropertyOfUnionOrIntersectionType(<UnionOrIntersectionType>type, name, skipObjectFunctionPropertyAugment);
}
return undefined;
}
Expand Down
4 changes: 3 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5193,7 +5193,9 @@ namespace ts {
/* @internal */
objectFlags: ObjectFlags;
/* @internal */
propertyCache: SymbolTable; // Cache of resolved properties
propertyCache?: SymbolTable; // Cache of resolved properties
/* @internal */
propertyCacheWithoutObjectFunctionPropertyAugment?: SymbolTable; // Cache of resolved properties that does not augment function or object type properties
/* @internal */
resolvedProperties: Symbol[];
/* @internal */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
tests/cases/compiler/bar.d.ts(1,10): error TS2305: Module '"./foo"' has no exported member 'Bar'.


==== tests/cases/compiler/foo.d.ts (0 errors) ====
export = Foo;
export as namespace Foo;

declare namespace Foo {
function foo();
}

declare global {
namespace Bar { }
}

==== tests/cases/compiler/bar.d.ts (1 errors) ====
import { Bar } from './foo';
~~~
!!! error TS2305: Module '"./foo"' has no exported member 'Bar'.
export = Bar;
export as namespace Bar;
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
=== tests/cases/compiler/foo.d.ts ===
export = Foo;
>Foo : Symbol(Foo, Decl(foo.d.ts, 1, 24))

export as namespace Foo;
>Foo : Symbol(Foo, Decl(foo.d.ts, 0, 13))

declare namespace Foo {
>Foo : Symbol(Foo, Decl(foo.d.ts, 1, 24))

function foo();
>foo : Symbol(foo, Decl(foo.d.ts, 3, 23))
}

declare global {
>global : Symbol(global, Decl(foo.d.ts, 5, 1))

namespace Bar { }
>Bar : Symbol(Bar, Decl(foo.d.ts, 7, 16))
}

=== tests/cases/compiler/bar.d.ts ===
import { Bar } from './foo';
>Bar : Symbol(Bar, Decl(bar.d.ts, 0, 8))

export = Bar;
>Bar : Symbol(Bar, Decl(bar.d.ts, 0, 8))

export as namespace Bar;
>Bar : Symbol(Bar, Decl(bar.d.ts, 1, 13))

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
=== tests/cases/compiler/foo.d.ts ===
export = Foo;
>Foo : typeof Foo

export as namespace Foo;
>Foo : typeof Foo

declare namespace Foo {
>Foo : typeof Foo

function foo();
>foo : () => any
}

declare global {
>global : any

namespace Bar { }
}

=== tests/cases/compiler/bar.d.ts ===
import { Bar } from './foo';
>Bar : any

export = Bar;
>Bar : any

export as namespace Bar;
>Bar : any

37 changes: 37 additions & 0 deletions tests/baselines/reference/namedImportNonExistentName.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
tests/cases/compiler/bar.ts(1,10): error TS2305: Module '"./foo"' has no exported member 'Bar'.
tests/cases/compiler/bar.ts(1,15): error TS2305: Module '"./foo"' has no exported member 'toString'.
tests/cases/compiler/bar.ts(3,10): error TS2305: Module '"./foo2"' has no exported member 'a'.
tests/cases/compiler/bar.ts(3,13): error TS2305: Module '"./foo2"' has no exported member 'b'.
tests/cases/compiler/bar.ts(3,19): error TS2305: Module '"./foo2"' has no exported member 'd'.
tests/cases/compiler/bar.ts(3,22): error TS2305: Module '"./foo2"' has no exported member 'toString'.


==== tests/cases/compiler/foo.d.ts (0 errors) ====
export = Foo;
export as namespace Foo;

declare namespace Foo {
function foo();
}

==== tests/cases/compiler/foo2.ts (0 errors) ====
let x: { a: string; c: string; } | { b: number; c: number; };
export = x

==== tests/cases/compiler/bar.ts (6 errors) ====
import { Bar, toString, foo } from './foo';
~~~
!!! error TS2305: Module '"./foo"' has no exported member 'Bar'.
~~~~~~~~
!!! error TS2305: Module '"./foo"' has no exported member 'toString'.
foo();
import { a, b, c, d, toString as foo2String } from './foo2';
~
!!! error TS2305: Module '"./foo2"' has no exported member 'a'.
~
!!! error TS2305: Module '"./foo2"' has no exported member 'b'.
~
!!! error TS2305: Module '"./foo2"' has no exported member 'd'.
~~~~~~~~
!!! error TS2305: Module '"./foo2"' has no exported member 'toString'.
c;
31 changes: 31 additions & 0 deletions tests/baselines/reference/namedImportNonExistentName.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//// [tests/cases/compiler/namedImportNonExistentName.ts] ////

//// [foo.d.ts]
export = Foo;
export as namespace Foo;

declare namespace Foo {
function foo();
}

//// [foo2.ts]
let x: { a: string; c: string; } | { b: number; c: number; };
export = x

//// [bar.ts]
import { Bar, toString, foo } from './foo';
foo();
import { a, b, c, d, toString as foo2String } from './foo2';
c;

//// [foo2.js]
"use strict";
var x;
module.exports = x;
//// [bar.js]
"use strict";
exports.__esModule = true;
var foo_1 = require("./foo");
foo_1.foo();
var foo2_1 = require("./foo2");
foo2_1.c;
44 changes: 44 additions & 0 deletions tests/baselines/reference/namedImportNonExistentName.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
=== tests/cases/compiler/foo.d.ts ===
export = Foo;
>Foo : Symbol(Foo, Decl(foo.d.ts, 1, 24))

export as namespace Foo;
>Foo : Symbol(Foo, Decl(foo.d.ts, 0, 13))

declare namespace Foo {
>Foo : Symbol(Foo, Decl(foo.d.ts, 1, 24))

function foo();
>foo : Symbol(foo, Decl(foo.d.ts, 3, 23))
}

=== tests/cases/compiler/foo2.ts ===
let x: { a: string; c: string; } | { b: number; c: number; };
>x : Symbol(x, Decl(foo2.ts, 0, 3))
>a : Symbol(a, Decl(foo2.ts, 0, 8))
>c : Symbol(c, Decl(foo2.ts, 0, 19))
>b : Symbol(b, Decl(foo2.ts, 0, 36))
>c : Symbol(c, Decl(foo2.ts, 0, 47))

export = x
>x : Symbol(x, Decl(foo2.ts, 0, 3))

=== tests/cases/compiler/bar.ts ===
import { Bar, toString, foo } from './foo';
>Bar : Symbol(Bar, Decl(bar.ts, 0, 8))
>toString : Symbol(toString, Decl(bar.ts, 0, 13))
>foo : Symbol(foo, Decl(bar.ts, 0, 23))

foo();
>foo : Symbol(foo, Decl(bar.ts, 0, 23))

import { a, b, c, d, toString as foo2String } from './foo2';
>a : Symbol(a, Decl(bar.ts, 2, 8))
>b : Symbol(b, Decl(bar.ts, 2, 11))
>c : Symbol(c, Decl(bar.ts, 2, 14))
>d : Symbol(d, Decl(bar.ts, 2, 17))
>foo2String : Symbol(foo2String, Decl(bar.ts, 2, 20))

c;
>c : Symbol(c, Decl(bar.ts, 2, 14))

46 changes: 46 additions & 0 deletions tests/baselines/reference/namedImportNonExistentName.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
=== tests/cases/compiler/foo.d.ts ===
export = Foo;
>Foo : typeof Foo

export as namespace Foo;
>Foo : typeof Foo

declare namespace Foo {
>Foo : typeof Foo

function foo();
>foo : () => any
}

=== tests/cases/compiler/foo2.ts ===
let x: { a: string; c: string; } | { b: number; c: number; };
>x : { a: string; c: string; } | { b: number; c: number; }
>a : string
>c : string
>b : number
>c : number

export = x
>x : { a: string; c: string; } | { b: number; c: number; }

=== tests/cases/compiler/bar.ts ===
import { Bar, toString, foo } from './foo';
>Bar : any
>toString : any
>foo : () => any

foo();
>foo() : any
>foo : () => any

import { a, b, c, d, toString as foo2String } from './foo2';
>a : any
>b : any
>c : string | number
>d : any
>toString : any
>foo2String : any

c;
>c : string | number

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// @filename: foo.d.ts
export = Foo;
export as namespace Foo;

declare namespace Foo {
function foo();
}

declare global {
namespace Bar { }
}

// @filename: bar.d.ts
import { Bar } from './foo';
export = Bar;
export as namespace Bar;
17 changes: 17 additions & 0 deletions tests/cases/compiler/namedImportNonExistentName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// @filename: foo.d.ts
export = Foo;
export as namespace Foo;

declare namespace Foo {
function foo();
}

// @filename: foo2.ts
let x: { a: string; c: string; } | { b: number; c: number; };
export = x

// @filename: bar.ts
import { Bar, toString, foo } from './foo';
foo();
import { a, b, c, d, toString as foo2String } from './foo2';
c;

0 comments on commit 9c60d5a

Please sign in to comment.