Skip to content

Commit

Permalink
Add testcase with secondary alias that should be picked up, add code …
Browse files Browse the repository at this point in the history
…to ensure locally accessible type aliases are materialized at printback time
  • Loading branch information
weswigham committed Aug 25, 2020
1 parent 1d7b9e1 commit 7d9bb77
Show file tree
Hide file tree
Showing 31 changed files with 276 additions and 65 deletions.
41 changes: 36 additions & 5 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -988,13 +988,19 @@ namespace ts {
typeAliases.add(type, alias);
}

function typeIsOrTriviallyContainsType(t2: Type): boolean {
function typeIsOrTriviallyContainsType(t2: Type, visited: Set<Type> = new Set()): boolean {
if (type === t2) return true;
if (t2.flags & TypeFlags.UnionOrIntersection) return some((t2 as UnionOrIntersectionType).types, typeIsOrTriviallyContainsType);
if (t2.flags & TypeFlags.Index) return typeIsOrTriviallyContainsType((t2 as IndexType).type);
if (t2.flags & TypeFlags.IndexedAccess) return typeIsOrTriviallyContainsType((t2 as IndexedAccessType).objectType) || typeIsOrTriviallyContainsType((t2 as IndexedAccessType).indexType);
if (getObjectFlags(t2) & ObjectFlags.Reference) return some(getTypeArguments(t2 as TypeReference), typeIsOrTriviallyContainsType);
if (visited.has(t2)) return false;
visited.add(t2);
if (t2.flags & TypeFlags.UnionOrIntersection) return some((t2 as UnionOrIntersectionType).types, recur);
if (t2.flags & TypeFlags.Index) return recur((t2 as IndexType).type);
if (t2.flags & TypeFlags.IndexedAccess) return recur((t2 as IndexedAccessType).objectType) || recur((t2 as IndexedAccessType).indexType);
if (getObjectFlags(t2) & ObjectFlags.Reference) return some(getTypeArguments(t2 as TypeReference), recur);
return false;

function recur(t: Type) {
return typeIsOrTriviallyContainsType(t, visited);
}
}
}

Expand Down Expand Up @@ -4451,6 +4457,7 @@ namespace ts {
}

