From 4cab1b9b5fcea7006d10b6db70d8eb5f16269d41 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Tue, 10 May 2022 15:21:32 -0700 Subject: [PATCH 1/4] Allow referencing type-only exports as namespace members in ImportTypes and TypeQueries --- src/compiler/checker.ts | 14 +++-- .../namespaceImportTypeQuery.errors.txt | 15 ----- .../namespaceImportTypeQuery.symbols | 2 + .../reference/namespaceImportTypeQuery.types | 6 +- .../reference/typeofImportTypeOnlyExport.js | 58 +++++++++++++++++++ .../typeofImportTypeOnlyExport.symbols | 35 +++++++++++ .../typeofImportTypeOnlyExport.types | 40 +++++++++++++ .../typeofImportTypeOnlyExport.ts | 18 ++++++ 8 files changed, 164 insertions(+), 24 deletions(-) delete mode 100644 tests/baselines/reference/namespaceImportTypeQuery.errors.txt create mode 100644 tests/baselines/reference/typeofImportTypeOnlyExport.js create mode 100644 tests/baselines/reference/typeofImportTypeOnlyExport.symbols create mode 100644 tests/baselines/reference/typeofImportTypeOnlyExport.types create mode 100644 tests/cases/conformance/declarationEmit/typeofImportTypeOnlyExport.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 025eeecc30624..f4e6f7f9a2480 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4110,8 +4110,10 @@ namespace ts { return getMergedSymbol(symbol && (symbol.flags & SymbolFlags.ExportValue) !== 0 && symbol.exportSymbol || symbol); } - function symbolIsValue(symbol: Symbol): boolean { - return !!(symbol.flags & SymbolFlags.Value || symbol.flags & SymbolFlags.Alias && resolveAlias(symbol).flags & SymbolFlags.Value && !getTypeOnlyAliasDeclaration(symbol)); + function symbolIsValue(symbol: Symbol, includeTypeOnlyMembers?: boolean): boolean { + return !!( + symbol.flags & SymbolFlags.Value || + symbol.flags & SymbolFlags.Alias && resolveAlias(symbol).flags & SymbolFlags.Value && (includeTypeOnlyMembers || !getTypeOnlyAliasDeclaration(symbol))); } function findConstructorDeclaration(node: ClassLikeDeclaration): ConstructorDeclaration | undefined { @@ -12575,12 +12577,12 @@ 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, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined { + function getPropertyOfType(type: Type, name: __String, skipObjectFunctionPropertyAugment?: boolean, includeTypeOnlyMembers?: boolean): Symbol | undefined { type = getReducedApparentType(type); if (type.flags & TypeFlags.Object) { const resolved = resolveStructuredTypeMembers(type as ObjectType); const symbol = resolved.members.get(name); - if (symbol && symbolIsValue(symbol)) { + if (symbol && symbolIsValue(symbol, includeTypeOnlyMembers)) { return symbol; } if (skipObjectFunctionPropertyAugment) return undefined; @@ -16208,7 +16210,7 @@ namespace ts { // the `exports` lookup process that only looks up namespace members which is used for most type references const mergedResolvedSymbol = getMergedSymbol(resolveSymbol(currentNamespace)); const next = node.isTypeOf - ? getPropertyOfType(getTypeOfSymbol(mergedResolvedSymbol), current.escapedText) + ? getPropertyOfType(getTypeOfSymbol(mergedResolvedSymbol), current.escapedText, /*skipObjectFunctionPropertyAugment*/ undefined, /*includeTypeOnlyMembers*/ true) : getSymbol(getExportsOfSymbol(mergedResolvedSymbol), current.escapedText, meaning); if (!next) { error(current, Diagnostics.Namespace_0_has_no_exported_member_1, getFullyQualifiedName(currentNamespace), declarationNameToString(current)); @@ -28997,7 +28999,7 @@ namespace ts { } return isErrorType(apparentType) ? errorType : apparentType;; } - prop = getPropertyOfType(apparentType, right.escapedText); + prop = getPropertyOfType(apparentType, right.escapedText, /*skipObjectFunctionPropertyAugment*/ false, /*includeTypeOnlyMembers*/ node.kind === SyntaxKind.QualifiedName); } // In `Foo.Bar.Baz`, 'Foo' is not referenced if 'Bar' is a const enum or a module containing only const enums. // `Foo` is also not referenced in `enum FooCopy { Bar = Foo.Bar }`, because the enum member value gets inlined diff --git a/tests/baselines/reference/namespaceImportTypeQuery.errors.txt b/tests/baselines/reference/namespaceImportTypeQuery.errors.txt deleted file mode 100644 index 5036215385d45..0000000000000 --- a/tests/baselines/reference/namespaceImportTypeQuery.errors.txt +++ /dev/null @@ -1,15 +0,0 @@ -/b.ts(2,21): error TS2339: Property 'A' does not exist on type 'typeof import("/a")'. - - -==== /a.ts (0 errors) ==== - class A {} - export type { A }; - export class B {}; - -==== /b.ts (1 errors) ==== - import * as types from './a'; - let A: typeof types.A; - ~ -!!! error TS2339: Property 'A' does not exist on type 'typeof import("/a")'. - let B: typeof types.B; - \ No newline at end of file diff --git a/tests/baselines/reference/namespaceImportTypeQuery.symbols b/tests/baselines/reference/namespaceImportTypeQuery.symbols index 9851b31b6b001..71d951b447273 100644 --- a/tests/baselines/reference/namespaceImportTypeQuery.symbols +++ b/tests/baselines/reference/namespaceImportTypeQuery.symbols @@ -14,7 +14,9 @@ import * as types from './a'; let A: typeof types.A; >A : Symbol(A, Decl(b.ts, 1, 3)) +>types.A : Symbol(types.A, Decl(a.ts, 1, 13)) >types : Symbol(types, Decl(b.ts, 0, 6)) +>A : Symbol(types.A, Decl(a.ts, 1, 13)) let B: typeof types.B; >B : Symbol(B, Decl(b.ts, 2, 3)) diff --git a/tests/baselines/reference/namespaceImportTypeQuery.types b/tests/baselines/reference/namespaceImportTypeQuery.types index 273a46b1507bc..7c5c63482c2b4 100644 --- a/tests/baselines/reference/namespaceImportTypeQuery.types +++ b/tests/baselines/reference/namespaceImportTypeQuery.types @@ -13,10 +13,10 @@ import * as types from './a'; >types : typeof types let A: typeof types.A; ->A : any ->types.A : any +>A : typeof types.A +>types.A : typeof types.A >types : typeof types ->A : any +>A : typeof types.A let B: typeof types.B; >B : typeof types.B diff --git a/tests/baselines/reference/typeofImportTypeOnlyExport.js b/tests/baselines/reference/typeofImportTypeOnlyExport.js new file mode 100644 index 0000000000000..b9cbd6ec6475d --- /dev/null +++ b/tests/baselines/reference/typeofImportTypeOnlyExport.js @@ -0,0 +1,58 @@ +//// [tests/cases/conformance/declarationEmit/typeofImportTypeOnlyExport.ts] //// + +//// [button.ts] +import {classMap} from './lit.js'; +export const c = classMap(); + +//// [lit.ts] +class ClassMapDirective {} + +export type {ClassMapDirective}; + +export const directive = + (class_: C) => + () => ({ + directive: class_, + }); + +export const classMap = directive(ClassMapDirective); + + +//// [lit.js] +"use strict"; +exports.__esModule = true; +exports.classMap = exports.directive = void 0; +var ClassMapDirective = /** @class */ (function () { + function ClassMapDirective() { + } + return ClassMapDirective; +}()); +var directive = function (class_) { + return function () { return ({ + directive: class_ + }); }; +}; +exports.directive = directive; +exports.classMap = (0, exports.directive)(ClassMapDirective); +//// [button.js] +"use strict"; +exports.__esModule = true; +exports.c = void 0; +var lit_js_1 = require("./lit.js"); +exports.c = (0, lit_js_1.classMap)(); + + +//// [lit.d.ts] +declare class ClassMapDirective { +} +export type { ClassMapDirective }; +export declare const directive: (class_: C) => () => { + directive: C; +}; +export declare const classMap: () => { + directive: typeof ClassMapDirective; +}; +//// [button.d.ts] +export declare const c: { + directive: typeof import("./lit.js").ClassMapDirective; +}; diff --git a/tests/baselines/reference/typeofImportTypeOnlyExport.symbols b/tests/baselines/reference/typeofImportTypeOnlyExport.symbols new file mode 100644 index 0000000000000..512c80aa94689 --- /dev/null +++ b/tests/baselines/reference/typeofImportTypeOnlyExport.symbols @@ -0,0 +1,35 @@ +=== tests/cases/conformance/declarationEmit/button.ts === +import {classMap} from './lit.js'; +>classMap : Symbol(classMap, Decl(button.ts, 0, 8)) + +export const c = classMap(); +>c : Symbol(c, Decl(button.ts, 1, 12)) +>classMap : Symbol(classMap, Decl(button.ts, 0, 8)) + +=== tests/cases/conformance/declarationEmit/lit.ts === +class ClassMapDirective {} +>ClassMapDirective : Symbol(ClassMapDirective, Decl(lit.ts, 0, 0)) + +export type {ClassMapDirective}; +>ClassMapDirective : Symbol(ClassMapDirective, Decl(lit.ts, 2, 13)) + +export const directive = +>directive : Symbol(directive, Decl(lit.ts, 4, 12)) + + (class_: C) => +>C : Symbol(C, Decl(lit.ts, 5, 3)) +>class_ : Symbol(class_, Decl(lit.ts, 5, 6)) +>C : Symbol(C, Decl(lit.ts, 5, 3)) + + () => ({ + directive: class_, +>directive : Symbol(directive, Decl(lit.ts, 6, 10)) +>class_ : Symbol(class_, Decl(lit.ts, 5, 6)) + + }); + +export const classMap = directive(ClassMapDirective); +>classMap : Symbol(classMap, Decl(lit.ts, 10, 12)) +>directive : Symbol(directive, Decl(lit.ts, 4, 12)) +>ClassMapDirective : Symbol(ClassMapDirective, Decl(lit.ts, 0, 0)) + diff --git a/tests/baselines/reference/typeofImportTypeOnlyExport.types b/tests/baselines/reference/typeofImportTypeOnlyExport.types new file mode 100644 index 0000000000000..6bc8950868762 --- /dev/null +++ b/tests/baselines/reference/typeofImportTypeOnlyExport.types @@ -0,0 +1,40 @@ +=== tests/cases/conformance/declarationEmit/button.ts === +import {classMap} from './lit.js'; +>classMap : () => { directive: typeof import("tests/cases/conformance/declarationEmit/lit").ClassMapDirective; } + +export const c = classMap(); +>c : { directive: typeof import("tests/cases/conformance/declarationEmit/lit").ClassMapDirective; } +>classMap() : { directive: typeof import("tests/cases/conformance/declarationEmit/lit").ClassMapDirective; } +>classMap : () => { directive: typeof import("tests/cases/conformance/declarationEmit/lit").ClassMapDirective; } + +=== tests/cases/conformance/declarationEmit/lit.ts === +class ClassMapDirective {} +>ClassMapDirective : ClassMapDirective + +export type {ClassMapDirective}; +>ClassMapDirective : ClassMapDirective + +export const directive = +>directive : (class_: C) => () => { directive: C; } + + (class_: C) => +>(class_: C) => () => ({ directive: class_, }) : (class_: C) => () => { directive: C; } +>class_ : C + + () => ({ +>() => ({ directive: class_, }) : () => { directive: C; } +>({ directive: class_, }) : { directive: C; } +>{ directive: class_, } : { directive: C; } + + directive: class_, +>directive : C +>class_ : C + + }); + +export const classMap = directive(ClassMapDirective); +>classMap : () => { directive: typeof ClassMapDirective; } +>directive(ClassMapDirective) : () => { directive: typeof ClassMapDirective; } +>directive : (class_: C) => () => { directive: C; } +>ClassMapDirective : typeof ClassMapDirective + diff --git a/tests/cases/conformance/declarationEmit/typeofImportTypeOnlyExport.ts b/tests/cases/conformance/declarationEmit/typeofImportTypeOnlyExport.ts new file mode 100644 index 0000000000000..aabf57bcc0135 --- /dev/null +++ b/tests/cases/conformance/declarationEmit/typeofImportTypeOnlyExport.ts @@ -0,0 +1,18 @@ +// @declaration: true + +// @Filename: button.ts +import {classMap} from './lit.js'; +export const c = classMap(); + +// @Filename: lit.ts +class ClassMapDirective {} + +export type {ClassMapDirective}; + +export const directive = + (class_: C) => + () => ({ + directive: class_, + }); + +export const classMap = directive(ClassMapDirective); From 90cf264a4b1d35faa321c978b75069c7d9362f41 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Tue, 10 May 2022 15:28:46 -0700 Subject: [PATCH 2/4] Add extra test case --- .../namespaceImportTypeQuery.errors.txt | 24 +++++++++++++++++++ .../reference/namespaceImportTypeQuery.js | 13 ++++++++++ .../namespaceImportTypeQuery.symbols | 15 ++++++++++++ .../reference/namespaceImportTypeQuery.types | 18 ++++++++++++++ .../typeOnly/namespaceImportTypeQuery.ts | 7 ++++++ 5 files changed, 77 insertions(+) create mode 100644 tests/baselines/reference/namespaceImportTypeQuery.errors.txt diff --git a/tests/baselines/reference/namespaceImportTypeQuery.errors.txt b/tests/baselines/reference/namespaceImportTypeQuery.errors.txt new file mode 100644 index 0000000000000..928882d2b179b --- /dev/null +++ b/tests/baselines/reference/namespaceImportTypeQuery.errors.txt @@ -0,0 +1,24 @@ +/b.ts(8,3): error TS2322: Type '{ A: any; B: any; }' is not assignable to type 'typeof import("/a")'. + Object literal may only specify known properties, and 'A' does not exist in type 'typeof import("/a")'. + + +==== /a.ts (0 errors) ==== + class A {} + export type { A }; + export class B {}; + +==== /b.ts (1 errors) ==== + import * as types from './a'; + let A: typeof types.A; + let B: typeof types.B; + + let t: typeof types = { + // error: while you can ask for `typeof types.A`, + // `typeof types` does not include `A` + A: undefined as any, + ~~~~~~~~~~~~~~~~~~~ +!!! error TS2322: Type '{ A: any; B: any; }' is not assignable to type 'typeof import("/a")'. +!!! error TS2322: Object literal may only specify known properties, and 'A' does not exist in type 'typeof import("/a")'. + B: undefined as any, + } + \ No newline at end of file diff --git a/tests/baselines/reference/namespaceImportTypeQuery.js b/tests/baselines/reference/namespaceImportTypeQuery.js index 18ea634352165..33746b223b1a6 100644 --- a/tests/baselines/reference/namespaceImportTypeQuery.js +++ b/tests/baselines/reference/namespaceImportTypeQuery.js @@ -9,6 +9,13 @@ export class B {}; import * as types from './a'; let A: typeof types.A; let B: typeof types.B; + +let t: typeof types = { + // error: while you can ask for `typeof types.A`, + // `typeof types` does not include `A` + A: undefined as any, + B: undefined as any, +} //// [a.js] @@ -32,3 +39,9 @@ exports.B = B; exports.__esModule = true; var A; var B; +var t = { + // error: while you can ask for `typeof types.A`, + // `typeof types` does not include `A` + A: undefined, + B: undefined +}; diff --git a/tests/baselines/reference/namespaceImportTypeQuery.symbols b/tests/baselines/reference/namespaceImportTypeQuery.symbols index 71d951b447273..30b8694f6022a 100644 --- a/tests/baselines/reference/namespaceImportTypeQuery.symbols +++ b/tests/baselines/reference/namespaceImportTypeQuery.symbols @@ -24,3 +24,18 @@ let B: typeof types.B; >types : Symbol(types, Decl(b.ts, 0, 6)) >B : Symbol(types.B, Decl(a.ts, 1, 18)) +let t: typeof types = { +>t : Symbol(t, Decl(b.ts, 4, 3)) +>types : Symbol(types, Decl(b.ts, 0, 6)) + + // error: while you can ask for `typeof types.A`, + // `typeof types` does not include `A` + A: undefined as any, +>A : Symbol(A, Decl(b.ts, 4, 23)) +>undefined : Symbol(undefined) + + B: undefined as any, +>B : Symbol(B, Decl(b.ts, 7, 22)) +>undefined : Symbol(undefined) +} + diff --git a/tests/baselines/reference/namespaceImportTypeQuery.types b/tests/baselines/reference/namespaceImportTypeQuery.types index 7c5c63482c2b4..6023d0961d580 100644 --- a/tests/baselines/reference/namespaceImportTypeQuery.types +++ b/tests/baselines/reference/namespaceImportTypeQuery.types @@ -24,3 +24,21 @@ let B: typeof types.B; >types : typeof types >B : typeof types.B +let t: typeof types = { +>t : typeof types +>types : typeof types +>{ // error: while you can ask for `typeof types.A`, // `typeof types` does not include `A` A: undefined as any, B: undefined as any,} : { A: any; B: any; } + + // error: while you can ask for `typeof types.A`, + // `typeof types` does not include `A` + A: undefined as any, +>A : any +>undefined as any : any +>undefined : undefined + + B: undefined as any, +>B : any +>undefined as any : any +>undefined : undefined +} + diff --git a/tests/cases/conformance/externalModules/typeOnly/namespaceImportTypeQuery.ts b/tests/cases/conformance/externalModules/typeOnly/namespaceImportTypeQuery.ts index 10e8e020158e8..f82f26607b070 100644 --- a/tests/cases/conformance/externalModules/typeOnly/namespaceImportTypeQuery.ts +++ b/tests/cases/conformance/externalModules/typeOnly/namespaceImportTypeQuery.ts @@ -7,3 +7,10 @@ export class B {}; import * as types from './a'; let A: typeof types.A; let B: typeof types.B; + +let t: typeof types = { + // error: while you can ask for `typeof types.A`, + // `typeof types` does not include `A` + A: undefined as any, + B: undefined as any, +} From 103b090bce3c3cf297452d212dd488799c1542e3 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Tue, 10 May 2022 20:10:00 -0700 Subject: [PATCH 3/4] ;; -> ; --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f4e6f7f9a2480..8d46ef18fbbf1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -28997,7 +28997,7 @@ namespace ts { if (isIdentifier(left) && parentSymbol) { markAliasReferenced(parentSymbol, node); } - return isErrorType(apparentType) ? errorType : apparentType;; + return isErrorType(apparentType) ? errorType : apparentType; } prop = getPropertyOfType(apparentType, right.escapedText, /*skipObjectFunctionPropertyAugment*/ false, /*includeTypeOnlyMembers*/ node.kind === SyntaxKind.QualifiedName); } From 2780135a6cb2efbe99eb8a167c40ab948aa51ffd Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 11 May 2022 09:23:40 -0700 Subject: [PATCH 4/4] undefined -> false --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8d46ef18fbbf1..6ba0d793501a8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16210,7 +16210,7 @@ namespace ts { // the `exports` lookup process that only looks up namespace members which is used for most type references const mergedResolvedSymbol = getMergedSymbol(resolveSymbol(currentNamespace)); const next = node.isTypeOf - ? getPropertyOfType(getTypeOfSymbol(mergedResolvedSymbol), current.escapedText, /*skipObjectFunctionPropertyAugment*/ undefined, /*includeTypeOnlyMembers*/ true) + ? getPropertyOfType(getTypeOfSymbol(mergedResolvedSymbol), current.escapedText, /*skipObjectFunctionPropertyAugment*/ false, /*includeTypeOnlyMembers*/ true) : getSymbol(getExportsOfSymbol(mergedResolvedSymbol), current.escapedText, meaning); if (!next) { error(current, Diagnostics.Namespace_0_has_no_exported_member_1, getFullyQualifiedName(currentNamespace), declarationNameToString(current));