Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
0b8c723
Merge pull request #4 from mheiber/syntaxkind
Neuroboy23 Aug 23, 2018
136e218
Start ES2015 transformation
Jun 28, 2018
d0a89b8
Fix lint errors
Jun 28, 2018
1e7326d
Transform private name references. Generate private field initializers.
Jun 29, 2018
4abc354
Generate WeakMap instances for private fields. Fix field initialization.
Jun 29, 2018
f7fc9af
Remove unused function.
Jun 29, 2018
f65b7bb
Clean up private name initializer code
Jul 2, 2018
23a8d60
Clean up private field class transformation.
Jul 3, 2018
7c63b54
Fix private name transformation clash with constructor overload list
Jul 3, 2018
832f457
Remove unnecessary exporting of private name helper functions.
Jul 5, 2018
fe4f98a
Transform compound assignment expressions
Jul 19, 2018
8293248
Fix linter errors
Jul 19, 2018
d6cc08c
Use IIFE, transform initializers for classes with private names.
Aug 20, 2018
0c22191
Fix class expression transformation, punt on IIFE class wrappers
Aug 21, 2018
34be11c
Only transform classes when there are private names.
Aug 21, 2018
8cbf34f
Fix weak map generation and class transformation
Aug 21, 2018
4f1a883
Fix build error and cast __String to string
Aug 21, 2018
d66e135
Fix out-of-order private named property initialization
Sep 11, 2018
fab8ff9
Clean up private name assignment expression temp variable emit
Sep 11, 2018
4fb9082
Remove private named property declaration initializer in TS transform
Sep 11, 2018
fec6ad8
Emit unchanged output for undeclared private names
Sep 11, 2018
9916d1c
Update privateNameField baseline
Sep 11, 2018
3b28ff7
Accept public API changes
Sep 11, 2018
1c4554a
Create initialized private named field conformance test
Sep 11, 2018
8ba9238
Create private named field accessor conformance test
Sep 11, 2018
c1d12f2
Create private named field mutation conformance test
Sep 11, 2018
c8ee370
Create private named field binary mutator conformance test
Sep 11, 2018
ddc58f1
Create private name non-trivial receiver mutation conformance test
Sep 11, 2018
2824474
Fix private name assignment referencing private names in the RHS expr
Sep 11, 2018
e2ce860
Create private named field initialization order conformance test
Sep 11, 2018
a63b52d
Revert unnecessary changes
Sep 11, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3311,6 +3311,11 @@ namespace ts {
transformFlags |= TransformFlags.ContainsPropertyInitializer;
}

// Private names are an ESNext feature.
if (isPrivateName(node.name)) {
transformFlags |= TransformFlags.AssertESNext;
}

node.transformFlags = transformFlags | TransformFlags.HasComputedFlags;
return transformFlags & ~TransformFlags.NodeExcludes;
}
Expand Down Expand Up @@ -3448,6 +3453,11 @@ namespace ts {
transformFlags |= TransformFlags.ContainsSuper;
}

// Private names are an ESNext feature.
if (isPrivateName(node.name)) {
transformFlags |= TransformFlags.AssertESNext;
}

