Skip to content

Commit

Permalink
Improve declaration emit type safety.
Browse files Browse the repository at this point in the history
Signed-off-by: Titian Cernicova-Dragomir <tcernicovad1@bloomberg.net>
  • Loading branch information
dragomirtitian committed Dec 5, 2023
1 parent d91c282 commit f9aa1b6
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 240 deletions.
118 changes: 45 additions & 73 deletions src/compiler/transformers/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
ConstructorTypeNode,
ConstructSignatureDeclaration,
contains,
CoreEmitResolver,
createDiagnosticForNode,
createDiagnosticForRange,
createEmptyExports,
Expand All @@ -46,7 +47,6 @@ import {
EnumDeclaration,
ExportAssignment,
ExportDeclaration,
Expression,
ExpressionWithTypeArguments,
factory,
FileReference,
Expand Down Expand Up @@ -139,7 +139,7 @@ import {
isMethodSignature,
isModifier,
isModuleDeclaration,
IsolatedEmitResolver,
IsolatedTransformationContext,
isOmittedExpression,
isPrivateIdentifier,
isPropertySignature,
Expand Down Expand Up @@ -167,7 +167,6 @@ import {
LateBoundDeclaration,
LateVisibilityPaintedStatement,
length,
LocalInferenceResolver,
map,
mapDefined,
MethodDeclaration,
Expand Down Expand Up @@ -293,7 +292,7 @@ const declarationEmitNodeBuilderFlags = NodeBuilderFlags.MultilineObjectLiterals
*
* @internal
*/
export function transformDeclarations(context: TransformationContext, _useTscEmit = true) {
export function transformDeclarations(context: (TransformationContext & { useLocalInferenceTypePrint?: false; }) | IsolatedTransformationContext) {
const throwDiagnostic = () => Debug.fail("Diagnostic emitted without context");
let getSymbolAccessibilityDiagnostic: GetSymbolAccessibilityDiagnostic = throwDiagnostic;
let needsDeclare = true;
Expand All @@ -318,108 +317,80 @@ export function transformDeclarations(context: TransformationContext, _useTscEmi
let refs: Map<NodeId, SourceFile>;
let libs: Map<string, boolean>;
let emittedImports: readonly AnyImportSyntax[] | undefined; // must be declared in container so it can be `undefined` while transformer's first pass
const { localInferenceResolver, isolatedDeclarations, host, resolver, symbolTracker, ensureNoInitializer, useTscEmit } = createTransformerServices();
const { localInferenceResolver, isolatedDeclarations, host, resolver, symbolTracker, ensureNoInitializer, useLocalInferenceTypePrint } = createTransformerServices();
const options = context.getCompilerOptions();
const { noResolve, stripInternal } = options;
return transformRoot;

function createTransformerServices(): {
isolatedDeclarations: true;
useTscEmit: false;
resolver: IsolatedEmitResolver;
localInferenceResolver: LocalInferenceResolver;
host: undefined;
symbolTracker: undefined;
ensureNoInitializer: (node: CanHaveLiteralInitializer) => Expression | undefined;
} | {
isolatedDeclarations: true;
useTscEmit: true;
resolver: EmitResolver;
localInferenceResolver: LocalInferenceResolver;
host: EmitHost;
symbolTracker: SymbolTracker;
ensureNoInitializer: (node: CanHaveLiteralInitializer) => Expression | undefined;
} | {
isolatedDeclarations: false;
useTscEmit: false;
resolver: EmitResolver;
localInferenceResolver: undefined;
host: EmitHost;
symbolTracker: SymbolTracker;
ensureNoInitializer: (node: CanHaveLiteralInitializer) => Expression | undefined;
} {
const { isolatedDeclarations, resolver: localInferenceResolver } = createLocalInferenceResolver({
ensureParameter,
context,
visitDeclarationSubtree,
setEnclosingDeclarations(node) {
const oldNode = enclosingDeclaration;
enclosingDeclaration = node;
return oldNode;
},
checkEntityNameVisibility(name, container) {
return checkEntityNameVisibility(name, container ?? enclosingDeclaration);
},
});
function createTransformerServices() {
const isolatedDeclarations = context.getCompilerOptions().isolatedDeclarations;

if (isolatedDeclarations) {
if (!_useTscEmit) {
const resolver: IsolatedEmitResolver = context.getEmitResolver();
const localInferenceResolver = createLocalInferenceResolver({
ensureParameter,
context,
visitDeclarationSubtree,
setEnclosingDeclarations(node) {
const oldNode = enclosingDeclaration;
enclosingDeclaration = node;
return oldNode;
},
checkEntityNameVisibility(name, container) {
return checkEntityNameVisibility(name, container ?? enclosingDeclaration);
},
});
if (context.useLocalInferenceTypePrint) {
const resolver: CoreEmitResolver = context.getEmitResolver();
// Ideally nothing should require the symbol tracker in isolated declarations mode.
// createLiteralConstValue is the one exception
const emptySymbolTracker = {};
return {
isolatedDeclarations,
useTscEmit: false,
useLocalInferenceTypePrint: true,
resolver,
localInferenceResolver,
symbolTracker: undefined,
host: undefined,
ensureNoInitializer: (node: CanHaveLiteralInitializer) => {
if (shouldPrintWithInitializer(node)) {
return resolver.createLiteralConstValue(getParseTreeNode(node) as CanHaveLiteralInitializer, emptySymbolTracker); // TODO: Make safe
}
return undefined;
},
};
ensureNoInitializer: createEnsureNoInitializer(emptySymbolTracker),
} as const;
}
else {
const host = context.getEmitHost();
const resolver: EmitResolver = context.getEmitResolver();
const symbolTracker = createSymbolTracker(resolver, host);
return {
isolatedDeclarations,
useTscEmit: true,
useLocalInferenceTypePrint: false,
resolver,
localInferenceResolver,
symbolTracker,
host,
ensureNoInitializer: (node: CanHaveLiteralInitializer) => {
if (shouldPrintWithInitializer(node)) {
return resolver.createLiteralConstValue(getParseTreeNode(node) as CanHaveLiteralInitializer, symbolTracker); // TODO: Make safe
}
return undefined;
},
};
ensureNoInitializer: createEnsureNoInitializer(symbolTracker),
} as const;
}
}
else {
Debug.assert(!context.useLocalInferenceTypePrint);
const host = context.getEmitHost();
const resolver = context.getEmitResolver();
const symbolTracker: SymbolTracker = createSymbolTracker(resolver, host);
return {
isolatedDeclarations,
useTscEmit: false,
localInferenceResolver,
isolatedDeclarations: false,
useLocalInferenceTypePrint: false,
localInferenceResolver: undefined,
resolver,
symbolTracker,
host,
ensureNoInitializer: (node: CanHaveLiteralInitializer) => {
if (shouldPrintWithInitializer(node)) {
return resolver.createLiteralConstValue(getParseTreeNode(node) as CanHaveLiteralInitializer, symbolTracker); // TODO: Make safe
}
return undefined;
},
ensureNoInitializer: createEnsureNoInitializer(symbolTracker),
} as const;
}

function createEnsureNoInitializer(symbolTracker: SymbolTracker) {
return function ensureNoInitializer(node: CanHaveLiteralInitializer) {
if (shouldPrintWithInitializer(node)) {
return resolver.createLiteralConstValue(getParseTreeNode(node) as CanHaveLiteralInitializer, symbolTracker); // TODO: Make safe
}
return undefined;
};
}
}
Expand Down Expand Up @@ -659,6 +630,7 @@ export function transformDeclarations(context: TransformationContext, _useTscEmi
libs = new Map();
existingTypeReferencesSources = node.sourceFiles;
let hasNoDefaultLib = false;
Debug.assert(!isolatedDeclarations, "Bundles are not supported in isolated declarations");
const bundle = factory.createBundle(
map(node.sourceFiles, sourceFile => {
if (sourceFile.isDeclarationFile) return undefined!; // Omit declaration files from bundle results, too // TODO: GH#18217
Expand All @@ -681,7 +653,7 @@ export function transformDeclarations(context: TransformationContext, _useTscEmi
sourceFile,
[factory.createModuleDeclaration(
[factory.createModifier(SyntaxKind.DeclareKeyword)],
factory.createStringLiteral(getResolvedExternalModuleName(context.getEmitHost(), sourceFile)),
factory.createStringLiteral(getResolvedExternalModuleName(host, sourceFile)),
factory.createModuleBlock(setTextRange(factory.createNodeArray(transformAndReplaceLatePaintedStatements(statements)), sourceFile.statements)),
)],
/*isDeclarationFile*/ true,
Expand Down Expand Up @@ -952,7 +924,7 @@ export function transformDeclarations(context: TransformationContext, _useTscEmi
}
if (isolatedDeclarations) {
const { typeNode, isInvalid } = localInferenceResolver.fromInitializer(node, type, currentSourceFile);
if (!useTscEmit || isInvalid) {
if (useLocalInferenceTypePrint || isInvalid) {
return typeNode;
}
}
Expand Down Expand Up @@ -1121,7 +1093,7 @@ export function transformDeclarations(context: TransformationContext, _useTscEmi
if (isBundledEmit) {
// Bundle emit not supported for isolatedDeclarations
if (!isolatedDeclarations) {
const newName = getExternalModuleNameFromDeclaration(context.getEmitHost(), resolver, parent);
const newName = getExternalModuleNameFromDeclaration(host, resolver, parent);
if (newName) {
return factory.createStringLiteral(newName);
}
Expand Down
6 changes: 3 additions & 3 deletions src/compiler/transformers/declarations/emitResolver.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
bindSourceFileForDeclarationEmit,
ComputedPropertyName,
CoreEmitResolver,
createEntityVisibilityChecker,
createEvaluator,
Debug,
Expand Down Expand Up @@ -39,7 +40,6 @@ import {
isIdentifier,
isInfinityOrNaNString,
isNumericLiteral,
IsolatedEmitResolver,
isPrefixUnaryExpression,
isPrimitiveLiteralValue,
isPropertyAccessExpression,
Expand Down Expand Up @@ -70,7 +70,7 @@ import {
} from "../../_namespaces/ts";

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

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

return false;
}
}
}
50 changes: 23 additions & 27 deletions src/compiler/transformers/declarations/localInferenceResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
isMethodOrAccessor,
isNoSubstitutionTemplateLiteral,
isNumericLiteral,
IsolatedTransformationContext,
isOmittedExpression,
isOptionalDeclaration,
isParameter,
Expand Down Expand Up @@ -120,44 +121,39 @@ export function createLocalInferenceResolver({
visitDeclarationSubtree(input: Node): VisitResult<Node | undefined>;
checkEntityNameVisibility(name: EntityNameOrEntityNameExpression, container?: Node): void;
ensureParameter(p: ParameterDeclaration): ParameterDeclaration;
context: TransformationContext;
}): { resolver: LocalInferenceResolver; isolatedDeclarations: true; } | { resolver: undefined; isolatedDeclarations: false; } {
context: IsolatedTransformationContext | TransformationContext;
}): LocalInferenceResolver {
let currentSourceFile: SourceFile;
const options = context.getCompilerOptions();
const resolver = context.getEmitResolver();
if (!options.isolatedDeclarations) {
return { resolver: undefined, isolatedDeclarations: false };
}
Debug.assert(options.isolatedDeclarations, "createLocalInferenceResolver can only be called when isolatedDeclarations is true");
const { factory } = context;
let inferenceContext: { isInvalid: boolean; disableErrors: boolean; } = undefined!;
const strictNullChecks = !!options.strict || !!options.strictNullChecks;

return {
resolver: {
fromInitializer(node: HasInferredType | ExportAssignment, type: TypeNode | undefined, sourceFile: SourceFile) {
const oldSourceFile = currentSourceFile;
const hasExistingContext = inferenceContext !== undefined;
if (!hasExistingContext) {
inferenceContext = { isInvalid: false, disableErrors: false };
}
currentSourceFile = sourceFile;
try {
const typeNode = localInferenceFromInitializer(node, type);
if (typeNode !== undefined) {
return { isInvalid: inferenceContext.isInvalid, typeNode };
}
return { isInvalid: true, typeNode: invalid(node) };
fromInitializer(node: HasInferredType | ExportAssignment, type: TypeNode | undefined, sourceFile: SourceFile) {
const oldSourceFile = currentSourceFile;
const hasExistingContext = inferenceContext !== undefined;
if (!hasExistingContext) {
inferenceContext = { isInvalid: false, disableErrors: false };
}
currentSourceFile = sourceFile;
try {
const typeNode = localInferenceFromInitializer(node, type);
if (typeNode !== undefined) {
return { isInvalid: inferenceContext.isInvalid, typeNode };
}
finally {
currentSourceFile = oldSourceFile;
if (!hasExistingContext) {
inferenceContext = undefined!;
}
return { isInvalid: true, typeNode: invalid(node) };
}
finally {
currentSourceFile = oldSourceFile;
if (!hasExistingContext) {
inferenceContext = undefined!;
}
},
makeInvalidType,
}
},
isolatedDeclarations: options.isolatedDeclarations,
makeInvalidType,
};
function hasParseError(node: Node) {
return !!(node.flags & NodeFlags.ThisNodeHasError);
Expand Down
Loading

0 comments on commit f9aa1b6

Please sign in to comment.