Skip to content

Commit 79202c7

Browse files
author
Max Heiber
committed
WIP private methods
Signed-off-by: Max Heiber <[email protected]>
1 parent 8bfc540 commit 79202c7

File tree

1 file changed

+183
-43
lines changed

1 file changed

+183
-43
lines changed

src/compiler/transformers/esnext.ts

Lines changed: 183 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,26 @@ namespace ts {
1313
/**
1414
* A mapping of private names to information needed for transformation.
1515
*/
16-
type PrivateNameEnvironment = UnderscoreEscapedMap<PrivateNamedInstanceField>;
16+
type PrivateNameEnvironment = UnderscoreEscapedMap<PrivateNamedInstanceField | PrivateNamedInstanceMethod>;
1717

1818
/**
1919
* Identifies the type of private name.
2020
*/
21-
const enum PrivateNameType {
22-
InstanceField
21+
const enum PrivateNamePlacement {
22+
InstanceField,
23+
InstanceMethod
2324
}
2425

2526
interface PrivateNamedInstanceField {
26-
type: PrivateNameType.InstanceField;
27-
weakMapName: Identifier;
27+
placement: PrivateNamePlacement.InstanceField;
28+
accumulator: Identifier;
29+
}
30+
31+
interface PrivateNamedInstanceMethod {
32+
placement: PrivateNamePlacement.InstanceMethod;
33+
accumulator: Identifier;
34+
origFunc: MethodDeclaration;
35+
funcName: Identifier;
2836
}
2937

3038
export function transformESNext(context: TransformationContext) {
@@ -369,11 +377,30 @@ namespace ts {
369377

370378
function transformClassMembers(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) {
371379
// Declare private names.
372-
const privateNamedProperties = filter(node.members, isPrivateNamedPropertyDeclaration);
373-
privateNamedProperties.forEach(property => addPrivateNameToEnvironment(property.name));
374-
380+
const privateNamedMembers = node.members
381+
.filter(element => isNamedDeclaration(element) && isPrivateName(element.name));
382+
privateNamedMembers.forEach(addPrivateName);
383+
384+
pendingExpressions = pendingExpressions || [];
385+
last(privateNameEnvironmentStack).forEach(entry => {
386+
const { placement } = entry;
387+
switch (placement) {
388+
case PrivateNamePlacement.InstanceField:
389+
break;
390+
case PrivateNamePlacement.InstanceMethod:
391+
entry = entry as PrivateNamedInstanceMethod;
392+
const func = privateNamedMethodToFunction(entry.origFunc, entry.funcName, entry.accumulator);
393+
(pendingExpressions = pendingExpressions || []).push(createAssignment(
394+
entry.funcName,
395+
func
396+
));
397+
break;
398+
default:
399+
Debug.assertNever(placement, "unexpected Private Name Placement");
400+
}
401+
});
375402
const members: ClassElement[] = [];
376-
const constructor = transformConstructor(node, isDerivedClass);
403+
const constructor = transformConstructor(node, isDerivedClass, !!privateNamedMembers.length);
377404
if (constructor) {
378405
members.push(constructor);
379406
}
@@ -382,17 +409,24 @@ namespace ts {
382409
return setTextRange(createNodeArray(members), /*location*/ node.members);
383410
}
384411

385-
function transformConstructor(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) {
412+
function transformConstructor(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean, declaresPrivateNames: boolean) {
386413
const constructor = visitNode(getFirstConstructorWithBody(node), visitor, isConstructorDeclaration);
387-
const containsPropertyInitializerOrPrivateProperty = forEach(
388-
node.members,
389-
member => isInitializedProperty(member) || isPrivateNamedPropertyDeclaration(member)
390-
);
391-
if (!containsPropertyInitializerOrPrivateProperty) {
392-
return constructor;
414+
const containsPropertyInitializer = forEach(node.members, isInitializedProperty);
415+
let body = constructor ? constructor.body : undefined;
416+
let parameters = constructor ? constructor.parameters : undefined;
417+
const shouldTransformConstructorBody = containsPropertyInitializer || declaresPrivateNames;
418+
if (shouldTransformConstructorBody) {
419+
if (containsPropertyInitializer) {
420+
parameters = visitParameterList(constructor ? constructor.parameters : undefined, visitor, context);
421+
}
422+
else {
423+
// provide a scope for hoisted declarations for WeakSet or WeakMap for private name brand checks
424+
// not needed if `visitParameterList` was called, because that starts/suspends the lexical environment
425+
context.startLexicalEnvironment();
426+
context.suspendLexicalEnvironment();
427+
}
428+
body = transformConstructorBody(node, constructor, isDerivedClass, declaresPrivateNames);
393429
}
394-
const parameters = visitParameterList(constructor ? constructor.parameters : undefined, visitor, context);
395-
const body = transformConstructorBody(node, constructor, isDerivedClass);
396430
if (!body) {
397431
return undefined;
398432
}
@@ -402,7 +436,7 @@ namespace ts {
402436
createConstructor(
403437
/*decorators*/ undefined,
404438
/*modifiers*/ undefined,
405-
parameters,
439+
parameters || [],
406440
body
407441
),
408442
constructor || node
@@ -412,18 +446,25 @@ namespace ts {
412446
);
413447
}
414448

415-
function transformConstructorBody(node: ClassDeclaration | ClassExpression, constructor: ConstructorDeclaration | undefined, isDerivedClass: boolean) {
449+
function transformConstructorBody(
450+
node: ClassDeclaration | ClassExpression,
451+
constructor: ConstructorDeclaration | undefined,
452+
isDerivedClass: boolean,
453+
classDeclaresPrivateNames: boolean,
454+
) {
416455
const properties = filter(node.members, (node): node is PropertyDeclaration => isPropertyDeclaration(node) && !hasStaticModifier(node));
417456

418-
// Only generate synthetic constructor when there are property declarations to move.
419-
if (!constructor && !some(properties)) {
457+
// Only generate synthetic constructor when there are property or private name declarations to move
458+
if (!constructor && !some(properties) && !classDeclaresPrivateNames) {
420459
return visitFunctionBody(/*node*/ undefined, visitor, context);
421460
}
422461

462+
let statements: Statement[] = [];
463+
464+
// was suspended to provide a scope for parameter properties and/or private names
423465
resumeLexicalEnvironment();
424466

425467
let indexOfFirstStatement = 0;
426-
let statements: Statement[] = [];
427468

428469
if (!constructor && isDerivedClass) {
429470
// Add a synthetic `super` call:
@@ -456,6 +497,26 @@ namespace ts {
456497
// }
457498
//
458499
addInitializedPropertyStatements(statements, properties, createThis());
500+
if (classDeclaresPrivateNames) {
501+
last(privateNameEnvironmentStack).forEach(({ placement, accumulator }) => {
502+
switch (placement) {
503+
case PrivateNamePlacement.InstanceField:
504+
// TODO: instance field add accumulator
505+
break;
506+
case PrivateNamePlacement.InstanceMethod:
507+
statements.push(
508+
createExpressionStatement(
509+
createCall(
510+
createPropertyAccess(accumulator, "add"),
511+
/* typeArguments */ undefined,
512+
[createThis()]
513+
)
514+
)
515+
);
516+
break;
517+
}
518+
});
519+
}
459520

460521
// Add existing statements, skipping the initial super call.
461522
if (constructor) {
@@ -535,10 +596,10 @@ namespace ts {
535596
if (isPrivateName(propertyName)) {
536597
const privateNameInfo = accessPrivateName(propertyName);
537598
if (privateNameInfo) {
538-
switch (privateNameInfo.type) {
539-
case PrivateNameType.InstanceField: {
599+
switch (privateNameInfo.placement) {
600+
case PrivateNamePlacement.InstanceField: {
540601
return createCall(
541-
createPropertyAccess(privateNameInfo.weakMapName, "set"),
602+
createPropertyAccess(privateNameInfo.accumulator, "set"),
542603
/*typeArguments*/ undefined,
543604
[receiver, initializer || createVoidZero()]
544605
);
@@ -564,18 +625,66 @@ namespace ts {
564625
privateNameEnvironmentStack.pop();
565626
}
566627

567-
function addPrivateNameToEnvironment(name: PrivateName) {
628+
function privateNamedMethodToFunction (declaration: MethodDeclaration, funcName: Identifier, accumulator: Identifier): FunctionExpression {
629+
const params = declaration.parameters;
630+
let body = getMutableClone(declaration.body || createBlock([], true));
631+
body = visitEachChild(body, visitor, context);
632+
const toPrepend = startOnNewLine(
633+
createStatement(
634+
createClassPrivateNamedCallCheckHelper(context, accumulator)
635+
)
636+
);
637+
body.statements = setTextRange(
638+
createNodeArray([
639+
toPrepend,
640+
...body.statements
641+
]),
642+
body.statements
643+
);
644+
const func = createFunctionExpression(
645+
/* modifiers */ undefined,
646+
/* asteriskToken */ undefined,
647+
funcName,
648+
/* typeParameters */ undefined,
649+
params,
650+
/* type */ undefined,
651+
body);
652+
return func;
653+
}
654+
655+
656+
function addPrivateName(element: ClassElement & { name: PrivateName }) {
568657
const env = last(privateNameEnvironmentStack);
569-
const text = getTextOfPropertyName(name) as string;
570-
const weakMapName = createOptimisticUniqueName("_" + text.substring(1));
571-
weakMapName.autoGenerateFlags |= GeneratedIdentifierFlags.ReservedInNestedScopes;
572-
hoistVariableDeclaration(weakMapName);
573-
env.set(name.escapedText, { type: PrivateNameType.InstanceField, weakMapName });
574-
(pendingExpressions || (pendingExpressions = [])).push(
658+
const text = getTextOfPropertyName(element.name) as string;
659+
const accumulator = createFileLevelUniqueName(`_${text.substring(1)}Private`);
660+
const { escapedText } = element.name;
661+
hoistVariableDeclaration(accumulator);
662+
663+
let identifierName: string;
664+
if (hasModifier(element, ModifierFlags.Static)) {
665+
// statics not supported yet
666+
return;
667+
}
668+
if (isPropertyDeclaration(element)) {
669+
identifierName = "WeakMap";
670+
env.set(escapedText, { placement: PrivateNamePlacement.InstanceField, accumulator });
671+
}
672+
else if (isMethodDeclaration(element)) {
673+
identifierName = "WeakSet";
674+
const escapedText = element.name.escapedText;
675+
const escapedTextNoHash = `_${`${escapedText}`.slice(1)}`;
676+
const funcName: Identifier = createFileLevelUniqueName(escapedTextNoHash);
677+
env.set(escapedText, { placement: PrivateNamePlacement.InstanceMethod, accumulator, funcName, origFunc: element });
678+
hoistVariableDeclaration(funcName); // todo: hoist in lexical, not func scope
679+
}
680+
else {
681+
return;
682+
}
683+
(pendingExpressions = pendingExpressions || []).push(
575684
createAssignment(
576-
weakMapName,
685+
accumulator,
577686
createNew(
578-
createIdentifier("WeakMap"),
687+
createIdentifier(identifierName),
579688
/*typeArguments*/ undefined,
580689
[]
581690
)
@@ -597,14 +706,14 @@ namespace ts {
597706
if (isPrivateName(node.name)) {
598707
const privateNameInfo = accessPrivateName(node.name);
599708
if (privateNameInfo) {
600-
switch (privateNameInfo.type) {
601-
case PrivateNameType.InstanceField:
709+
switch (privateNameInfo.placement) {
710+
case PrivateNamePlacement.InstanceField:
602711
return setOriginalNode(
603712
setTextRange(
604713
createClassPrivateFieldGetHelper(
605714
context,
606-
visitNode(node.expression, visitor, isExpression),
607-
privateNameInfo.weakMapName
715+
visitNode(node.expression, visitor, isExpression),
716+
privateNameInfo.accumulator
608717
),
609718
node
610719
),
@@ -699,6 +808,23 @@ namespace ts {
699808
);
700809
receiver = generatedName;
701810
}
811+
const privateNameEntry = last(privateNameEnvironmentStack).get(node.expression.name.escapedText);
812+
if (privateNameEntry && privateNameEntry.placement === PrivateNamePlacement.InstanceMethod) {
813+
return setOriginalNode(
814+
setTextRange(
815+
createCall(
816+
createPropertyAccess(
817+
privateNameEntry.funcName,
818+
"call"
819+
),
820+
/*typeArguments*/ undefined,
821+
[createThis(), ...node.arguments]
822+
),
823+
/* location */ node
824+
),
825+
node
826+
);
827+
}
702828
return visitNode(
703829
updateCall(
704830
node,
@@ -980,7 +1106,7 @@ namespace ts {
9801106
}
9811107
else if (isAssignmentExpression(node) && isPropertyAccessExpression(node.left) && isPrivateName(node.left.name)) {
9821108
const privateNameInfo = accessPrivateName(node.left.name);
983-
if (privateNameInfo && privateNameInfo.type === PrivateNameType.InstanceField) {
1109+
if (privateNameInfo && privateNameInfo.placement === PrivateNamePlacement.InstanceField) {
9841110
if (isCompoundAssignment(node.operatorToken.kind)) {
9851111
const isReceiverInlineable = isSimpleInlineableExpression(node.left.expression);
9861112
const getReceiver = isReceiverInlineable ? node.left.expression : createTempVariable(hoistVariableDeclaration);
@@ -991,12 +1117,12 @@ namespace ts {
9911117
createClassPrivateFieldSetHelper(
9921118
context,
9931119
setReceiver,
994-
privateNameInfo.weakMapName,
1120+
privateNameInfo.accumulator,
9951121
createBinary(
9961122
createClassPrivateFieldGetHelper(
9971123
context,
9981124
getReceiver,
999-
privateNameInfo.weakMapName
1125+
privateNameInfo.accumulator
10001126
),
10011127
getOperatorForCompoundAssignment(node.operatorToken.kind),
10021128
visitNode(node.right, visitor)
@@ -1010,7 +1136,7 @@ namespace ts {
10101136
createClassPrivateFieldSetHelper(
10111137
context,
10121138
node.left.expression,
1013-
privateNameInfo.weakMapName,
1139+
privateNameInfo.accumulator,
10141140
visitNode(node.right, visitor)
10151141
),
10161142
node
@@ -1329,6 +1455,9 @@ namespace ts {
13291455
function visitMethodDeclaration(node: MethodDeclaration) {
13301456
const savedEnclosingFunctionFlags = enclosingFunctionFlags;
13311457
enclosingFunctionFlags = getFunctionFlags(node);
1458+
if (isPrivateName(node.name)) {
1459+
return [];
1460+
}
13321461
const updated = updateMethod(
13331462
node,
13341463
/*decorators*/ undefined,
@@ -1866,4 +1995,15 @@ namespace ts {
18661995
context.requestEmitHelper(classPrivateFieldSetHelper);
18671996
return createCall(getHelperName("_classPrivateFieldSet"), /* typeArguments */ undefined, [receiver, privateField, value]);
18681997
}
1998+
const classPrivateNamedCallCheckHelper: EmitHelper = {
1999+
name: "typescript:classPrivateNamedCallCheck",
2000+
scoped: false,
2001+
text: `var _classPrivateNamedCallCheck = function (receiver, privateSet) { if (!privateSet.has(receiver)) { throw new TypeError("attempted to get weak field on non-instance"); }};`
2002+
};
2003+
2004+
function createClassPrivateNamedCallCheckHelper(context: TransformationContext, weakSet: Identifier) {
2005+
context.requestEmitHelper(classPrivateNamedCallCheckHelper);
2006+
return createCall(getHelperName("_classPrivateNamedCallCheck"), /* typeArguments */ undefined, [ createThis(), weakSet ]);
2007+
}
2008+
18692009
}

0 commit comments

Comments
 (0)