Skip to content

Commit

Permalink
Support class expressions in DTE
Browse files Browse the repository at this point in the history
Signed-off-by: Hana Joo <hanajoo@google.com>
  • Loading branch information
h-joo committed Oct 17, 2023
1 parent 6c2a8d3 commit a990f05
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 5 deletions.
1 change: 1 addition & 0 deletions external-declarations/fixed-tests.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
9008, // Declaration_emit_for_this_file_requires_adding_a_type_reference_directive_Add_a_type_reference_directive_to_0_to_unblock_declaration_emit
9009, // Assigning_properties_to_functions_without_declaring_them_is_not_supported_with_isolatedDeclarations_Add_an_explicit_declaration_for_the_properties_assigned_to_this_function
9010, // Reference directives are not supported in isolated declaration mode.
9011, // Heritage_clause_for_Class_Expressions_is_not_allowed_with_isolatedDeclarations
],
"with-unreliable-errors": [
2784, // 'get' and 'set' accessors cannot declare 'this' parameters.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export const isolatedDeclarationsErrors = new Set([
9008,
9009,
9010,
9011,
]);
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -6710,6 +6710,10 @@
"category": "Error",
"code": 9010
},
"Heritage clauses in class expressions are not allowed with --isolatedDeclarations": {
"category": "Error",
"code": 9011
},
"JSX attributes must only be assigned a non-empty 'expression'.": {
"category": "Error",
"code": 17000
Expand Down
156 changes: 151 additions & 5 deletions src/compiler/transformers/declarations/localInferenceResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import {
} from "../../diagnosticInformationMap.generated";
import {
isComputedPropertyName,
isConstructorDeclaration,
isExportAssignment,
isGetAccessorDeclaration,
isIdentifier,
isIndexSignatureDeclaration,
isInterfaceDeclaration,
isLiteralTypeNode,
isMethodDeclaration,
Expand Down Expand Up @@ -49,6 +51,7 @@ import {
ArrayLiteralExpression,
ArrowFunction,
AsExpression,
ClassExpression,
EntityNameOrEntityNameExpression,
ExportAssignment,
Expression,
Expand All @@ -62,6 +65,7 @@ import {
MethodDeclaration,
MethodSignature,
Modifier,
ModifierLike,
Node,
NodeArray,
NodeFlags,
Expand All @@ -85,6 +89,7 @@ import {
} from "../../types";
import {
createDiagnosticForNode,
createDiagnosticForRange,
isEntityNameExpression,
} from "../../utilities";
import {
Expand All @@ -110,6 +115,7 @@ enum LocalTypeInfoFlags {
None = 0,
Invalid = 1 << 1,
}
const propertyLikeModifiers = new Set<SyntaxKind>([SyntaxKind.ReadonlyKeyword, SyntaxKind.PublicKeyword]);

interface LocalInferenceResolver {
makeInvalidType(): Node;
Expand All @@ -128,7 +134,7 @@ export function createLocalInferenceResolver({
ensureParameter(p: ParameterDeclaration): ParameterDeclaration;
context: TransformationContext;
}): { resolver: LocalInferenceResolver, isolatedDeclarations: true } | { resolver: undefined, isolatedDeclarations: false } {
let currentSourceFile: SourceFile | undefined;
let currentSourceFile: SourceFile;
const options = context.getCompilerOptions();
const resolver = context.getEmitResolver();
if (!options.isolatedDeclarations) {
Expand Down Expand Up @@ -187,10 +193,10 @@ export function createLocalInferenceResolver({

const getAccessor = knownIsGetAccessor ? knownAccessor :
otherAccessor && isGetAccessorDeclaration(otherAccessor) ? otherAccessor :
undefined;
undefined;
const setAccessor = !knownIsGetAccessor ? knownAccessor :
otherAccessor && isSetAccessorDeclaration(otherAccessor) ? otherAccessor :
undefined;
undefined;

return {
otherAccessorIndex,
Expand Down Expand Up @@ -338,13 +344,15 @@ export function createLocalInferenceResolver({
);
tupleType.emitNode = { flags: 1, autoGenerate: undefined, internalFlags: 0 };
return regular(factory.createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, tupleType), node, inheritedArrayTypeFlags);
case SyntaxKind.ObjectLiteralExpression:
case SyntaxKind.ObjectLiteralExpression:
return getTypeForObjectLiteralExpression(node as ObjectLiteralExpression, inferenceFlags);
case SyntaxKind.ClassExpression:
return getClassExpressionTypeNode(node as ClassExpression);
}

return invalid(node);
}
function invalid(sourceNode: Node): LocalTypeInfo {
function invalid(sourceNode: Node): LocalTypeInfo {
reportIsolatedDeclarationError(sourceNode);
return { typeNode: makeInvalidType(), flags: LocalTypeInfoFlags.Invalid, sourceNode };
}
Expand Down Expand Up @@ -496,6 +504,144 @@ export function createLocalInferenceResolver({
const typeNode: TypeNode = replaceWithInvalid ? makeInvalidType() : factory.createTypeLiteralNode(properties);
return regular(typeNode, objectLiteral, inheritedObjectTypeFlags);
}

function getClassExpressionTypeNode(node: ClassExpression): LocalTypeInfo {
let invalid = false;
let hasGetSetAccessor = false;
const staticMembers: TypeElement[] = [];
const nonStaticMembers: TypeElement[] = [];
const constructorParameters: ParameterDeclaration[] = [];

if (node.heritageClauses && node.heritageClauses.length > 0) {
context.addDiagnostic({
...createDiagnosticForNode(node, Diagnostics.Declaration_emit_for_this_file_requires_type_resolution_An_explicit_type_annotation_may_unblock_declaration_emit),
relatedInformation: [
createDiagnosticForRange(
currentSourceFile,
{
pos: node.heritageClauses[0].pos,
end: node.heritageClauses[node.heritageClauses.length - 1].end
},
Diagnostics.Heritage_clause_for_Class_Expressions_is_not_allowed_with_isolatedDeclarations)
],
});
invalid = true;
}

for (const member of node.members) {
if (isConstructorDeclaration(member)) {
for (const parameter of member.parameters) {
const type = localInferenceFromInitializer(parameter, parameter.type);
if (!type) {
invalid = true;
}
// TODO: See what happens on private modifiers.
if (parameter.modifiers?.some((modifier) => propertyLikeModifiers.has(modifier.kind))) {
nonStaticMembers.push(factory.createPropertySignature(
keepReadonlyKeyword(parameter.modifiers),
parameter.name as Identifier,
parameter.questionToken,
type,
));
}
constructorParameters.push(factory.createParameterDeclaration(
/*modifiers*/ undefined,
parameter.dotDotDotToken,
parameter.name,
parameter.questionToken,
type,
parameter.initializer,
));
}
} else if (isMethodDeclaration(member)) {
const type = localInferenceFromInitializer(member, member.type);
if (!type) {
invalid = true;
}
const methodSignature = factory.createMethodSignature(
/*modifiers*/ undefined,
member.name,
member.questionToken,
member.typeParameters,
member.parameters,
type,
);
if (member.modifiers?.some((modifier) => modifier.kind === SyntaxKind.StaticKeyword)) {
staticMembers.push(methodSignature);
} else {
nonStaticMembers.push(methodSignature);
}
} else if (isGetAccessorDeclaration(member) || isSetAccessorDeclaration(member)) {
if (!hasGetSetAccessor) {
hasGetSetAccessor = true;
let type;
if (isGetAccessorDeclaration(member)) {
type = localInferenceFromInitializer(member, member.type);
} else {
type = localInferenceFromInitializer(member.parameters[0], member.parameters[0].type)
}
if (!type) {
invalid = true;
}
nonStaticMembers.push(
factory.createPropertySignature(
[],
member.name,
/*questionToken*/ undefined,
type,
)
);
}
} else if (isIndexSignatureDeclaration(member)) {
nonStaticMembers.push(member);
} else if (isPropertyDeclaration(member)) {
const name = isPrivateIdentifier(member.name) ?
// imitating the behavior from utilities.ts : getSymbolNameForPrivateIdentifier, but as we don't have
// a Symbol & SymbolId in hand, we use NodeId of the declaration instead as an approximiation and to provide uniqueness.
// TODO: This seems to have a high collision possibilitiy than the vanilla implementation as we have much less
// ids for nodes in DTE.
factory.createStringLiteral(`__#${node.parent.id}@${member.name.escapedText}`) :
member.name;
const type = localInferenceFromInitializer(member, member.type);
if (!type) {
invalid = true;
}
const propertySignature = factory.createPropertySignature(
keepReadonlyKeyword(member.modifiers),
name,
member.questionToken,
type,
)
if (member.modifiers?.some((modifier) => modifier.kind === SyntaxKind.StaticKeyword)) {
staticMembers.push(propertySignature);
} else {
nonStaticMembers.push(propertySignature);
}
}
}

if (invalid) {
return { typeNode: makeInvalidType(), flags: LocalTypeInfoFlags.Invalid, sourceNode: node };
}
else {
const constructorSignature = factory.createConstructSignature(
node.typeParameters,
constructorParameters,
factory.createTypeLiteralNode(nonStaticMembers)
);
const typeNode = factory.createTypeLiteralNode([constructorSignature, ...staticMembers]);
return { typeNode, flags: LocalTypeInfoFlags.None, sourceNode: node };
}
}

function keepReadonlyKeyword(modifiers?: NodeArray<ModifierLike>): Modifier[] {
if (modifiers?.some((modifier) => modifier.kind === SyntaxKind.ReadonlyKeyword)) {
return [factory.createModifier(SyntaxKind.ReadonlyKeyword)];
} else {
return [];
}
}

function normalizeLiteralValue(literal: LiteralExpression) {
switch (literal.kind) {
case SyntaxKind.BigIntLiteral:
Expand Down

0 comments on commit a990f05

Please sign in to comment.