-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Fix reference marking of values merged with unresolvable type-only imports #54799
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4457,32 +4457,37 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | |
* @returns SymbolFlags.All if `symbol` is an alias that ultimately resolves to `unknown`; | ||
* combined flags of all alias targets otherwise. | ||
*/ | ||
function getAllSymbolFlags(symbol: Symbol): SymbolFlags { | ||
let flags = symbol.flags; | ||
let seenSymbols; | ||
while (symbol.flags & SymbolFlags.Alias) { | ||
const target = resolveAlias(symbol); | ||
if (target === unknownSymbol) { | ||
return SymbolFlags.All; | ||
} | ||
|
||
// Optimizations - try to avoid creating or adding to | ||
// `seenSymbols` if possible | ||
if (target === symbol || seenSymbols?.has(target)) { | ||
break; | ||
} | ||
if (target.flags & SymbolFlags.Alias) { | ||
if (seenSymbols) { | ||
seenSymbols.add(target); | ||
} | ||
else { | ||
seenSymbols = new Set([symbol, target]); | ||
} | ||
} | ||
flags |= target.flags; | ||
symbol = target; | ||
} | ||
return flags; | ||
function getAllSymbolFlags(symbol: Symbol, excludeTypeOnlyMeanings?: boolean): SymbolFlags { | ||
const typeOnlyDeclaration = excludeTypeOnlyMeanings && getTypeOnlyAliasDeclaration(symbol); | ||
const typeOnlyResolution = typeOnlyDeclaration && resolveAlias(typeOnlyDeclaration.symbol); | ||
let flags = symbol.flags; | ||
let seenSymbols; | ||
while (symbol.flags & SymbolFlags.Alias) { | ||
const target = resolveAlias(symbol); | ||
if (target === typeOnlyResolution) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This name is a little confusing since it's hard to notice that it's the exports of a module. But maybe the property that it's targets of resolution is more important. |
||
break; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The indentation on this function was messed up, so I recommend viewing it with whitespace changes hidden. These few lines are the core change that I mentioned in the PR description. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (dprint when?) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My thoughts exactly 👀 |
||
if (target === unknownSymbol) { | ||
return SymbolFlags.All; | ||
} | ||
|
||
// Optimizations - try to avoid creating or adding to | ||
// `seenSymbols` if possible | ||
if (target === symbol || seenSymbols?.has(target)) { | ||
break; | ||
} | ||
if (target.flags & SymbolFlags.Alias) { | ||
if (seenSymbols) { | ||
seenSymbols.add(target); | ||
} | ||
else { | ||
seenSymbols = new Set([symbol, target]); | ||
} | ||
} | ||
flags |= target.flags; | ||
symbol = target; | ||
} | ||
return flags; | ||
} | ||
|
||
/** | ||
|
@@ -4572,7 +4577,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | |
const target = resolveAlias(symbol); | ||
if (target) { | ||
const markAlias = target === unknownSymbol || | ||
((getAllSymbolFlags(target) & SymbolFlags.Value) && !isConstEnumOrConstEnumOnlyModule(target) && !getTypeOnlyAliasDeclaration(symbol, SymbolFlags.Value)); | ||
((getAllSymbolFlags(target, /*excludeTypeOnlyMeanings*/ true) & SymbolFlags.Value) && !isConstEnumOrConstEnumOnlyModule(target)); | ||
|
||
if (markAlias) { | ||
markAliasSymbolAsReferenced(symbol); | ||
|
@@ -5567,7 +5572,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | |
function symbolIsValue(symbol: Symbol, includeTypeOnlyMembers?: boolean): boolean { | ||
return !!( | ||
symbol.flags & SymbolFlags.Value || | ||
symbol.flags & SymbolFlags.Alias && getAllSymbolFlags(symbol) & SymbolFlags.Value && (includeTypeOnlyMembers || !getTypeOnlyAliasDeclaration(symbol))); | ||
symbol.flags & SymbolFlags.Alias && getAllSymbolFlags(symbol, !includeTypeOnlyMembers) & SymbolFlags.Value); | ||
} | ||
|
||
function findConstructorDeclaration(node: ClassLikeDeclaration): ConstructorDeclaration | undefined { | ||
|
@@ -27808,9 +27813,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | |
if (!canCollectSymbolAliasAccessabilityData) { | ||
return; | ||
} | ||
if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !isInTypeQuery(location) && !getTypeOnlyAliasDeclaration(symbol, SymbolFlags.Value)) { | ||
if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !isInTypeQuery(location)) { | ||
const target = resolveAlias(symbol); | ||
if (getAllSymbolFlags(target) & (SymbolFlags.Value | SymbolFlags.ExportValue)) { | ||
if (getAllSymbolFlags(target, /*excludeTypeOnlyMeanings*/ true) & (SymbolFlags.Value | SymbolFlags.ExportValue)) { | ||
// An alias resolving to a const enum cannot be elided if (1) 'isolatedModules' is enabled | ||
// (because the const enum value will not be inlined), or if (2) the alias is an export | ||
// of a const enum declaration that will be preserved. | ||
|
@@ -46312,7 +46317,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | |
case SyntaxKind.ImportSpecifier: | ||
case SyntaxKind.ExportSpecifier: | ||
const symbol = getSymbolOfDeclaration(node as ImportClause | NamespaceImport | ImportSpecifier | ExportSpecifier); | ||
return !!symbol && isAliasResolvedToValue(symbol) && !getTypeOnlyAliasDeclaration(symbol, SymbolFlags.Value); | ||
return !!symbol && isAliasResolvedToValue(symbol, /*excludeTypeOnlyValues*/ true); | ||
case SyntaxKind.ExportDeclaration: | ||
const exportClause = (node as ExportDeclaration).exportClause; | ||
return !!exportClause && ( | ||
|
@@ -46338,7 +46343,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | |
return isValue && node.moduleReference && !nodeIsMissing(node.moduleReference); | ||
} | ||
|
||
function isAliasResolvedToValue(symbol: Symbol | undefined): boolean { | ||
function isAliasResolvedToValue(symbol: Symbol | undefined, excludeTypeOnlyValues?: boolean): boolean { | ||
if (!symbol) { | ||
return false; | ||
} | ||
|
@@ -46348,7 +46353,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | |
} | ||
// const enums and modules that contain only const enums are not considered values from the emit perspective | ||
// unless 'preserveConstEnums' option is set to true | ||
return !!((getAllSymbolFlags(target) ?? -1) & SymbolFlags.Value) && | ||
return !!((getAllSymbolFlags(target, excludeTypeOnlyValues) ?? -1) & SymbolFlags.Value) && | ||
(shouldPreserveConstEnums(compilerOptions) || !isConstEnumOrConstEnumOnlyModule(target)); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
//// [tests/cases/conformance/externalModules/typeOnly/namespaceImportTypeQuery2.ts] //// | ||
|
||
//// [z.ts] | ||
interface A {} | ||
export type { A }; | ||
|
||
//// [a.ts] | ||
import { A } from './z'; | ||
const A = 0; | ||
export { A }; | ||
export class B {}; | ||
|
||
//// [b.ts] | ||
import * as types from './a'; | ||
let t: typeof types = { | ||
A: undefined as any, // ok | ||
B: undefined as any, | ||
} | ||
|
||
|
||
//// [z.js] | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
//// [a.js] | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.B = exports.A = void 0; | ||
var A = 0; | ||
exports.A = A; | ||
var B = /** @class */ (function () { | ||
function B() { | ||
} | ||
return B; | ||
}()); | ||
exports.B = B; | ||
; | ||
//// [b.js] | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var t = { | ||
A: undefined, | ||
B: undefined, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
//// [tests/cases/conformance/externalModules/typeOnly/namespaceImportTypeQuery2.ts] //// | ||
|
||
=== /z.ts === | ||
interface A {} | ||
>A : Symbol(A, Decl(z.ts, 0, 0)) | ||
|
||
export type { A }; | ||
>A : Symbol(A, Decl(z.ts, 1, 13)) | ||
|
||
=== /a.ts === | ||
import { A } from './z'; | ||
>A : Symbol(A, Decl(a.ts, 0, 8), Decl(a.ts, 1, 5)) | ||
|
||
const A = 0; | ||
>A : Symbol(A, Decl(a.ts, 0, 8), Decl(a.ts, 1, 5)) | ||
|
||
export { A }; | ||
>A : Symbol(A, Decl(a.ts, 2, 8)) | ||
|
||
export class B {}; | ||
>B : Symbol(B, Decl(a.ts, 2, 13)) | ||
|
||
=== /b.ts === | ||
import * as types from './a'; | ||
>types : Symbol(types, Decl(b.ts, 0, 6)) | ||
|
||
let t: typeof types = { | ||
>t : Symbol(t, Decl(b.ts, 1, 3)) | ||
>types : Symbol(types, Decl(b.ts, 0, 6)) | ||
|
||
A: undefined as any, // ok | ||
>A : Symbol(A, Decl(b.ts, 1, 23)) | ||
>undefined : Symbol(undefined) | ||
|
||
B: undefined as any, | ||
>B : Symbol(B, Decl(b.ts, 2, 22)) | ||
>undefined : Symbol(undefined) | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
//// [tests/cases/conformance/externalModules/typeOnly/namespaceImportTypeQuery2.ts] //// | ||
|
||
=== /z.ts === | ||
interface A {} | ||
export type { A }; | ||
>A : A | ||
|
||
=== /a.ts === | ||
import { A } from './z'; | ||
>A : 0 | ||
|
||
const A = 0; | ||
>A : 0 | ||
>0 : 0 | ||
|
||
export { A }; | ||
>A : 0 | ||
|
||
export class B {}; | ||
>B : B | ||
|
||
=== /b.ts === | ||
import * as types from './a'; | ||
>types : typeof types | ||
|
||
let t: typeof types = { | ||
>t : typeof types | ||
>types : typeof types | ||
>{ A: undefined as any, // ok B: undefined as any,} : { A: any; B: any; } | ||
|
||
A: undefined as any, // ok | ||
>A : any | ||
>undefined as any : any | ||
>undefined : undefined | ||
|
||
B: undefined as any, | ||
>B : any | ||
>undefined as any : any | ||
>undefined : undefined | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/a.ts(1,24): error TS2307: Cannot find module './z' or its corresponding type declarations. | ||
|
||
|
||
==== /a.ts (1 errors) ==== | ||
import type { A } from './z'; // unresolved | ||
~~~~~ | ||
!!! error TS2307: Cannot find module './z' or its corresponding type declarations. | ||
const A = 0; | ||
export { A }; | ||
export class B {}; | ||
|
||
==== /b.ts (0 errors) ==== | ||
import * as types from './a'; | ||
let t: typeof types = { | ||
A: undefined as any, // ok | ||
B: undefined as any, | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
//// [tests/cases/conformance/externalModules/typeOnly/namespaceImportTypeQuery3.ts] //// | ||
|
||
//// [a.ts] | ||
import type { A } from './z'; // unresolved | ||
const A = 0; | ||
export { A }; | ||
export class B {}; | ||
|
||
//// [b.ts] | ||
import * as types from './a'; | ||
let t: typeof types = { | ||
A: undefined as any, // ok | ||
B: undefined as any, | ||
} | ||
|
||
|
||
//// [a.js] | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.B = void 0; | ||
var A = 0; | ||
var B = /** @class */ (function () { | ||
function B() { | ||
} | ||
return B; | ||
}()); | ||
exports.B = B; | ||
; | ||
//// [b.js] | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var t = { | ||
A: undefined, | ||
B: undefined, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
//// [tests/cases/conformance/externalModules/typeOnly/namespaceImportTypeQuery3.ts] //// | ||
|
||
=== /a.ts === | ||
import type { A } from './z'; // unresolved | ||
>A : Symbol(A, Decl(a.ts, 0, 13), Decl(a.ts, 1, 5)) | ||
|
||
const A = 0; | ||
>A : Symbol(A, Decl(a.ts, 0, 13), Decl(a.ts, 1, 5)) | ||
|
||
export { A }; | ||
>A : Symbol(A, Decl(a.ts, 2, 8)) | ||
|
||
export class B {}; | ||
>B : Symbol(B, Decl(a.ts, 2, 13)) | ||
|
||
=== /b.ts === | ||
import * as types from './a'; | ||
>types : Symbol(types, Decl(b.ts, 0, 6)) | ||
|
||
let t: typeof types = { | ||
>t : Symbol(t, Decl(b.ts, 1, 3)) | ||
>types : Symbol(types, Decl(b.ts, 0, 6)) | ||
|
||
A: undefined as any, // ok | ||
>A : Symbol(A, Decl(b.ts, 1, 23)) | ||
>undefined : Symbol(undefined) | ||
|
||
B: undefined as any, | ||
>B : Symbol(B, Decl(b.ts, 2, 22)) | ||
>undefined : Symbol(undefined) | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
//// [tests/cases/conformance/externalModules/typeOnly/namespaceImportTypeQuery3.ts] //// | ||
|
||
=== /a.ts === | ||
import type { A } from './z'; // unresolved | ||
>A : any | ||
|
||
const A = 0; | ||
>A : 0 | ||
>0 : 0 | ||
|
||
export { A }; | ||
>A : 0 | ||
|
||
export class B {}; | ||
>B : B | ||
|
||
=== /b.ts === | ||
import * as types from './a'; | ||
>types : typeof types | ||
|
||
let t: typeof types = { | ||
>t : typeof types | ||
>types : typeof types | ||
>{ A: undefined as any, // ok B: undefined as any,} : { A: any; B: any; } | ||
|
||
A: undefined as any, // ok | ||
>A : any | ||
>undefined as any : any | ||
>undefined : undefined | ||
|
||
B: undefined as any, | ||
>B : any | ||
>undefined as any : any | ||
>undefined : undefined | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this name makes it sound like all uses of
symbol.flags
should becomegetSymbolFlags(symbol)
. Is that true? It doesn't look like it from the diff.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to audit all uses of
symbol.flags
when I first wrotegetAllSymbolFlags
and did make many substitutions. The remaining uses, as best I could tell, intentionally only look at local meanings. The namegetAllSymbolFlags
no longer felt appropriate with arguments that explicitly filter them down from “all.” Perhaps a way to saygetSymbolFlagsIncludingNonLocalOnes
would make a better name.