node.transformFlags = transformFlags | TransformFlags.HasComputedFlags;
return transformFlags & ~TransformFlags.PropertyAccessExcludes;
}
Expand Down
12 changes: 10 additions & 2 deletions src/compiler/transformers/es2015.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3306,11 +3306,14 @@ namespace ts {

// The class statements are the statements generated by visiting the first statement with initializer of the
// body (1), while all other statements are added to remainingStatements (2)
const isVariableStatementWithInitializer = (stmt: Statement) => isVariableStatement(stmt) && !!first(stmt.declarationList.declarations).initializer;
const isVariableStatementWithInitializer = (stmt: Statement) => !isEndOfDeclarationMarker(stmt) &&
isVariableStatement(stmt) && !!first(stmt.declarationList.declarations).initializer;
const isEndOfDeclarationMarker = (stmt: Statement) => stmt.kind === SyntaxKind.EndOfDeclarationMarker;
const bodyStatements = visitNodes(body.statements, visitor, isStatement);
const classStatements = filter(bodyStatements, isVariableStatementWithInitializer);
const remainingStatements = filter(bodyStatements, stmt => !isVariableStatementWithInitializer(stmt));
const varStatement = cast(first(classStatements), isVariableStatement);
const endOfDeclarationMarkers = filter(bodyStatements, isEndOfDeclarationMarker);

// We know there is only one variable declaration here as we verified this in an
// earlier call to isTypeScriptClassWrapper
Expand Down Expand Up @@ -3382,12 +3385,17 @@ namespace ts {
addRange(statements, funcStatements, classBodyEnd + 1);
}

// Add other class statements (such as the WeakMap declarations output by the 'esnext'
// transformer for private names).
addRange(statements, classStatements, /*start*/ 1);

// Add the remaining statements of the outer wrapper.
addRange(statements, remainingStatements);

// The 'es2015' class transform may add an end-of-declaration marker. If so we will add it
// after the remaining statements from the 'ts' transformer.
addRange(statements, classStatements, /*start*/ 1);
addRange(statements, endOfDeclarationMarkers);


// Recreate any outer parentheses or partially-emitted expressions to preserve source map
// and comment locations.
Expand Down
277 changes: 277 additions & 0 deletions src/compiler/transformers/esnext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ namespace ts {
let enclosingFunctionFlags: FunctionFlags;
let enclosingSuperContainerFlags: NodeCheckFlags = 0;


/**
* Maps private names to the generated name of the WeakMap.
*/
interface PrivateNameEnvironment {
[name: string]: {
weakMap: Identifier;
initializer?: Expression;
};
}
const privateNameEnvironmentStack: PrivateNameEnvironment[] = [];
let privateNameEnvironmentIndex = -1;

return chainBundle(transformSourceFile);

function transformSourceFile(node: SourceFile) {
Expand Down Expand Up @@ -101,11 +114,204 @@ namespace ts {
return visitParenthesizedExpression(node as ParenthesizedExpression, noDestructuringValue);
case SyntaxKind.CatchClause:
return visitCatchClause(node as CatchClause);
case SyntaxKind.PropertyAccessExpression:
return visitPropertyAccessExpression(node as PropertyAccessExpression);
case SyntaxKind.ClassDeclaration:
return visitClassDeclaration(node as ClassDeclaration);
case SyntaxKind.ClassExpression:
return visitClassExpression(node as ClassExpression);
default:
return visitEachChild(node, visitor, context);
}
}

function currentPrivateNameEnvironment() {
return privateNameEnvironmentStack[privateNameEnvironmentIndex];
}

function addPrivateName(name: PrivateName, initializer?: Expression) {
const environment = currentPrivateNameEnvironment();
const nameString = name.escapedText as string;
if (nameString in environment) {
throw new Error("Redeclaring private name " + nameString + ".");
}
const weakMap = createFileLevelUniqueName("_" + nameString.substring(1));
environment[nameString] = {
weakMap,
initializer
};
return weakMap;
}

function accessPrivateName(name: PrivateName) {
const environment = currentPrivateNameEnvironment();
const nameString = name.escapedText as string;
if (nameString in environment) {
return environment[nameString].weakMap;
}
// Undeclared private name.
return undefined;
}

function visitPropertyAccessExpression(node: PropertyAccessExpression): Expression {
if (isPrivateName(node.name)) {
const weakMapName = accessPrivateName(node.name);
if (!weakMapName) {
return node;
}
return setOriginalNode(
setTextRange(
createClassPrivateFieldGetHelper(context, node.expression, weakMapName),
/* location */ node
),
node
);
}
return visitEachChild(node, visitor, context);
}

function visitorCollectPrivateNames(node: Node): VisitResult<Node> {
if (isPrivateNamedPropertyDeclaration(node)) {
addPrivateName(node.name);
return undefined;
}
// Don't collect private names from nested classes.
if (isClassLike(node)) {
return node;
}
return visitEachChild(node, visitorCollectPrivateNames, context);
}

function visitClassDeclaration(node: ClassDeclaration) {
startPrivateNameEnvironment();
node = visitEachChild(node, visitorCollectPrivateNames, context);
node = visitEachChild(node, visitor, context);
const statements = createPrivateNameWeakMapDeclarations(
currentPrivateNameEnvironment()
);
if (statements.length) {
node = updateClassDeclaration(
node,
node.decorators,
node.modifiers,
node.name,
node.typeParameters,
node.heritageClauses,
transformClassMembers(node.members)
);
}
statements.unshift(node);
endPrivateNameEnvironment();
return statements;
}

function visitClassExpression(node: ClassExpression) {
startPrivateNameEnvironment();
node = visitEachChild(node, visitorCollectPrivateNames, context);
node = visitEachChild(node, visitor, context);
const expressions = createPrivateNameWeakMapAssignments(
currentPrivateNameEnvironment()
);
if (expressions.length) {
node = updateClassExpression(
node,
node.modifiers,
node.name,
node.typeParameters,
node.heritageClauses,
transformClassMembers(node.members)
);
}
expressions.push(node);
endPrivateNameEnvironment();
return expressions.length > 1 ? createCommaList(expressions) : expressions[0];
}

function startPrivateNameEnvironment() {
// Create private name environment.
privateNameEnvironmentStack[++privateNameEnvironmentIndex] = {};
return currentPrivateNameEnvironment();
}

function endPrivateNameEnvironment(): PrivateNameEnvironment {
const privateNameEnvironment = currentPrivateNameEnvironment();
// Destroy private name environment.
delete privateNameEnvironmentStack[privateNameEnvironmentIndex--];
return privateNameEnvironment;
}

function createPrivateNameWeakMapDeclarations(environment: PrivateNameEnvironment): Statement[] {
return Object.keys(environment).map(name => {
const privateName = environment[name];
return createVariableStatement(
/* modifiers */ undefined,
[createVariableDeclaration(privateName.weakMap,
/* typeNode */ undefined,
createNew(
createIdentifier("WeakMap"),
/* typeArguments */ undefined,
/* argumentsArray */ undefined
))]
);
});
}

function createPrivateNameWeakMapAssignments(environment: PrivateNameEnvironment): Expression[] {
return Object.keys(environment).map(name => {
const privateName = environment[name];
hoistVariableDeclaration(privateName.weakMap);
return createBinary(
privateName.weakMap,
SyntaxKind.EqualsToken,
createNew(createIdentifier("WeakMap"), /* typeArguments */ undefined, /* argumentsArray */ undefined)
);
});
}

function transformClassMembers(members: ReadonlyArray<ClassElement>): ClassElement[] {
// Rewrite constructor with private name initializers.
const privateNameEnvironment = currentPrivateNameEnvironment();
// Initialize private properties.
const initializerStatements = Object.keys(privateNameEnvironment).map(name => {
const privateName = privateNameEnvironment[name];
return createStatement(
createCall(
createPropertyAccess(privateName.weakMap, "set"),
/* typeArguments */ undefined,
[createThis(), privateName.initializer || createVoidZero()]
)
);
});
const ctor = find(
members,
(member) => isConstructorDeclaration(member) && !!member.body
) as ConstructorDeclaration | undefined;
if (ctor) {
const body = updateBlock(ctor.body!, [...initializerStatements, ...ctor.body!.statements]);
return members.map(member => {
if (member === ctor) {
return updateConstructor(
ctor,
ctor.decorators,
ctor.modifiers,
ctor.parameters,
body
);
}
return member;
});
}
return [
createConstructor(
/* decorators */ undefined,
/* modifiers */ undefined,
/* parameters */ [],
createBlock(initializerStatements)
),
...members
];
}

function visitAwaitExpression(node: AwaitExpression): Expression {
if (enclosingFunctionFlags & FunctionFlags.Async && enclosingFunctionFlags & FunctionFlags.Generator) {
return setOriginalNode(
Expand Down Expand Up @@ -266,6 +472,55 @@ namespace ts {
visitNode(node.right, noDestructuringValue ? visitorNoDestructuringValue : visitor, isExpression)
);
}
else if (isAssignmentOperator(node.operatorToken.kind) &&
isPropertyAccessExpression(node.left) &&
isPrivateName(node.left.name)) {

const weakMapName = accessPrivateName(node.left.name);
if (!weakMapName) {
// Don't change output for undeclared private names (error).
return node;
}
if (isCompoundAssignment(node.operatorToken.kind)) {
let setReceiver: Expression;
let getReceiver: Expression;
const receiverExpr = node.left.expression;
if (!isIdentifier(receiverExpr) && !isThisProperty(node.left) && !isSuperProperty(node.left)) {
const tempVariable = createTempVariable(/* recordTempVariable */ undefined);
hoistVariableDeclaration(tempVariable);
setReceiver = createBinary(tempVariable, SyntaxKind.EqualsToken, node.left.expression);
getReceiver = tempVariable;
}
else {
getReceiver = node.left.expression;
setReceiver = node.left.expression;
}
return setOriginalNode(
createClassPrivateFieldSetHelper(
context,
setReceiver,
weakMapName,
createBinary(
createClassPrivateFieldGetHelper(context, getReceiver, weakMapName),
getOperatorForCompoundAssignment(node.operatorToken.kind),
visitNode(node.right, visitor)
)
),
node
);
}
else {
return setOriginalNode(
createClassPrivateFieldSetHelper(
context,
node.left.expression,
weakMapName,
visitNode(node.right, visitor)
),
node
);
}
}
return visitEachChild(node, visitor, context);
}

Expand Down Expand Up @@ -917,6 +1172,28 @@ namespace ts {
);
}

const classPrivateFieldGetHelper: EmitHelper = {
name: "typescript:classPrivateFieldGet",
scoped: false,
text: `var _classPrivateFieldGet = function (receiver, privateMap) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } return privateMap.get(receiver); };`
};

function createClassPrivateFieldGetHelper(context: TransformationContext, receiver: Expression, privateField: Identifier) {
context.requestEmitHelper(classPrivateFieldGetHelper);
return createCall(getHelperName("_classPrivateFieldGet"), /* typeArguments */ undefined, [ receiver, privateField ]);
}

const classPrivateFieldSetHelper: EmitHelper = {
name: "typescript:classPrivateFieldSet",
scoped: false,
text: `var _classPrivateFieldSet = function (receiver, privateMap, value) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to set private field on non-instance"); } privateMap.set(receiver, value); return value; };`
};

function createClassPrivateFieldSetHelper(context: TransformationContext, receiver: Expression, privateField: Identifier, value: Expression) {
context.requestEmitHelper(classPrivateFieldSetHelper);
return createCall(getHelperName("_classPrivateFieldSet"), /* typeArguments */ undefined, [ receiver, privateField, value ]);
}

const awaitHelper: EmitHelper = {
name: "typescript:await",
scoped: false,
Expand Down
22 changes: 0 additions & 22 deletions src/compiler/transformers/generators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -667,28 +667,6 @@ namespace ts {
}
}

function isCompoundAssignment(kind: BinaryOperator): kind is CompoundAssignmentOperator {
return kind >= SyntaxKind.FirstCompoundAssignment
&& kind <= SyntaxKind.LastCompoundAssignment;
}

function getOperatorForCompoundAssignment(kind: CompoundAssignmentOperator): BitwiseOperatorOrHigher {
switch (kind) {
case SyntaxKind.PlusEqualsToken: return SyntaxKind.PlusToken;
case SyntaxKind.MinusEqualsToken: return SyntaxKind.MinusToken;
case SyntaxKind.AsteriskEqualsToken: return SyntaxKind.AsteriskToken;
case SyntaxKind.AsteriskAsteriskEqualsToken: return SyntaxKind.AsteriskAsteriskToken;
case SyntaxKind.SlashEqualsToken: return SyntaxKind.SlashToken;
case SyntaxKind.PercentEqualsToken: return SyntaxKind.PercentToken;
case SyntaxKind.LessThanLessThanEqualsToken: return SyntaxKind.LessThanLessThanToken;
case SyntaxKind.GreaterThanGreaterThanEqualsToken: return SyntaxKind.GreaterThanGreaterThanToken;
case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: return SyntaxKind.GreaterThanGreaterThanGreaterThanToken;
case SyntaxKind.AmpersandEqualsToken: return SyntaxKind.AmpersandToken;
case SyntaxKind.BarEqualsToken: return SyntaxKind.BarToken;
case SyntaxKind.CaretEqualsToken: return SyntaxKind.CaretToken;
}
}

/**
* Visits a right-associative binary expression containing `yield`.
*
Expand Down
Loading