if (!inTypeAlias) {
ensureAliasesDiscovered(context.enclosingDeclaration);
const accessibleAlias = find(typeAliases.get(type) || emptyArray, t => t.kind === AliasKind.Reference && !!(context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope || isTypeSymbolAccessible(t.symbol, context.enclosingDeclaration))) as AliasReference | undefined;
if (accessibleAlias && (!context.aliasStack || context.aliasStack.indexOf(accessibleAlias) === -1)) {
(context.aliasStack ||= []).push(accessibleAlias);
Expand Down Expand Up @@ -7578,6 +7585,30 @@ namespace ts {
}
}

function ensureAliasesDiscovered(node: Node | undefined) {
if (!node) return;
const links = getNodeLinks(node);
if (!(links.flags & NodeCheckFlags.AllTypeAliasesAcessibleAreMaterialized)) {
links.flags |= NodeCheckFlags.AllTypeAliasesAcessibleAreMaterialized;
forEachSymbolTableInScope(node, table => {
forEachEntry(table, s => {
// So... this is kinda an approximation - technically we'd wanna guarantee
// that the whole program is typechecked (and thus all aliases are available)
// however that's a bit much to do - so we do a kind of first order approximation
// and guarantee that at least all type aliases with local references (and therefore are trivially visible to print) have been materialized.
// This notably means that type aliases who are members of namespaces which have local references
// may not have been materialized. We _could_ traverse all local references looking for those, too... but
// then we get into essentially performing the traversal recursively on every symbol table in scope,
// which, if symbol visibility checking has taught us anything, could be a costly endeavor.
const resolved = resolveSymbol(s);
if (resolved.flags & SymbolFlags.TypeAlias) {
getDeclaredTypeOfTypeAlias(resolved);
}
});
});
}
}

/**
* Push an entry on the type resolution stack. If an entry with the given target and the given property name
* is already on the stack, and no entries in between already have a type, then a circularity has occurred.
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4778,6 +4778,7 @@ namespace ts {
ClassWithConstructorReference = 0x01000000, // Class that contains a binding to its constructor inside of the class body.
ConstructorReferenceInClass = 0x02000000, // Binding to a class constructor inside of the class's body.
ContainsClassWithPrivateIdentifiers = 0x04000000, // Marked on all block-scoped containers containing a class with private identifiers.
AllTypeAliasesAcessibleAreMaterialized = 0x08000000, // Indicates that, if the file hasn't been typechecked, it's at least had its imports traversed and checked
}

/* @internal */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//// [tests/cases/compiler/aliasDeclaredAfterFirstUsageUsedByDeclarationEmit.ts] ////

//// [init.ts]
export interface A {
a: true;
}

export interface B {
b: true;
}

export function f(thing?: A | B): A | B { return null as any; };
//// [utils.ts]
import {A, B} from "./init";

export type Either = A | B;
//// [usage.ts]
import {Either} from "./utils";
import {f} from "./init";

export function doThing() {
return f();
}

//// [init.js]
"use strict";
exports.__esModule = true;
exports.f = void 0;
function f(thing) { return null; }
exports.f = f;
;
//// [utils.js]
"use strict";
exports.__esModule = true;
//// [usage.js]
"use strict";
exports.__esModule = true;
exports.doThing = void 0;
var init_1 = require("./init");
function doThing() {
return init_1.f();
}
exports.doThing = doThing;


//// [init.d.ts]
export interface A {
a: true;
}
export interface B {
b: true;
}
export declare function f(thing?: A | B): A | B;
//// [utils.d.ts]
import { A, B } from "./init";
export declare type Either = A | B;
//// [usage.d.ts]
import { Either } from "./utils";
export declare function doThing(): Either;
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
=== tests/cases/compiler/init.ts ===
export interface A {
>A : Symbol(A, Decl(init.ts, 0, 0))

a: true;
>a : Symbol(A.a, Decl(init.ts, 0, 20))
}

export interface B {
>B : Symbol(B, Decl(init.ts, 2, 1))

b: true;
>b : Symbol(B.b, Decl(init.ts, 4, 20))
}

export function f(thing?: A | B): A | B { return null as any; };
>f : Symbol(f, Decl(init.ts, 6, 1))
>thing : Symbol(thing, Decl(init.ts, 8, 18))
>A : Symbol(A, Decl(init.ts, 0, 0))
>B : Symbol(B, Decl(init.ts, 2, 1))
>A : Symbol(A, Decl(init.ts, 0, 0))
>B : Symbol(B, Decl(init.ts, 2, 1))

=== tests/cases/compiler/utils.ts ===
import {A, B} from "./init";
>A : Symbol(A, Decl(utils.ts, 0, 8))
>B : Symbol(B, Decl(utils.ts, 0, 10))

export type Either = A | B;
>Either : Symbol(Either, Decl(utils.ts, 0, 28))
>A : Symbol(A, Decl(utils.ts, 0, 8))
>B : Symbol(B, Decl(utils.ts, 0, 10))

=== tests/cases/compiler/usage.ts ===
import {Either} from "./utils";
>Either : Symbol(Either, Decl(usage.ts, 0, 8))

import {f} from "./init";
>f : Symbol(f, Decl(usage.ts, 1, 8))

export function doThing() {
>doThing : Symbol(doThing, Decl(usage.ts, 1, 25))

return f();
>f : Symbol(f, Decl(usage.ts, 1, 8))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
=== tests/cases/compiler/init.ts ===
export interface A {
a: true;
>a : true
>true : true
}

export interface B {
b: true;
>b : true
>true : true
}

export function f(thing?: A | B): A | B { return null as any; };
>f : (thing?: A | B) => A | B
>thing : import("tests/cases/compiler/utils").Either
>null as any : any
>null : null

=== tests/cases/compiler/utils.ts ===
import {A, B} from "./init";
>A : any
>B : any

export type Either = A | B;
>Either : Either

=== tests/cases/compiler/usage.ts ===
import {Either} from "./utils";
>Either : any

import {f} from "./init";
>f : (thing?: Either) => Either

export function doThing() {
>doThing : () => Either

return f();
>f() : Either
>f : (thing?: Either) => Either
}
10 changes: 5 additions & 5 deletions tests/baselines/reference/bigintIndex.types
Original file line number Diff line number Diff line change
Expand Up @@ -32,27 +32,27 @@ num = arr[1n]; // should error
>1n : 1n

let key: keyof any; // should be type "string | number | symbol"
>key : string | number | symbol
>key : PropertyKey

key = 123;
>key = 123 : 123
>key : string | number | symbol
>key : PropertyKey
>123 : 123

key = "abc";
>key = "abc" : "abc"
>key : string | number | symbol
>key : PropertyKey
>"abc" : "abc"

key = Symbol();
>key = Symbol() : symbol
>key : string | number | symbol
>key : PropertyKey
>Symbol() : symbol
>Symbol : SymbolConstructor

key = 123n; // should error
>key = 123n : 123n
>key : string | number | symbol
>key : PropertyKey
>123n : 123n

// Show correct usage of bigint index: explicitly convert to string
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/computedPropertyNames15_ES5.types
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ var p1: number | string;
>p1 : string | number

var p2: number | number[];
>p2 : number | number[]
>p2 : VibratePattern

var p3: string | boolean;
>p3 : string | boolean
Expand All @@ -17,7 +17,7 @@ class C {

[p2]() { }
>[p2] : () => void
>p2 : number | number[]
>p2 : VibratePattern

[p3]() { }
>[p3] : () => void
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/computedPropertyNames15_ES6.types
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ var p1: number | string;
>p1 : string | number

var p2: number | number[];
>p2 : number | number[]
>p2 : VibratePattern

var p3: string | boolean;
>p3 : string | boolean
Expand All @@ -17,7 +17,7 @@ class C {

[p2]() { }
>[p2] : () => void
>p2 : number | number[]
>p2 : VibratePattern

[p3]() { }
>[p3] : () => void
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/computedPropertyNames6_ES5.types
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ var p1: number | string;
>p1 : string | number

var p2: number | number[];
>p2 : number | number[]
>p2 : VibratePattern

var p3: string | boolean;
>p3 : string | boolean
Expand All @@ -19,7 +19,7 @@ var v = {

[p2]: 1,
>[p2] : number
>p2 : number | number[]
>p2 : VibratePattern
>1 : 1

[p3]: 2
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/computedPropertyNames6_ES6.types
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ var p1: number | string;
>p1 : string | number

var p2: number | number[];
>p2 : number | number[]
>p2 : VibratePattern

var p3: string | boolean;
>p3 : string | boolean
Expand All @@ -19,7 +19,7 @@ var v = {

[p2]: 1,
>[p2] : number
>p2 : number | number[]
>p2 : VibratePattern
>1 : 1

[p3]: 2
Expand Down
10 changes: 5 additions & 5 deletions tests/baselines/reference/conditionalTypeDoesntSpinForever.types
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ export enum PubSubRecordIsStoredInRedisAsA {

hasField: (fieldName: string | number | symbol) => fieldName is keyof SO_FAR
>hasField : (fieldName: string | number | symbol) => fieldName is keyof SO_FAR
>fieldName : string | number | symbol
>fieldName : PropertyKey

} : {}

Expand Down Expand Up @@ -418,9 +418,9 @@ export enum PubSubRecordIsStoredInRedisAsA {
hasField: (fieldName: string | number | symbol) => fieldName in soFar
>hasField : (fieldName: string | number | symbol) => boolean
>(fieldName: string | number | symbol) => fieldName in soFar : (fieldName: string | number | symbol) => boolean
>fieldName : string | number | symbol
>fieldName : PropertyKey
>fieldName in soFar : boolean
>fieldName : string | number | symbol
>fieldName : PropertyKey
>soFar : SO_FAR
}
);
Expand Down Expand Up @@ -474,8 +474,8 @@ export enum PubSubRecordIsStoredInRedisAsA {
>soFar : SO_FAR

buildType(soFar)
>buildType(soFar) : { type?: undefined; fields?: undefined; hasField?: undefined; } | { type: SO_FAR; fields: () => Set<keyof SO_FAR>; hasField: (fieldName: string | number | symbol) => boolean; }
>buildType : <SO_FAR>(soFar: SO_FAR) => { type?: undefined; fields?: undefined; hasField?: undefined; } | { type: SO_FAR; fields: () => Set<keyof SO_FAR>; hasField: (fieldName: string | number | symbol) => boolean; }
>buildType(soFar) : { type?: undefined; fields?: undefined; hasField?: undefined; } | { type: SO_FAR; fields: () => Set<keyof SO_FAR>; hasField: (fieldName: PropertyKey) => boolean; }
>buildType : <SO_FAR>(soFar: SO_FAR) => { type?: undefined; fields?: undefined; hasField?: undefined; } | { type: SO_FAR; fields: () => Set<keyof SO_FAR>; hasField: (fieldName: PropertyKey) => boolean; }
>soFar : SO_FAR

) as BuildPubSubRecordType<SO_FAR>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -335,14 +335,14 @@ const memoizedAdd3 = memoize((x: number, y = 0): number => x + y);

declare function execute(script: string | Function): Promise<string>;
>execute : (script: string | Function) => Promise<string>
>script : string | Function
>script : TimerHandler

export function executeSomething() {
>executeSomething : () => Promise<string>

return execute((root: HTMLElement, debug = true) => {
>execute((root: HTMLElement, debug = true) => { if (debug) { root.innerHTML = ''; } }) : Promise<string>
>execute : (script: string | Function) => Promise<string>
>execute : (script: TimerHandler) => Promise<string>
>(root: HTMLElement, debug = true) => { if (debug) { root.innerHTML = ''; } } : (root: HTMLElement, debug?: boolean) => void
>root : HTMLElement
>debug : boolean
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/inOperatorWithValidOperands.types
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ var a2: number;
>a2 : number

var a3: string | number | symbol;
>a3 : string | number | symbol
>a3 : PropertyKey

var a4: any;
>a4 : any
Expand Down Expand Up @@ -49,7 +49,7 @@ var ra5 = 0 in x;
var ra6 = a3 in x;
>ra6 : boolean
>a3 in x : boolean
>a3 : string | number | symbol
>a3 : PropertyKey
>x : any

var ra7 = a4 in x;
Expand Down
Loading

0 comments on commit 7d9bb77

Please sign in to comment.