Skip to content

Commit d991f49

Browse files
committed
Awesome module reference types
1 parent 76fb654 commit d991f49

15 files changed

+362
-45
lines changed

src/compiler/checker.ts

+191-33
Large diffs are not rendered by default.

src/compiler/declarationEmitter.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,7 @@ namespace ts {
410410
emitType(type);
411411
}
412412

413-
function emitType(type: TypeNode | Identifier | QualifiedName) {
413+
function emitType(type: TypeNode | EntityName) {
414414
switch (type.kind) {
415415
case SyntaxKind.AnyKeyword:
416416
case SyntaxKind.StringKeyword:
@@ -456,14 +456,25 @@ namespace ts {
456456
return emitEntityName(<Identifier>type);
457457
case SyntaxKind.QualifiedName:
458458
return emitEntityName(<QualifiedName>type);
459+
case SyntaxKind.ModuleReference:
460+
return emitModuleReference(<ModuleReferenceNode>type);
459461
case SyntaxKind.TypePredicate:
460462
return emitTypePredicate(<TypePredicateNode>type);
461463
}
462464

465+
function emitModuleReference(node: ModuleReferenceNode) {
466+
write("module(");
467+
emitType(node.type);
468+
write(")");
469+
}
470+
463471
function writeEntityName(entityName: EntityName | Expression) {
464472
if (entityName.kind === SyntaxKind.Identifier) {
465473
writeTextOfNode(currentText, entityName);
466474
}
475+
else if (entityName.kind === SyntaxKind.ModuleReference) {
476+
emitType(entityName as ModuleReferenceNode);
477+
}
467478
else {
468479
const left = entityName.kind === SyntaxKind.QualifiedName ? (<QualifiedName>entityName).left : (<PropertyAccessExpression>entityName).expression;
469480
const right = entityName.kind === SyntaxKind.QualifiedName ? (<QualifiedName>entityName).right : (<PropertyAccessExpression>entityName).name;

src/compiler/diagnosticMessages.json

+8
Original file line numberDiff line numberDiff line change
@@ -2200,6 +2200,14 @@
22002200
"category": "Error",
22012201
"code": 2713
22022202
},
2203+
"Module name '{0}' could not be resolved during type checking. Consider explicitly adding a reference to '{0}'.": {
2204+
"category": "Error",
2205+
"code": 2714
2206+
},
2207+
"Module type reference refers to multiple modules, and cannot be used in this context.": {
2208+
"category": "Error",
2209+
"code": 2715
2210+
},
22032211

22042212
"Import declaration '{0}' is using private name '{1}'.": {
22052213
"category": "Error",

src/compiler/emitter.ts

+9
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,8 @@ namespace ts {
492492
// Names
493493
case SyntaxKind.QualifiedName:
494494
return emitQualifiedName(<QualifiedName>node);
495+
case SyntaxKind.ModuleReference:
496+
return emitModuleReferenceNode(<ModuleReferenceNode>node);
495497
case SyntaxKind.ComputedPropertyName:
496498
return emitComputedPropertyName(<ComputedPropertyName>node);
497499

@@ -879,6 +881,13 @@ namespace ts {
879881
}
880882
}
881883

884+
function emitModuleReferenceNode(node: ModuleReferenceNode) {
885+
writeToken(SyntaxKind.ModuleKeyword, node.pos);
886+
write("(");
887+
emit(node.type);
888+
write(")");
889+
}
890+
882891
function emitComputedPropertyName(node: ComputedPropertyName) {
883892
write("[");
884893
emitExpression(node.expression);

src/compiler/factory.ts

+19-3
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,18 @@ namespace ts {
202202

203203
// Names
204204

205+
export function createModuleReference(type: TypeNode) {
206+
const node = <ModuleReferenceNode>createSynthesizedNode(SyntaxKind.ModuleReference);
207+
node.type = type;
208+
return node;
209+
}
210+
211+
export function updateModuleReference(node: ModuleReferenceNode, type: TypeNode) {
212+
return node.type !== type
213+
? updateNode(createModuleReference(type), node)
214+
: node;
215+
}
216+
205217
export function createQualifiedName(left: EntityName, right: string | Identifier) {
206218
const node = <QualifiedName>createSynthesizedNode(SyntaxKind.QualifiedName);
207219
node.left = left;
@@ -2437,7 +2449,7 @@ namespace ts {
24372449
function asName(name: string | PropertyName): PropertyName;
24382450
function asName(name: string | EntityName): EntityName;
24392451
function asName(name: string | Identifier | ThisTypeNode): Identifier | ThisTypeNode;
2440-
function asName(name: string | Identifier | BindingName | PropertyName | QualifiedName | ThisTypeNode) {
2452+
function asName(name: string | Identifier | BindingName | PropertyName | QualifiedName | ThisTypeNode | ModuleReferenceNode) {
24412453
return typeof name === "string" ? createIdentifier(name) : name;
24422454
}
24432455

@@ -2867,9 +2879,12 @@ namespace ts {
28672879
right.escapedText = jsxFactory.right.escapedText;
28682880
return createPropertyAccess(left, right);
28692881
}
2870-
else {
2882+
else if (isIdentifier(jsxFactory)) {
28712883
return createReactNamespace(unescapeLeadingUnderscores(jsxFactory.escapedText), parent);
28722884
}
2885+
else {
2886+
Debug.fail("An entity name which contains a module reference should never be a jsx factory.");
2887+
}
28732888
}
28742889

28752890
function createJsxFactoryExpression(jsxFactoryEntity: EntityName, reactNamespace: string, parent: JsxOpeningLikeElement): Expression {
@@ -3180,7 +3195,8 @@ namespace ts {
31803195
return setTextRange(createPropertyAccess(left, right), node);
31813196
}
31823197
else {
3183-
return getMutableClone(node);
3198+
Debug.assert(node.kind !== SyntaxKind.ModuleReference, "A module reference is not allowed in any value positions");
3199+
return getMutableClone(node as Expression);
31843200
}
31853201
}
31863202

src/compiler/parser.ts

+26-3
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ namespace ts {
7171
case SyntaxKind.QualifiedName:
7272
return visitNode(cbNode, (<QualifiedName>node).left) ||
7373
visitNode(cbNode, (<QualifiedName>node).right);
74+
case SyntaxKind.ModuleReference:
75+
return visitNode(cbNode, (<ModuleReferenceNode>node).type);
7476
case SyntaxKind.TypeParameter:
7577
return visitNode(cbNode, (<TypeParameterDeclaration>node).name) ||
7678
visitNode(cbNode, (<TypeParameterDeclaration>node).constraint) ||
@@ -1953,8 +1955,26 @@ namespace ts {
19531955
return createMissingList<T>();
19541956
}
19551957

1958+
function parseModuleTypeOperator() {
1959+
const node = createNode(SyntaxKind.ModuleReference) as ModuleReferenceNode;
1960+
parseExpected(SyntaxKind.ModuleKeyword);
1961+
parseExpected(SyntaxKind.OpenParenToken);
1962+
node.type = parseType();
1963+
parseExpected(SyntaxKind.CloseParenToken);
1964+
return finishNode(node);
1965+
}
1966+
19561967
function parseEntityName(allowReservedWords: boolean, diagnosticMessage?: DiagnosticMessage): EntityName {
1957-
let entity: EntityName = allowReservedWords ? parseIdentifierName() : parseIdentifier(diagnosticMessage);
1968+
let entity: EntityName;
1969+
if (token() === SyntaxKind.ModuleKeyword) {
1970+
entity = parseModuleTypeOperator();
1971+
}
1972+
else if (allowReservedWords) {
1973+
entity = parseIdentifierName();
1974+
}
1975+
else {
1976+
entity = parseIdentifier(diagnosticMessage);
1977+
}
19581978
let dotPos = scanner.getStartPos();
19591979
while (parseOptional(SyntaxKind.DotToken)) {
19601980
if (token() === SyntaxKind.LessThanToken) {
@@ -2691,6 +2711,7 @@ namespace ts {
26912711
return parseTupleType();
26922712
case SyntaxKind.OpenParenToken:
26932713
return parseParenthesizedType();
2714+
case SyntaxKind.ModuleKeyword: // Intentional fallthrough to default
26942715
default:
26952716
return parseTypeReference();
26962717
}
@@ -2721,6 +2742,7 @@ namespace ts {
27212742
case SyntaxKind.FalseKeyword:
27222743
case SyntaxKind.ObjectKeyword:
27232744
case SyntaxKind.AsteriskToken:
2745+
case SyntaxKind.ModuleKeyword:
27242746
return true;
27252747
case SyntaxKind.MinusToken:
27262748
return lookAhead(nextTokenIsNumericLiteral);
@@ -6719,7 +6741,7 @@ namespace ts {
67196741

67206742
function escapedTextsEqual(a: EntityName, b: EntityName): boolean {
67216743
while (!ts.isIdentifier(a) || !ts.isIdentifier(b)) {
6722-
if (!ts.isIdentifier(a) && !ts.isIdentifier(b) && a.right.escapedText === b.right.escapedText) {
6744+
if (ts.isQualifiedName(a) && ts.isQualifiedName(b) && a.right.escapedText === b.right.escapedText) {
67236745
a = a.left;
67246746
b = b.left;
67256747
}
@@ -6742,7 +6764,8 @@ namespace ts {
67426764
if (canParseTag) {
67436765
const child = tryParseChildTag(target);
67446766
if (child && child.kind === SyntaxKind.JSDocParameterTag &&
6745-
(ts.isIdentifier(child.name) || !escapedTextsEqual(name, child.name.left))) {
6767+
// JSDoc entity names can never have module references
6768+
(ts.isIdentifier(child.name) || !escapedTextsEqual(name, (child.name as QualifiedName).left))) {
67466769
return false;
67476770
}
67486771
return child;

src/compiler/types.ts

+17-1
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ namespace ts {
207207

208208
// Names
209209
QualifiedName,
210+
ModuleReference,
210211
ComputedPropertyName,
211212
// Signature elements
212213
TypeParameter,
@@ -604,14 +605,21 @@ namespace ts {
604605
| GeneratedIdentifierKind.Node;
605606
}
606607

608+
export interface ModuleReferenceNode extends TypeNode {
609+
kind: SyntaxKind.ModuleReference;
610+
type: TypeNode;
611+
/*@internal*/ jsdocDotPos?: number; // QualifiedName occurs in JSDoc-style generic: module("foo").<T>
612+
/*@internal*/ typeArguments?: NodeArray<TypeNode>; // Only defined on synthesized nodes. Though not syntactically valid, used in emitting diagnostics.
613+
}
614+
607615
export interface QualifiedName extends Node {
608616
kind: SyntaxKind.QualifiedName;
609617
left: EntityName;
610618
right: Identifier;
611619
/*@internal*/ jsdocDotPos?: number; // QualifiedName occurs in JSDoc-style generic: Id1.Id2.<T>
612620
}
613621

614-
export type EntityName = Identifier | QualifiedName;
622+
export type EntityName = Identifier | QualifiedName | ModuleReferenceNode;
615623

616624
export type PropertyName = Identifier | StringLiteral | NumericLiteral | ComputedPropertyName;
617625

@@ -3140,6 +3148,7 @@ namespace ts {
31403148
NonPrimitive = 1 << 24, // intrinsic object type
31413149
/* @internal */
31423150
JsxAttributes = 1 << 25, // Jsx attributes type
3151+
ModuleReference = 1 << 26, // module(T)
31433152

31443153
/* @internal */
31453154
Nullable = Undefined | Null,
@@ -3389,9 +3398,16 @@ namespace ts {
33893398

33903399
// keyof T types (TypeFlags.Index)
33913400
export interface IndexType extends Type {
3401+
__indexTypeBrand: any;
33923402
type: TypeVariable | UnionOrIntersectionType;
33933403
}
33943404

3405+
// module(T) types (TypeFlags.ModuleReference)
3406+
export interface ModuleReferenceType extends Type {
3407+
__moduleReferenceBrand: any;
3408+
type: Type;
3409+
}
3410+
33953411
export const enum SignatureKind {
33963412
Call,
33973413
Construct,

src/compiler/utilities.ts

+11-4
Original file line numberDiff line numberDiff line change
@@ -2511,11 +2511,17 @@ namespace ts {
25112511
};
25122512
}
25132513

2514-
export function getResolvedExternalModuleName(host: EmitHost, file: SourceFile): string {
2514+
export interface ExternalNameResolutionHost {
2515+
getCanonicalFileName(name: string): string;
2516+
getCommonSourceDirectory(): string;
2517+
getCurrentDirectory(): string;
2518+
}
2519+
2520+
export function getResolvedExternalModuleName(host: ExternalNameResolutionHost, file: SourceFile): string {
25152521
return file.moduleName || getExternalModuleNameFromPath(host, file.fileName);
25162522
}
25172523

2518-
export function getExternalModuleNameFromDeclaration(host: EmitHost, resolver: EmitResolver, declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration): string {
2524+
export function getExternalModuleNameFromDeclaration(host: ExternalNameResolutionHost, resolver: EmitResolver, declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration): string {
25192525
const file = resolver.getExternalModuleFileFromDeclaration(declaration);
25202526
if (!file || file.isDeclarationFile) {
25212527
return undefined;
@@ -2526,7 +2532,7 @@ namespace ts {
25262532
/**
25272533
* Resolves a local path to a path which is absolute to the base of the emit
25282534
*/
2529-
export function getExternalModuleNameFromPath(host: EmitHost, fileName: string): string {
2535+
export function getExternalModuleNameFromPath(host: ExternalNameResolutionHost, fileName: string): string {
25302536
const getCanonicalFileName = (f: string) => host.getCanonicalFileName(f);
25312537
const dir = toPath(host.getCommonSourceDirectory(), host.getCurrentDirectory(), getCanonicalFileName);
25322538
const filePath = getNormalizedAbsolutePath(fileName, host.getCurrentDirectory());
@@ -4811,7 +4817,8 @@ namespace ts {
48114817
|| kind === SyntaxKind.UndefinedKeyword
48124818
|| kind === SyntaxKind.NullKeyword
48134819
|| kind === SyntaxKind.NeverKeyword
4814-
|| kind === SyntaxKind.ExpressionWithTypeArguments;
4820+
|| kind === SyntaxKind.ExpressionWithTypeArguments
4821+
|| kind === SyntaxKind.ModuleReference;
48154822
}
48164823

48174824
/**

src/compiler/visitor.ts

+4
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,10 @@ namespace ts {
233233
return updateComputedPropertyName(<ComputedPropertyName>node,
234234
visitNode((<ComputedPropertyName>node).expression, visitor, isExpression));
235235

236+
case SyntaxKind.ModuleReference:
237+
return updateModuleReference(<ModuleReferenceNode>node,
238+
visitNode((<ModuleReferenceNode>node).type, visitor, isTypeNode));
239+
236240
// Signature elements
237241

238242
case SyntaxKind.TypeParameter:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// @declaration: true
2+
// @lib: es6
3+
declare module "foo" {
4+
interface Point {
5+
x: number;
6+
y: number;
7+
}
8+
export = Point;
9+
}
10+
const x: module("foo") = { x: 0, y: 0 };
11+
12+
declare module "foo2" {
13+
namespace Bar {
14+
interface I {
15+
a: string;
16+
b: number;
17+
}
18+
}
19+
20+
namespace Baz {
21+
interface J {
22+
a: number;
23+
b: string;
24+
}
25+
}
26+
27+
class Bar {
28+
item: Bar.I;
29+
constructor(input: Baz.J);
30+
}
31+
}
32+
33+
let y: module("foo2").Bar.I = { a: "", b: 0 };
34+
35+
class Bar2 {
36+
item: {a: string, b: number, c: object};
37+
constructor(input?: any) {}
38+
}
39+
40+
let shim: typeof module("foo2") = {
41+
Bar: Bar2
42+
};
43+
44+
declare module "a" {
45+
export const f: number;
46+
export const g: string;
47+
}
48+
49+
50+
declare module "b" {
51+
export const f: string;
52+
export const g: number;
53+
}
54+
55+
type Modules<T extends string> = {[K in T]: module(K)}[T];
56+
57+
let z: Modules<"a" | "b"> = {
58+
f: 0,
59+
g: ""
60+
};
61+
62+
z = {
63+
f: "",
64+
g: 0
65+
};

tests/cases/conformance/types/modules/moduleTypeAmbientPattern.ts

Whitespace-only changes.

tests/cases/conformance/types/modules/moduleTypeExternal.ts

Whitespace-only changes.

tests/cases/conformance/types/modules/moduleTypePromise.ts

Whitespace-only changes.

tests/cases/conformance/types/modules/moduleTypeSyntheticDefault.ts

Whitespace-only changes.

tests/cases/conformance/types/modules/moduleTypeUnion.ts

Whitespace-only changes.

0 commit comments

Comments
 (0)