From 8149e5e6f14242e12171735228816775e125a06e Mon Sep 17 00:00:00 2001 From: Max Heiber Date: Wed, 10 Oct 2018 22:03:57 -0400 Subject: [PATCH 01/12] WIP --- src/compiler/transformers/esnext.ts | 116 ++++++++++++++++++++-------- src/compiler/types.ts | 2 +- src/compiler/utilities.ts | 4 +- 3 files changed, 85 insertions(+), 37 deletions(-) diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index 3c3234b174817..564a1722e6230 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -27,15 +27,19 @@ namespace ts { let enclosingSuperContainerFlags: NodeCheckFlags = 0; + const enum PrivateNamePlacement { InstancePropertyLike, InstanceMethod }; + /** - * Maps private names to the generated name of the WeakMap. + * Maps private names to the generated name and (if applicable) accessor */ interface PrivateNameEnvironment { [name: string]: { - weakMap: Identifier; + placement: PrivateNamePlacement + accumulator: Identifier; initializer?: Expression; - }; + } } + const privateNameEnvironmentStack: PrivateNameEnvironment[] = []; let privateNameEnvironmentIndex = -1; @@ -129,25 +133,53 @@ namespace ts { return privateNameEnvironmentStack[privateNameEnvironmentIndex]; } - function addPrivateName(name: PrivateName, initializer?: Expression) { + function getPrivateNamePlacement (declaration: PrivateNamedDeclaration): PrivateNamePlacement | undefined { + if (declaration.kind === SyntaxKind.PropertyDeclaration) { + if (hasModifier(declaration, ModifierFlags.Static)) { + // todo: static property + return undefined; + } + else { + return PrivateNamePlacement.InstancePropertyLike; + } + } + else if (declaration.kind === SyntaxKind.MethodDeclaration) { + if (hasModifier(declaration, ModifierFlags.Static)) { + // todo: static method + return undefined; + } + else { + return PrivateNamePlacement.InstanceMethod; + } + } + else { + return undefined; + } + } + + function addPrivateName(declaration: PrivateNamedDeclaration, initializer?: Expression): void { const environment = currentPrivateNameEnvironment(); - const nameString = name.escapedText as string; + const nameString = declaration.name.escapedText as string; if (nameString in environment) { throw new Error("Redeclaring private name " + nameString + "."); } - const weakMap = createFileLevelUniqueName("_" + nameString.substring(1)); + const accumulator = createFileLevelUniqueName("_" + nameString.substring(1)); + const placement = getPrivateNamePlacement(declaration); + if (!placement) { + return; + } environment[nameString] = { - weakMap, + placement, + accumulator, initializer }; - return weakMap; } - function accessPrivateName(name: PrivateName) { + function getPrivateNameRecord(name: PrivateName) { const environment = currentPrivateNameEnvironment(); const nameString = name.escapedText as string; if (nameString in environment) { - return environment[nameString].weakMap; + return environment[nameString]; } // Undeclared private name. return undefined; @@ -155,24 +187,40 @@ namespace ts { function visitPropertyAccessExpression(node: PropertyAccessExpression): Expression { if (isPrivateName(node.name)) { - const weakMapName = accessPrivateName(node.name); - if (!weakMapName) { + const record = getPrivateNameRecord(node.name); + if (!record) { return node; } - return setOriginalNode( - setTextRange( - createClassPrivateFieldGetHelper(context, node.expression, weakMapName), - /* location */ node - ), - node - ); + const { placement, accumulator } = record; + + switch (placement) { + case PrivateNamePlacement.InstancePropertyLike: + return setOriginalNode( + setTextRange( + createClassPrivateFieldGetHelper(context, node.expression, accumulator), + /* location */ node + ), + node + ); + case PrivateNamePlacement.InstanceMethod: + // TODO: use private instance method helper instead here + return setOriginalNode( + setTextRange( + createClassPrivateFieldGetHelper(context, node.expression, accumulator), + /* location */ node + ), + node + ); + default: + Debug.assertNever(placement); + } } return visitEachChild(node, visitor, context); } function visitorCollectPrivateNames(node: Node): VisitResult { - if (isPrivateNamedPropertyDeclaration(node)) { - addPrivateName(node.name); + if (isPrivateNamedDeclaration(node)) { + addPrivateName(node); return undefined; } // Don't collect private names from nested classes. @@ -186,7 +234,7 @@ namespace ts { startPrivateNameEnvironment(); node = visitEachChild(node, visitorCollectPrivateNames, context); node = visitEachChild(node, visitor, context); - const statements = createPrivateNameWeakMapDeclarations( + const statements = createPrivateNameaccumulatorDeclarations( currentPrivateNameEnvironment() ); if (statements.length) { @@ -209,7 +257,7 @@ namespace ts { startPrivateNameEnvironment(); node = visitEachChild(node, visitorCollectPrivateNames, context); node = visitEachChild(node, visitor, context); - const expressions = createPrivateNameWeakMapAssignments( + const expressions = createPrivateNameaccumulatorAssignments( currentPrivateNameEnvironment() ); if (expressions.length) { @@ -240,12 +288,12 @@ namespace ts { return privateNameEnvironment; } - function createPrivateNameWeakMapDeclarations(environment: PrivateNameEnvironment): Statement[] { + function createPrivateNameaccumulatorDeclarations(environment: PrivateNameEnvironment): Statement[] { return Object.keys(environment).map(name => { const privateName = environment[name]; return createVariableStatement( /* modifiers */ undefined, - [createVariableDeclaration(privateName.weakMap, + [createVariableDeclaration(privateName.accumulator, /* typeNode */ undefined, createNew( createIdentifier("WeakMap"), @@ -256,12 +304,12 @@ namespace ts { }); } - function createPrivateNameWeakMapAssignments(environment: PrivateNameEnvironment): Expression[] { + function createPrivateNameaccumulatorAssignments(environment: PrivateNameEnvironment): Expression[] { return Object.keys(environment).map(name => { const privateName = environment[name]; - hoistVariableDeclaration(privateName.weakMap); + hoistVariableDeclaration(privateName.accumulator); return createBinary( - privateName.weakMap, + privateName.accumulator, SyntaxKind.EqualsToken, createNew(createIdentifier("WeakMap"), /* typeArguments */ undefined, /* argumentsArray */ undefined) ); @@ -276,7 +324,7 @@ namespace ts { const privateName = privateNameEnvironment[name]; return createStatement( createCall( - createPropertyAccess(privateName.weakMap, "set"), + createPropertyAccess(privateName.accumulator, "set"), /* typeArguments */ undefined, [createThis(), privateName.initializer || createVoidZero()] ) @@ -476,8 +524,8 @@ namespace ts { isPropertyAccessExpression(node.left) && isPrivateName(node.left.name)) { - const weakMapName = accessPrivateName(node.left.name); - if (!weakMapName) { + const accumulatorName = getPrivateNameRecord(node.left.name); + if (!accumulatorName) { // Don't change output for undeclared private names (error). return node; } @@ -499,9 +547,9 @@ namespace ts { createClassPrivateFieldSetHelper( context, setReceiver, - weakMapName, + accumulatorName, createBinary( - createClassPrivateFieldGetHelper(context, getReceiver, weakMapName), + createClassPrivateFieldGetHelper(context, getReceiver, accumulatorName), getOperatorForCompoundAssignment(node.operatorToken.kind), visitNode(node.right, visitor) ) @@ -514,7 +562,7 @@ namespace ts { createClassPrivateFieldSetHelper( context, node.left.expression, - weakMapName, + accumulatorName, visitNode(node.right, visitor) ), node diff --git a/src/compiler/types.ts b/src/compiler/types.ts index a86badd2c56a4..87ff3a38a98fb 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -864,7 +864,7 @@ namespace ts { initializer?: Expression; // Optional initializer } - export interface PrivateNamedPropertyDeclaration extends PropertyDeclaration { + export interface PrivateNamedDeclaration extends PropertyDeclaration { name: PrivateName; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 74c1e6086514f..e58c394b91f47 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -6091,8 +6091,8 @@ namespace ts { } } - export function isPrivateNamedPropertyDeclaration(node: Node): node is PrivateNamedPropertyDeclaration { - return node && isPropertyDeclaration(node) && isPrivateName(node.name); + export function isPrivateNamedDeclaration(node: Node): node is PrivateNamedDeclaration { + return node && isNamedDeclaration(node) && isPrivateName(node.name); } // Type members From df2bb615a88849bb35f14ff1be356745151e2e21 Mon Sep 17 00:00:00 2001 From: Max Heiber Date: Thu, 11 Oct 2018 21:05:52 -0400 Subject: [PATCH 02/12] WIP --- src/compiler/transformers/esnext.ts | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index 564a1722e6230..de1fa6a3c575b 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -197,6 +197,7 @@ namespace ts { case PrivateNamePlacement.InstancePropertyLike: return setOriginalNode( setTextRange( + // todo: rename createClassPrivateFieldGetHelper(context, node.expression, accumulator), /* location */ node ), @@ -206,7 +207,7 @@ namespace ts { // TODO: use private instance method helper instead here return setOriginalNode( setTextRange( - createClassPrivateFieldGetHelper(context, node.expression, accumulator), + createClassPrivateNamedCallHelper(context, node.expression, accumulator), /* location */ node ), node @@ -219,7 +220,7 @@ namespace ts { } function visitorCollectPrivateNames(node: Node): VisitResult { - if (isPrivateNamedDeclaration(node)) { + if (isPrivateNamedDeclaration(node) && (isPropertyDeclaration(node) || isMethodDeclaration(node))) { addPrivateName(node); return undefined; } @@ -524,11 +525,12 @@ namespace ts { isPropertyAccessExpression(node.left) && isPrivateName(node.left.name)) { - const accumulatorName = getPrivateNameRecord(node.left.name); - if (!accumulatorName) { + const privateNameRecord = getPrivateNameRecord(node.left.name); + if (!privateNameRecord) { // Don't change output for undeclared private names (error). return node; } + const accumulatorName = privateNameRecord.accumulator; if (isCompoundAssignment(node.operatorToken.kind)) { let setReceiver: Expression; let getReceiver: Expression; @@ -1231,6 +1233,17 @@ namespace ts { return createCall(getHelperName("_classPrivateFieldGet"), /* typeArguments */ undefined, [ receiver, privateField ]); } + const classPrivateNamedCallHelper: EmitHelper = { + name: "typescript:classPrivateNamedCall", + scoped: false, + text: `var _classPrivateNamedCall = function classPrivateNamedCall (receiver, privateMap) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } return privateMap.get(receiver); };` + }; + + function createClassPrivateNamedCallHelper(context: TransformationContext, receiver: Expression, privateField: Identifier) { + context.requestEmitHelper(classPrivateNamedCallHelper); + return createCall(getHelperName("_classPrivateNamedCall"), /* typeArguments */ undefined, [ receiver, privateField ]); + } + const classPrivateFieldSetHelper: EmitHelper = { name: "typescript:classPrivateFieldSet", scoped: false, From 6282e2b45c0ee4a7eea70307ddb038661d7c6aba Mon Sep 17 00:00:00 2001 From: Max Heiber Date: Fri, 12 Oct 2018 09:49:30 -0400 Subject: [PATCH 03/12] WIP --- src/compiler/transformers/esnext.ts | 56 ++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index de1fa6a3c575b..b1022e983484e 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -165,7 +165,7 @@ namespace ts { } const accumulator = createFileLevelUniqueName("_" + nameString.substring(1)); const placement = getPrivateNamePlacement(declaration); - if (!placement) { + if (placement === undefined) { return; } environment[nameString] = { @@ -220,7 +220,15 @@ namespace ts { } function visitorCollectPrivateNames(node: Node): VisitResult { - if (isPrivateNamedDeclaration(node) && (isPropertyDeclaration(node) || isMethodDeclaration(node))) { + if ( + isPrivateNamedDeclaration(node) + && (isPropertyDeclaration(node) + || isMethodDeclaration(node) + // TODO: getters/setters + ) + // TODO: statics + && !hasModifier(node, ModifierFlags.Static) + ) { addPrivateName(node); return undefined; } @@ -291,26 +299,42 @@ namespace ts { function createPrivateNameaccumulatorDeclarations(environment: PrivateNameEnvironment): Statement[] { return Object.keys(environment).map(name => { - const privateName = environment[name]; - return createVariableStatement( - /* modifiers */ undefined, - [createVariableDeclaration(privateName.accumulator, - /* typeNode */ undefined, - createNew( - createIdentifier("WeakMap"), - /* typeArguments */ undefined, - /* argumentsArray */ undefined - ))] - ); + const { placement, accumulator } = environment[name]; + switch (placement) { + case PrivateNamePlacement.InstancePropertyLike: + return createVariableStatement( + /* modifiers */ undefined, + [createVariableDeclaration(accumulator, + /* typeNode */ undefined, + createNew( + createIdentifier("WeakMap"), + /* typeArguments */ undefined, + /* argumentsArray */ undefined + ))] + ); + case PrivateNamePlacement.InstanceMethod: + return createVariableStatement( + /* modifiers */ undefined, + [createVariableDeclaration(accumulator, + /* typeNode */ undefined, + createNew( + createIdentifier("WeakSet"), + /* typeArguments */ undefined, + /* argumentsArray */ undefined + ))] + ); + default: + return Debug.assertNever(placement); + } }); } function createPrivateNameaccumulatorAssignments(environment: PrivateNameEnvironment): Expression[] { return Object.keys(environment).map(name => { - const privateName = environment[name]; - hoistVariableDeclaration(privateName.accumulator); + const { accumulator } = environment[name]; + hoistVariableDeclaration(accumulator); return createBinary( - privateName.accumulator, + accumulator, SyntaxKind.EqualsToken, createNew(createIdentifier("WeakMap"), /* typeArguments */ undefined, /* argumentsArray */ undefined) ); From 3f21455b63e1ef4e3bc8fbee08c972fb688c670a Mon Sep 17 00:00:00 2001 From: Max Heiber Date: Fri, 12 Oct 2018 12:42:36 -0400 Subject: [PATCH 04/12] WIP --- src/compiler/transformers/esnext.ts | 44 ++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index b1022e983484e..1afb02925e257 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -207,7 +207,7 @@ namespace ts { // TODO: use private instance method helper instead here return setOriginalNode( setTextRange( - createClassPrivateNamedCallHelper(context, node.expression, accumulator), + createClassPrivateNamedCallCheckHelper(context, node.expression, accumulator), /* location */ node ), node @@ -346,14 +346,30 @@ namespace ts { const privateNameEnvironment = currentPrivateNameEnvironment(); // Initialize private properties. const initializerStatements = Object.keys(privateNameEnvironment).map(name => { - const privateName = privateNameEnvironment[name]; - return createStatement( - createCall( - createPropertyAccess(privateName.accumulator, "set"), - /* typeArguments */ undefined, - [createThis(), privateName.initializer || createVoidZero()] - ) - ); + const { accumulator, placement, initializer} = privateNameEnvironment[name]; + + switch(placement) { + case PrivateNamePlacement.InstancePropertyLike: + return createStatement( + createCall( + createPropertyAccess(accumulator, "set"), + /* typeArguments */ undefined, + [createThis(), initializer || createVoidZero()] + ) + ); + case PrivateNamePlacement.InstanceMethod: + return createStatement( + createCall( + createPropertyAccess(accumulator, "add"), + /* typeArguments */ undefined, + [createThis()] + ) + ); + default: + return Debug.assertNever(placement); + + } + }); const ctor = find( members, @@ -1257,15 +1273,15 @@ namespace ts { return createCall(getHelperName("_classPrivateFieldGet"), /* typeArguments */ undefined, [ receiver, privateField ]); } - const classPrivateNamedCallHelper: EmitHelper = { + const classPrivateNamedCallCheckHelper: EmitHelper = { name: "typescript:classPrivateNamedCall", scoped: false, - text: `var _classPrivateNamedCall = function classPrivateNamedCall (receiver, privateMap) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } return privateMap.get(receiver); };` + text: `var _classPrivateNamedCall = function (receiver, privateSet) { if (!privateSet.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); }};` }; - function createClassPrivateNamedCallHelper(context: TransformationContext, receiver: Expression, privateField: Identifier) { - context.requestEmitHelper(classPrivateNamedCallHelper); - return createCall(getHelperName("_classPrivateNamedCall"), /* typeArguments */ undefined, [ receiver, privateField ]); + function createClassPrivateNamedCallCheckHelper(context: TransformationContext, receiver: Expression, privateField: Identifier) { + context.requestEmitHelper(classPrivateNamedCallCheckHelper); + return createCall(getHelperName("_classPrivateNamedCallCheck"), /* typeArguments */ undefined, [ receiver, privateField ]); } const classPrivateFieldSetHelper: EmitHelper = { From bd58d07c4c49b94a4c39f0b1748d53de40f980c0 Mon Sep 17 00:00:00 2001 From: Max Heiber Date: Sat, 13 Oct 2018 14:09:13 -0400 Subject: [PATCH 05/12] WIP borken --- src/compiler/factory.ts | 10 +++++++ src/compiler/transformers/esnext.ts | 46 +++++++++++++++-------------- 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 154416b78567c..f5b26cb406793 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -3025,6 +3025,16 @@ namespace ts { return node; } + export function replaceNode(original: Node | undefined, node: T) : T { + return setOriginalNode( + setTextRange( + node, + /* location */ original + ), + original + ); + } + function mergeEmitNode(sourceEmitNode: EmitNode, destEmitNode: EmitNode | undefined) { const { flags, diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index 1afb02925e257..0cd362674e53d 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -27,19 +27,28 @@ namespace ts { let enclosingSuperContainerFlags: NodeCheckFlags = 0; - const enum PrivateNamePlacement { InstancePropertyLike, InstanceMethod }; + const enum PrivateNamePlacement { InstanceField, InstanceMethod }; /** * Maps private names to the generated name and (if applicable) accessor */ interface PrivateNameEnvironment { - [name: string]: { - placement: PrivateNamePlacement - accumulator: Identifier; - initializer?: Expression; - } + [name: string]: PrivateNamedInstanceFieldEntry | PrivateNamedInstanceMethodEntry; + } + + interface PrivateNamedInstanceFieldEntry { + placement: PrivateNamePlacement.InstanceField; + accumulator: Identifier; + initializer?: Expression; } + interface PrivateNamedInstanceMethodEntry { + accumulator: Identifier; + placement: PrivateNamePlacement.InstanceField; + parameters + } + + const privateNameEnvironmentStack: PrivateNameEnvironment[] = []; let privateNameEnvironmentIndex = -1; @@ -195,22 +204,15 @@ namespace ts { switch (placement) { case PrivateNamePlacement.InstancePropertyLike: - return setOriginalNode( - setTextRange( - // todo: rename + return replaceNode( createClassPrivateFieldGetHelper(context, node.expression, accumulator), - /* location */ node - ), - node - ); + node + ); case PrivateNamePlacement.InstanceMethod: // TODO: use private instance method helper instead here - return setOriginalNode( - setTextRange( + return replaceNode( createClassPrivateNamedCallCheckHelper(context, node.expression, accumulator), - /* location */ node - ), - node + node ); default: Debug.assertNever(placement); @@ -1274,14 +1276,14 @@ namespace ts { } const classPrivateNamedCallCheckHelper: EmitHelper = { - name: "typescript:classPrivateNamedCall", + name: "typescript:classPrivateNamedCallCheck", scoped: false, - text: `var _classPrivateNamedCall = function (receiver, privateSet) { if (!privateSet.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); }};` + text: `var _classPrivateNamedCall = function (receiver, weakSet) { if (!weakSet.has(receiver)) { throw new TypeError("attempted to get weak field on non-instance"); }};` }; - function createClassPrivateNamedCallCheckHelper(context: TransformationContext, receiver: Expression, privateField: Identifier) { + function createClassPrivateNamedCallCheckHelper(context: TransformationContext, receiver: Expression, weakSet: Identifier) { context.requestEmitHelper(classPrivateNamedCallCheckHelper); - return createCall(getHelperName("_classPrivateNamedCallCheck"), /* typeArguments */ undefined, [ receiver, privateField ]); + return createCall(getHelperName("_classPrivateNamedCallCheck"), /* typeArguments */ undefined, [ receiver, weakSet ]); } const classPrivateFieldSetHelper: EmitHelper = { From dbcd840c74e1e78763a4f449c7cfd88cd3ddfda3 Mon Sep 17 00:00:00 2001 From: Max Heiber Date: Sun, 14 Oct 2018 20:58:18 -0400 Subject: [PATCH 06/12] WIP --- src/compiler/transformers/esnext.ts | 74 ++++++++++++++--------------- 1 file changed, 36 insertions(+), 38 deletions(-) diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index 0cd362674e53d..f9f7f924b2e68 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -43,12 +43,12 @@ namespace ts { } interface PrivateNamedInstanceMethodEntry { + placement: PrivateNamePlacement.InstanceMethod; accumulator: Identifier; - placement: PrivateNamePlacement.InstanceField; - parameters + parameters: NodeArray; + functionBody: FunctionBody; } - const privateNameEnvironmentStack: PrivateNameEnvironment[] = []; let privateNameEnvironmentIndex = -1; @@ -142,46 +142,43 @@ namespace ts { return privateNameEnvironmentStack[privateNameEnvironmentIndex]; } - function getPrivateNamePlacement (declaration: PrivateNamedDeclaration): PrivateNamePlacement | undefined { + function addPrivateName(declaration: PrivateNamedDeclaration, initializer?: Expression): void { + const environment = currentPrivateNameEnvironment(); + const nameString = declaration.name.escapedText as string; + if (nameString in environment) { + throw new Error("Redeclaring private name " + nameString + "."); + } + const accumulator = createFileLevelUniqueName("_" + nameString.substring(1)); if (declaration.kind === SyntaxKind.PropertyDeclaration) { if (hasModifier(declaration, ModifierFlags.Static)) { // todo: static property - return undefined; + return; } else { - return PrivateNamePlacement.InstancePropertyLike; + environment[nameString] = { + placement: PrivateNamePlacement.InstanceField, + accumulator: accumulator, + initializer + }; } } - else if (declaration.kind === SyntaxKind.MethodDeclaration) { + else if (isMethodDeclaration(declaration)) { if (hasModifier(declaration, ModifierFlags.Static)) { // todo: static method - return undefined; + return; + } + else if (!declaration.body) { + return; } else { - return PrivateNamePlacement.InstanceMethod; + environment[nameString] = { + placement: PrivateNamePlacement.InstanceMethod, + accumulator: accumulator, + parameters: declaration.parameters, + functionBody: declaration.body + } } } - else { - return undefined; - } - } - - function addPrivateName(declaration: PrivateNamedDeclaration, initializer?: Expression): void { - const environment = currentPrivateNameEnvironment(); - const nameString = declaration.name.escapedText as string; - if (nameString in environment) { - throw new Error("Redeclaring private name " + nameString + "."); - } - const accumulator = createFileLevelUniqueName("_" + nameString.substring(1)); - const placement = getPrivateNamePlacement(declaration); - if (placement === undefined) { - return; - } - environment[nameString] = { - placement, - accumulator, - initializer - }; } function getPrivateNameRecord(name: PrivateName) { @@ -196,14 +193,14 @@ namespace ts { function visitPropertyAccessExpression(node: PropertyAccessExpression): Expression { if (isPrivateName(node.name)) { - const record = getPrivateNameRecord(node.name); - if (!record) { + const entry = getPrivateNameRecord(node.name); + if (!entry) { return node; } - const { placement, accumulator } = record; + const { placement, accumulator } = entry; switch (placement) { - case PrivateNamePlacement.InstancePropertyLike: + case PrivateNamePlacement.InstanceField: return replaceNode( createClassPrivateFieldGetHelper(context, node.expression, accumulator), node @@ -303,7 +300,7 @@ namespace ts { return Object.keys(environment).map(name => { const { placement, accumulator } = environment[name]; switch (placement) { - case PrivateNamePlacement.InstancePropertyLike: + case PrivateNamePlacement.InstanceField: return createVariableStatement( /* modifiers */ undefined, [createVariableDeclaration(accumulator, @@ -348,15 +345,16 @@ namespace ts { const privateNameEnvironment = currentPrivateNameEnvironment(); // Initialize private properties. const initializerStatements = Object.keys(privateNameEnvironment).map(name => { - const { accumulator, placement, initializer} = privateNameEnvironment[name]; + const entry = privateNameEnvironment[name]; + const { accumulator, placement } = entry; switch(placement) { - case PrivateNamePlacement.InstancePropertyLike: + case PrivateNamePlacement.InstanceField: return createStatement( createCall( createPropertyAccess(accumulator, "set"), /* typeArguments */ undefined, - [createThis(), initializer || createVoidZero()] + [createThis(), (entry as PrivateNamedInstanceFieldEntry).initializer || createVoidZero()] ) ); case PrivateNamePlacement.InstanceMethod: From ea818be084c5778c002e1f0ba78945ba3faf0f50 Mon Sep 17 00:00:00 2001 From: Max Heiber Date: Sun, 14 Oct 2018 21:10:43 -0400 Subject: [PATCH 07/12] WIP --- src/compiler/transformers/esnext.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index f9f7f924b2e68..26ac0d109cb4c 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -202,14 +202,14 @@ namespace ts { switch (placement) { case PrivateNamePlacement.InstanceField: return replaceNode( + node, createClassPrivateFieldGetHelper(context, node.expression, accumulator), - node ); case PrivateNamePlacement.InstanceMethod: // TODO: use private instance method helper instead here return replaceNode( + node, createClassPrivateNamedCallCheckHelper(context, node.expression, accumulator), - node ); default: Debug.assertNever(placement); @@ -1276,7 +1276,7 @@ namespace ts { const classPrivateNamedCallCheckHelper: EmitHelper = { name: "typescript:classPrivateNamedCallCheck", scoped: false, - text: `var _classPrivateNamedCall = function (receiver, weakSet) { if (!weakSet.has(receiver)) { throw new TypeError("attempted to get weak field on non-instance"); }};` + text: `var _classPrivateNamedCallCheck = function (receiver, weakSet) { if (!weakSet.has(receiver)) { throw new TypeError("attempted to get weak field on non-instance"); }};` }; function createClassPrivateNamedCallCheckHelper(context: TransformationContext, receiver: Expression, weakSet: Identifier) { From 81d38115486e75712569972bfdbe1b5c896fcdc6 Mon Sep 17 00:00:00 2001 From: Max Heiber Date: Sun, 14 Oct 2018 21:46:39 -0400 Subject: [PATCH 08/12] WIP --- src/compiler/transformers/esnext.ts | 35 ++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index 26ac0d109cb4c..92f7d4709c0c2 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -45,8 +45,7 @@ namespace ts { interface PrivateNamedInstanceMethodEntry { placement: PrivateNamePlacement.InstanceMethod; accumulator: Identifier; - parameters: NodeArray; - functionBody: FunctionBody; + func: FunctionDeclaration; } const privateNameEnvironmentStack: PrivateNameEnvironment[] = []; @@ -171,11 +170,28 @@ namespace ts { return; } else { + const params = declaration.parameters; + const body = getMutableClone(declaration.body); + body.statements = setTextRange( + createNodeArray([ + startOnNewLine(createStatement(createLiteral("use strict"))), + ...body.statements + ]), + body.statements + ); + const func = createFunctionDeclaration( + undefined, + undefined, + undefined, + "foo", // TODO: max + undefined, + params, // TODO: max + undefined, + body) environment[nameString] = { placement: PrivateNamePlacement.InstanceMethod, - accumulator: accumulator, - parameters: declaration.parameters, - functionBody: declaration.body + accumulator, + func } } } @@ -255,6 +271,15 @@ namespace ts { node.heritageClauses, transformClassMembers(node.members) ); + Object.keys( + currentPrivateNameEnvironment() + ) + .map(key => currentPrivateNameEnvironment()[key]) + .filter(x => x.placement === PrivateNamePlacement.InstanceMethod) + .forEach(e => { + const entry = e as PrivateNamedInstanceMethodEntry; + statements.push(entry.func) + }); } statements.unshift(node); endPrivateNameEnvironment(); From 6d0c54916ead1aaa553c18a047c273de66fa1d33 Mon Sep 17 00:00:00 2001 From: Max Heiber Date: Sun, 14 Oct 2018 22:11:11 -0400 Subject: [PATCH 09/12] WIP --- src/compiler/transformers/esnext.ts | 32 ++++++++++++++++++----------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index 92f7d4709c0c2..e336ec6e8749a 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -45,7 +45,7 @@ namespace ts { interface PrivateNamedInstanceMethodEntry { placement: PrivateNamePlacement.InstanceMethod; accumulator: Identifier; - func: FunctionDeclaration; + func: FunctionDeclaration & {name: Identifier}; } const privateNameEnvironmentStack: PrivateNameEnvironment[] = []; @@ -172,27 +172,33 @@ namespace ts { else { const params = declaration.parameters; const body = getMutableClone(declaration.body); + const toPrepend = startOnNewLine( + createStatement( + createClassPrivateNamedCallCheckHelper(context, createThis(), accumulator) + ) + ); body.statements = setTextRange( createNodeArray([ - startOnNewLine(createStatement(createLiteral("use strict"))), + toPrepend, ...body.statements ]), body.statements ); + const funcName = createFileLevelUniqueName(`_${nameString.slice(1)}Func`); const func = createFunctionDeclaration( - undefined, - undefined, - undefined, - "foo", // TODO: max - undefined, - params, // TODO: max - undefined, - body) + /* decorators */ undefined, + /* modifiers */ undefined, + /* asteriskToken */ undefined, + funcName, + /* typeParameters */ undefined, + params, + /* type */ undefined, + body) as FunctionDeclaration & {name: Identifier}; environment[nameString] = { placement: PrivateNamePlacement.InstanceMethod, accumulator, func - } + }; } } } @@ -222,10 +228,12 @@ namespace ts { createClassPrivateFieldGetHelper(context, node.expression, accumulator), ); case PrivateNamePlacement.InstanceMethod: + + const { func } = entry as PrivateNamedInstanceMethodEntry; // TODO: use private instance method helper instead here return replaceNode( node, - createClassPrivateNamedCallCheckHelper(context, node.expression, accumulator), + createCall(func.name, undefined, []) ); default: Debug.assertNever(placement); From 8186a4770708bbeb0587c2123350ba115a2f5d3e Mon Sep 17 00:00:00 2001 From: Max Heiber Date: Sun, 14 Oct 2018 22:30:18 -0400 Subject: [PATCH 10/12] WIP --- src/compiler/transformers/esnext.ts | 39 +++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index e336ec6e8749a..a5c5085a27af2 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -132,6 +132,8 @@ namespace ts { return visitClassDeclaration(node as ClassDeclaration); case SyntaxKind.ClassExpression: return visitClassExpression(node as ClassExpression); + case SyntaxKind.CallExpression: + return visitCallExpression(node as CallExpression); default: return visitEachChild(node, visitor, context); } @@ -213,13 +215,39 @@ namespace ts { return undefined; } + function visitCallExpression(node: CallExpression): Expression { + if (isPropertyAccessExpression(node.expression) + && isPrivateName(node.expression.name)) { + const entry = getPrivateNameRecord(node.expression.name); + if (!entry) { + return node; + } + const { placement } = entry; + + switch (placement) { + case PrivateNamePlacement.InstanceField: + // handled by visitPropertyAccessExpression + return node; + case PrivateNamePlacement.InstanceMethod: + const { func } = entry as PrivateNamedInstanceMethodEntry; + return replaceNode( + node, + createCall(func.name, undefined, node.arguments) + ); + return node; + default: + return Debug.assertNever(placement); + } + } + return visitEachChild(node, visitor, context); + } + function visitPropertyAccessExpression(node: PropertyAccessExpression): Expression { if (isPrivateName(node.name)) { const entry = getPrivateNameRecord(node.name); if (!entry) { return node; } - const { placement, accumulator } = entry; switch (placement) { case PrivateNamePlacement.InstanceField: @@ -228,13 +256,8 @@ namespace ts { createClassPrivateFieldGetHelper(context, node.expression, accumulator), ); case PrivateNamePlacement.InstanceMethod: - - const { func } = entry as PrivateNamedInstanceMethodEntry; - // TODO: use private instance method helper instead here - return replaceNode( - node, - createCall(func.name, undefined, []) - ); + // handled by visitCallExpression + return node; default: Debug.assertNever(placement); } From ccf98cf9a67440a5b73f1a78caf7ec746a9b3150 Mon Sep 17 00:00:00 2001 From: Max Heiber Date: Sun, 14 Oct 2018 22:30:50 -0400 Subject: [PATCH 11/12] WIP --- src/compiler/transformers/esnext.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index a5c5085a27af2..c2d1fc626cb69 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -234,7 +234,6 @@ namespace ts { node, createCall(func.name, undefined, node.arguments) ); - return node; default: return Debug.assertNever(placement); } From ea3800245e368576e1059fd997dbf358f00e7dd6 Mon Sep 17 00:00:00 2001 From: Max Heiber Date: Sun, 14 Oct 2018 23:31:15 -0400 Subject: [PATCH 12/12] WIP --- src/compiler/transformers/esnext.ts | 49 +++++++++++++---------------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index c2d1fc626cb69..8508d28bafd5a 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -149,7 +149,7 @@ namespace ts { if (nameString in environment) { throw new Error("Redeclaring private name " + nameString + "."); } - const accumulator = createFileLevelUniqueName("_" + nameString.substring(1)); + const accumulator = createFileLevelUniqueName(`_accumulator_${nameString.slice(1)}`); if (declaration.kind === SyntaxKind.PropertyDeclaration) { if (hasModifier(declaration, ModifierFlags.Static)) { // todo: static property @@ -174,9 +174,10 @@ namespace ts { else { const params = declaration.parameters; const body = getMutableClone(declaration.body); + const receiver = createIdentifier("receiver"); const toPrepend = startOnNewLine( createStatement( - createClassPrivateNamedCallCheckHelper(context, createThis(), accumulator) + createClassPrivateNamedCallCheckHelper(context, receiver, accumulator) ) ); body.statements = setTextRange( @@ -186,14 +187,17 @@ namespace ts { ]), body.statements ); - const funcName = createFileLevelUniqueName(`_${nameString.slice(1)}Func`); + const funcName = createFileLevelUniqueName(`_${nameString.slice(1)}`); const func = createFunctionDeclaration( /* decorators */ undefined, /* modifiers */ undefined, /* asteriskToken */ undefined, funcName, /* typeParameters */ undefined, - params, + [ + createParameter(undefined, undefined, undefined, receiver), + ...params + ], /* type */ undefined, body) as FunctionDeclaration & {name: Identifier}; environment[nameString] = { @@ -222,20 +226,15 @@ namespace ts { if (!entry) { return node; } + const receiver = node.expression.expression; const { placement } = entry; + if (placement === PrivateNamePlacement.InstanceMethod) { + const { func } = entry as PrivateNamedInstanceMethodEntry; + return replaceNode( + node, + createCall(func.name, undefined, [receiver, ...node.arguments]) + ); - switch (placement) { - case PrivateNamePlacement.InstanceField: - // handled by visitPropertyAccessExpression - return node; - case PrivateNamePlacement.InstanceMethod: - const { func } = entry as PrivateNamedInstanceMethodEntry; - return replaceNode( - node, - createCall(func.name, undefined, node.arguments) - ); - default: - return Debug.assertNever(placement); } } return visitEachChild(node, visitor, context); @@ -248,17 +247,11 @@ namespace ts { return node; } const { placement, accumulator } = entry; - switch (placement) { - case PrivateNamePlacement.InstanceField: - return replaceNode( - node, - createClassPrivateFieldGetHelper(context, node.expression, accumulator), - ); - case PrivateNamePlacement.InstanceMethod: - // handled by visitCallExpression - return node; - default: - Debug.assertNever(placement); + if (placement === PrivateNamePlacement.InstanceField) { + return replaceNode( + node, + createClassPrivateFieldGetHelper(context, node.expression, accumulator), + ); } } return visitEachChild(node, visitor, context); @@ -1331,7 +1324,7 @@ namespace ts { const classPrivateNamedCallCheckHelper: EmitHelper = { name: "typescript:classPrivateNamedCallCheck", scoped: false, - text: `var _classPrivateNamedCallCheck = function (receiver, weakSet) { if (!weakSet.has(receiver)) { throw new TypeError("attempted to get weak field on non-instance"); }};` + text: `var _classPrivateNamedCallCheck = function (receiver, privateSet) { if (!privateSet.has(receiver)) { throw new TypeError("attempted to get weak field on non-instance"); }};` }; function createClassPrivateNamedCallCheckHelper(context: TransformationContext, receiver: Expression, weakSet: Identifier) {