Skip to content

Commit f9aa1b6

Browse files
Improve declaration emit type safety.
Signed-off-by: Titian Cernicova-Dragomir <tcernicovad1@bloomberg.net>
1 parent d91c282 commit f9aa1b6

File tree

8 files changed

+152
-240
lines changed

8 files changed

+152
-240
lines changed

src/compiler/transformers/declarations.ts

+45-73
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
ConstructorTypeNode,
2424
ConstructSignatureDeclaration,
2525
contains,
26+
CoreEmitResolver,
2627
createDiagnosticForNode,
2728
createDiagnosticForRange,
2829
createEmptyExports,
@@ -46,7 +47,6 @@ import {
4647
EnumDeclaration,
4748
ExportAssignment,
4849
ExportDeclaration,
49-
Expression,
5050
ExpressionWithTypeArguments,
5151
factory,
5252
FileReference,
@@ -139,7 +139,7 @@ import {
139139
isMethodSignature,
140140
isModifier,
141141
isModuleDeclaration,
142-
IsolatedEmitResolver,
142+
IsolatedTransformationContext,
143143
isOmittedExpression,
144144
isPrivateIdentifier,
145145
isPropertySignature,
@@ -167,7 +167,6 @@ import {
167167
LateBoundDeclaration,
168168
LateVisibilityPaintedStatement,
169169
length,
170-
LocalInferenceResolver,
171170
map,
172171
mapDefined,
173172
MethodDeclaration,
@@ -293,7 +292,7 @@ const declarationEmitNodeBuilderFlags = NodeBuilderFlags.MultilineObjectLiterals
293292
*
294293
* @internal
295294
*/
296-
export function transformDeclarations(context: TransformationContext, _useTscEmit = true) {
295+
export function transformDeclarations(context: (TransformationContext & { useLocalInferenceTypePrint?: false; }) | IsolatedTransformationContext) {
297296
const throwDiagnostic = () => Debug.fail("Diagnostic emitted without context");
298297
let getSymbolAccessibilityDiagnostic: GetSymbolAccessibilityDiagnostic = throwDiagnostic;
299298
let needsDeclare = true;
@@ -318,108 +317,80 @@ export function transformDeclarations(context: TransformationContext, _useTscEmi
318317
let refs: Map<NodeId, SourceFile>;
319318
let libs: Map<string, boolean>;
320319
let emittedImports: readonly AnyImportSyntax[] | undefined; // must be declared in container so it can be `undefined` while transformer's first pass
321-
const { localInferenceResolver, isolatedDeclarations, host, resolver, symbolTracker, ensureNoInitializer, useTscEmit } = createTransformerServices();
320+
const { localInferenceResolver, isolatedDeclarations, host, resolver, symbolTracker, ensureNoInitializer, useLocalInferenceTypePrint } = createTransformerServices();
322321
const options = context.getCompilerOptions();
323322
const { noResolve, stripInternal } = options;
324323
return transformRoot;
325324

326-
function createTransformerServices(): {
327-
isolatedDeclarations: true;
328-
useTscEmit: false;
329-
resolver: IsolatedEmitResolver;
330-
localInferenceResolver: LocalInferenceResolver;
331-
host: undefined;
332-
symbolTracker: undefined;
333-
ensureNoInitializer: (node: CanHaveLiteralInitializer) => Expression | undefined;
334-
} | {
335-
isolatedDeclarations: true;
336-
useTscEmit: true;
337-
resolver: EmitResolver;
338-
localInferenceResolver: LocalInferenceResolver;
339-
host: EmitHost;
340-
symbolTracker: SymbolTracker;
341-
ensureNoInitializer: (node: CanHaveLiteralInitializer) => Expression | undefined;
342-
} | {
343-
isolatedDeclarations: false;
344-
useTscEmit: false;
345-
resolver: EmitResolver;
346-
localInferenceResolver: undefined;
347-
host: EmitHost;
348-
symbolTracker: SymbolTracker;
349-
ensureNoInitializer: (node: CanHaveLiteralInitializer) => Expression | undefined;
350-
} {
351-
const { isolatedDeclarations, resolver: localInferenceResolver } = createLocalInferenceResolver({
352-
ensureParameter,
353-
context,
354-
visitDeclarationSubtree,
355-
setEnclosingDeclarations(node) {
356-
const oldNode = enclosingDeclaration;
357-
enclosingDeclaration = node;
358-
return oldNode;
359-
},
360-
checkEntityNameVisibility(name, container) {
361-
return checkEntityNameVisibility(name, container ?? enclosingDeclaration);
362-
},
363-
});
325+
function createTransformerServices() {
326+
const isolatedDeclarations = context.getCompilerOptions().isolatedDeclarations;
364327

365328
if (isolatedDeclarations) {
366-
if (!_useTscEmit) {
367-
const resolver: IsolatedEmitResolver = context.getEmitResolver();
329+
const localInferenceResolver = createLocalInferenceResolver({
330+
ensureParameter,
331+
context,
332+
visitDeclarationSubtree,
333+
setEnclosingDeclarations(node) {
334+
const oldNode = enclosingDeclaration;
335+
enclosingDeclaration = node;
336+
return oldNode;
337+
},
338+
checkEntityNameVisibility(name, container) {
339+
return checkEntityNameVisibility(name, container ?? enclosingDeclaration);
340+
},
341+
});
342+
if (context.useLocalInferenceTypePrint) {
343+
const resolver: CoreEmitResolver = context.getEmitResolver();
368344
// Ideally nothing should require the symbol tracker in isolated declarations mode.
369345
// createLiteralConstValue is the one exception
370346
const emptySymbolTracker = {};
371347
return {
372348
isolatedDeclarations,
373-
useTscEmit: false,
349+
useLocalInferenceTypePrint: true,
374350
resolver,
375351
localInferenceResolver,
376352
symbolTracker: undefined,
377353
host: undefined,
378-
ensureNoInitializer: (node: CanHaveLiteralInitializer) => {
379-
if (shouldPrintWithInitializer(node)) {
380-
return resolver.createLiteralConstValue(getParseTreeNode(node) as CanHaveLiteralInitializer, emptySymbolTracker); // TODO: Make safe
381-
}
382-
return undefined;
383-
},
384-
};
354+
ensureNoInitializer: createEnsureNoInitializer(emptySymbolTracker),
355+
} as const;
385356
}
386357
else {
387358
const host = context.getEmitHost();
388359
const resolver: EmitResolver = context.getEmitResolver();
389360
const symbolTracker = createSymbolTracker(resolver, host);
390361
return {
391362
isolatedDeclarations,
392-
useTscEmit: true,
363+
useLocalInferenceTypePrint: false,
393364
resolver,
394365
localInferenceResolver,
395366
symbolTracker,
396367
host,
397-
ensureNoInitializer: (node: CanHaveLiteralInitializer) => {
398-
if (shouldPrintWithInitializer(node)) {
399-
return resolver.createLiteralConstValue(getParseTreeNode(node) as CanHaveLiteralInitializer, symbolTracker); // TODO: Make safe
400-
}
401-
return undefined;
402-
},
403-
};
368+
ensureNoInitializer: createEnsureNoInitializer(symbolTracker),
369+
} as const;
404370
}
405371
}
406372
else {
373+
Debug.assert(!context.useLocalInferenceTypePrint);
407374
const host = context.getEmitHost();
408375
const resolver = context.getEmitResolver();
409376
const symbolTracker: SymbolTracker = createSymbolTracker(resolver, host);
410377
return {
411-
isolatedDeclarations,
412-
useTscEmit: false,
413-
localInferenceResolver,
378+
isolatedDeclarations: false,
379+
useLocalInferenceTypePrint: false,
380+
localInferenceResolver: undefined,
414381
resolver,
415382
symbolTracker,
416383
host,
417-
ensureNoInitializer: (node: CanHaveLiteralInitializer) => {
418-
if (shouldPrintWithInitializer(node)) {
419-
return resolver.createLiteralConstValue(getParseTreeNode(node) as CanHaveLiteralInitializer, symbolTracker); // TODO: Make safe
420-
}
421-
return undefined;
422-
},
384+
ensureNoInitializer: createEnsureNoInitializer(symbolTracker),
385+
} as const;
386+
}
387+
388+
function createEnsureNoInitializer(symbolTracker: SymbolTracker) {
389+
return function ensureNoInitializer(node: CanHaveLiteralInitializer) {
390+
if (shouldPrintWithInitializer(node)) {
391+
return resolver.createLiteralConstValue(getParseTreeNode(node) as CanHaveLiteralInitializer, symbolTracker); // TODO: Make safe
392+
}
393+
return undefined;
423394
};
424395
}
425396
}
@@ -659,6 +630,7 @@ export function transformDeclarations(context: TransformationContext, _useTscEmi
659630
libs = new Map();
660631
existingTypeReferencesSources = node.sourceFiles;
661632
let hasNoDefaultLib = false;
633+
Debug.assert(!isolatedDeclarations, "Bundles are not supported in isolated declarations");
662634
const bundle = factory.createBundle(
663635
map(node.sourceFiles, sourceFile => {
664636
if (sourceFile.isDeclarationFile) return undefined!; // Omit declaration files from bundle results, too // TODO: GH#18217
@@ -681,7 +653,7 @@ export function transformDeclarations(context: TransformationContext, _useTscEmi
681653
sourceFile,
682654
[factory.createModuleDeclaration(
683655
[factory.createModifier(SyntaxKind.DeclareKeyword)],
684-
factory.createStringLiteral(getResolvedExternalModuleName(context.getEmitHost(), sourceFile)),
656+
factory.createStringLiteral(getResolvedExternalModuleName(host, sourceFile)),
685657
factory.createModuleBlock(setTextRange(factory.createNodeArray(transformAndReplaceLatePaintedStatements(statements)), sourceFile.statements)),
686658
)],
687659
/*isDeclarationFile*/ true,
@@ -952,7 +924,7 @@ export function transformDeclarations(context: TransformationContext, _useTscEmi
952924
}
953925
if (isolatedDeclarations) {
954926
const { typeNode, isInvalid } = localInferenceResolver.fromInitializer(node, type, currentSourceFile);
955-
if (!useTscEmit || isInvalid) {
927+
if (useLocalInferenceTypePrint || isInvalid) {
956928
return typeNode;
957929
}
958930
}
@@ -1121,7 +1093,7 @@ export function transformDeclarations(context: TransformationContext, _useTscEmi
11211093
if (isBundledEmit) {
11221094
// Bundle emit not supported for isolatedDeclarations
11231095
if (!isolatedDeclarations) {
1124-
const newName = getExternalModuleNameFromDeclaration(context.getEmitHost(), resolver, parent);
1096+
const newName = getExternalModuleNameFromDeclaration(host, resolver, parent);
11251097
if (newName) {
11261098
return factory.createStringLiteral(newName);
11271099
}

src/compiler/transformers/declarations/emitResolver.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
bindSourceFileForDeclarationEmit,
33
ComputedPropertyName,
4+
CoreEmitResolver,
45
createEntityVisibilityChecker,
56
createEvaluator,
67
Debug,
@@ -39,7 +40,6 @@ import {
3940
isIdentifier,
4041
isInfinityOrNaNString,
4142
isNumericLiteral,
42-
IsolatedEmitResolver,
4343
isPrefixUnaryExpression,
4444
isPrimitiveLiteralValue,
4545
isPropertyAccessExpression,
@@ -70,7 +70,7 @@ import {
7070
} from "../../_namespaces/ts";
7171

7272
/** @internal */
73-
export function createEmitDeclarationResolver(file: SourceFile): IsolatedEmitResolver {
73+
export function createEmitDeclarationResolver(file: SourceFile): CoreEmitResolver {
7474
const { getNodeLinks, resolveMemberKey, resolveName, resolveEntityName } = bindSourceFileForDeclarationEmit(file);
7575

7676
const { isEntityNameVisible } = createEntityVisibilityChecker({
@@ -394,4 +394,4 @@ export function createEmitDeclarationResolver(file: SourceFile): IsolatedEmitRes
394394

395395
return false;
396396
}
397-
}
397+
}

src/compiler/transformers/declarations/localInferenceResolver.ts

+23-27
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
isMethodOrAccessor,
3232
isNoSubstitutionTemplateLiteral,
3333
isNumericLiteral,
34+
IsolatedTransformationContext,
3435
isOmittedExpression,
3536
isOptionalDeclaration,
3637
isParameter,
@@ -120,44 +121,39 @@ export function createLocalInferenceResolver({
120121
visitDeclarationSubtree(input: Node): VisitResult<Node | undefined>;
121122
checkEntityNameVisibility(name: EntityNameOrEntityNameExpression, container?: Node): void;
122123
ensureParameter(p: ParameterDeclaration): ParameterDeclaration;
123-
context: TransformationContext;
124-
}): { resolver: LocalInferenceResolver; isolatedDeclarations: true; } | { resolver: undefined; isolatedDeclarations: false; } {
124+
context: IsolatedTransformationContext | TransformationContext;
125+
}): LocalInferenceResolver {
125126
let currentSourceFile: SourceFile;
126127
const options = context.getCompilerOptions();
127128
const resolver = context.getEmitResolver();
128-
if (!options.isolatedDeclarations) {
129-
return { resolver: undefined, isolatedDeclarations: false };
130-
}
129+
Debug.assert(options.isolatedDeclarations, "createLocalInferenceResolver can only be called when isolatedDeclarations is true");
131130
const { factory } = context;
132131
let inferenceContext: { isInvalid: boolean; disableErrors: boolean; } = undefined!;
133132
const strictNullChecks = !!options.strict || !!options.strictNullChecks;
134133

135134
return {
136-
resolver: {
137-
fromInitializer(node: HasInferredType | ExportAssignment, type: TypeNode | undefined, sourceFile: SourceFile) {
138-
const oldSourceFile = currentSourceFile;
139-
const hasExistingContext = inferenceContext !== undefined;
140-
if (!hasExistingContext) {
141-
inferenceContext = { isInvalid: false, disableErrors: false };
142-
}
143-
currentSourceFile = sourceFile;
144-
try {
145-
const typeNode = localInferenceFromInitializer(node, type);
146-
if (typeNode !== undefined) {
147-
return { isInvalid: inferenceContext.isInvalid, typeNode };
148-
}
149-
return { isInvalid: true, typeNode: invalid(node) };
135+
fromInitializer(node: HasInferredType | ExportAssignment, type: TypeNode | undefined, sourceFile: SourceFile) {
136+
const oldSourceFile = currentSourceFile;
137+
const hasExistingContext = inferenceContext !== undefined;
138+
if (!hasExistingContext) {
139+
inferenceContext = { isInvalid: false, disableErrors: false };
140+
}
141+
currentSourceFile = sourceFile;
142+
try {
143+
const typeNode = localInferenceFromInitializer(node, type);
144+
if (typeNode !== undefined) {
145+
return { isInvalid: inferenceContext.isInvalid, typeNode };
150146
}
151-
finally {
152-
currentSourceFile = oldSourceFile;
153-
if (!hasExistingContext) {
154-
inferenceContext = undefined!;
155-
}
147+
return { isInvalid: true, typeNode: invalid(node) };
148+
}
149+
finally {
150+
currentSourceFile = oldSourceFile;
151+
if (!hasExistingContext) {
152+
inferenceContext = undefined!;
156153
}
157-
},
158-
makeInvalidType,
154+
}
159155
},
160-
isolatedDeclarations: options.isolatedDeclarations,
156+
makeInvalidType,
161157
};
162158
function hasParseError(node: Node) {
163159
return !!(node.flags & NodeFlags.ThisNodeHasError);

0 commit comments

Comments
 (0)