Skip to content

(WIP) Private name class field transformation #8

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
eac1bf8
Update Travis-CI configuration to build es-private-fields branch
kpfleming Aug 21, 2018
548c9f8
Parse Private Names
Jul 8, 2018
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
6 changes: 1 addition & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,7 @@ matrix:

branches:
only:
- master
- release-2.7
- release-2.8
- release-2.9
- release-3.0
- es-private-fields

install:
- npm uninstall typescript --no-save
Expand Down
23 changes: 18 additions & 5 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,9 @@ namespace ts {
Debug.assert(isWellKnownSymbolSyntactically(nameExpression));
return getPropertyNameForKnownSymbolName(idText((<PropertyAccessExpression>nameExpression).name));
}
if (isPrivateName(node)) {
return nodePosToString(node) as __String;
}
return isPropertyNameLiteral(name) ? getEscapedTextOfIdentifierOrLiteral(name) : undefined;
}
switch (node.kind) {
Expand Down Expand Up @@ -1394,7 +1397,7 @@ namespace ts {
}
if (node.expression.kind === SyntaxKind.PropertyAccessExpression) {
const propertyAccess = <PropertyAccessExpression>node.expression;
if (isNarrowableOperand(propertyAccess.expression) && isPushOrUnshiftIdentifier(propertyAccess.name)) {
if (isIdentifier(propertyAccess.name) && isNarrowableOperand(propertyAccess.expression) && isPushOrUnshiftIdentifier(propertyAccess.name)) {
currentFlow = createFlowArrayMutation(currentFlow, node);
}
}
Expand Down Expand Up @@ -2359,7 +2362,7 @@ namespace ts {
return;
}
const lhs = node.left as PropertyAccessEntityNameExpression;
const symbol = forEachIdentifierInEntityName(lhs.expression, /*parent*/ undefined, (id, symbol) => {
const symbol = forEachIdentifierOrPrivateNameInEntityName(lhs.expression, /*parent*/ undefined, (id, symbol) => {
if (symbol) {
addDeclarationToSymbol(symbol, id, SymbolFlags.Module | SymbolFlags.JSContainer);
}
Expand Down Expand Up @@ -2518,7 +2521,7 @@ namespace ts {
// make symbols or add declarations for intermediate containers
const flags = SymbolFlags.Module | SymbolFlags.JSContainer;
const excludeFlags = SymbolFlags.ValueModuleExcludes & ~SymbolFlags.JSContainer;
namespaceSymbol = forEachIdentifierInEntityName(propertyAccess.expression, namespaceSymbol, (id, symbol, parent) => {
namespaceSymbol = forEachIdentifierOrPrivateNameInEntityName(propertyAccess.expression, namespaceSymbol, (id, symbol, parent) => {
if (symbol) {
addDeclarationToSymbol(symbol, id, flags);
return symbol;
Expand Down Expand Up @@ -2590,15 +2593,15 @@ namespace ts {
}
}

function forEachIdentifierInEntityName(e: EntityNameExpression, parent: Symbol | undefined, action: (e: Identifier, symbol: Symbol | undefined, parent: Symbol | undefined) => Symbol | undefined): Symbol | undefined {
function forEachIdentifierOrPrivateNameInEntityName(e: EntityNameExpression, parent: Symbol | undefined, action: (e: Identifier | PrivateName, symbol: Symbol | undefined, parent: Symbol | undefined) => Symbol | undefined): Symbol | undefined {
if (isExportsOrModuleExportsOrAlias(file, e)) {
return file.symbol;
}
else if (isIdentifier(e)) {
return action(e, lookupSymbolForPropertyAccess(e), parent);
}
else {
const s = forEachIdentifierInEntityName(e.expression, parent, action);
const s = forEachIdentifierOrPrivateNameInEntityName(e.expression, parent, action);
if (!s || !s.exports) return Debug.fail();
return action(e.name, s.exports.get(e.name.escapedText), s);
}
Expand Down Expand Up @@ -3308,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 @@ -3445,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
31 changes: 20 additions & 11 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14346,8 +14346,10 @@ namespace ts {
const root = getReferenceRoot(node);
const parent = root.parent;
const isLengthPushOrUnshift = parent.kind === SyntaxKind.PropertyAccessExpression && (
(<PropertyAccessExpression>parent).name.escapedText === "length" ||
parent.parent.kind === SyntaxKind.CallExpression && isPushOrUnshiftIdentifier((<PropertyAccessExpression>parent).name));
(<PropertyAccessExpression>parent).name.escapedText === "length" || (
parent.parent.kind === SyntaxKind.CallExpression
&& isIdentifier((parent as PropertyAccessExpression).name)
&& isPushOrUnshiftIdentifier((parent as PropertyAccessExpression).name as Identifier)));
const isElementAssignment = parent.kind === SyntaxKind.ElementAccessExpression &&
(<ElementAccessExpression>parent).expression === root &&
parent.parent.kind === SyntaxKind.BinaryExpression &&
Expand Down Expand Up @@ -17972,7 +17974,7 @@ namespace ts {
return checkPropertyAccessExpressionOrQualifiedName(node, node.left, node.right);
}

function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, right: Identifier) {
function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, right: Identifier | PrivateName) {
let propType: Type;
const leftType = checkNonNullExpression(left);
const parentSymbol = getNodeLinks(left).resolvedSymbol;
Expand All @@ -17990,7 +17992,7 @@ namespace ts {
}
if (!prop) {
const indexInfo = getIndexInfoOfType(apparentType, IndexKind.String);
if (!(indexInfo && indexInfo.type)) {
if (!(indexInfo && indexInfo.type) || isPrivateName(right)) {
if (isJSLiteralType(leftType)) {
return anyType;
}
Expand Down Expand Up @@ -18048,7 +18050,7 @@ namespace ts {
return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType;
}

function checkPropertyNotUsedBeforeDeclaration(prop: Symbol, node: PropertyAccessExpression | QualifiedName, right: Identifier): void {
function checkPropertyNotUsedBeforeDeclaration(prop: Symbol, node: PropertyAccessExpression | QualifiedName, right: Identifier | PrivateName): void {
const { valueDeclaration } = prop;
if (!valueDeclaration) {
return;
Expand Down Expand Up @@ -18118,7 +18120,7 @@ namespace ts {
return getIntersectionType(x);
}

function reportNonexistentProperty(propNode: Identifier, containingType: Type) {
function reportNonexistentProperty(propNode: Identifier | PrivateName, containingType: Type) {
let errorInfo: DiagnosticMessageChain | undefined;
let relatedInfo: Diagnostic | undefined;
if (containingType.flags & TypeFlags.Union && !(containingType.flags & TypeFlags.Primitive)) {
Expand Down Expand Up @@ -18161,11 +18163,11 @@ namespace ts {
return prop !== undefined && prop.valueDeclaration && hasModifier(prop.valueDeclaration, ModifierFlags.Static);
}

function getSuggestedSymbolForNonexistentProperty(name: Identifier | string, containingType: Type): Symbol | undefined {
function getSuggestedSymbolForNonexistentProperty(name: Identifier | PrivateName | string, containingType: Type): Symbol | undefined {
return getSpellingSuggestionForName(isString(name) ? name : idText(name), getPropertiesOfType(containingType), SymbolFlags.Value);
}

function getSuggestionForNonexistentProperty(name: Identifier | string, containingType: Type): string | undefined {
function getSuggestionForNonexistentProperty(name: Identifier | PrivateName | string, containingType: Type): string | undefined {
const suggestion = getSuggestedSymbolForNonexistentProperty(name, containingType);
return suggestion && symbolName(suggestion);
}
Expand Down Expand Up @@ -21876,6 +21878,9 @@ namespace ts {
checkGrammarDecoratorsAndModifiers(node);

checkVariableLikeDeclaration(node);
if (node.name && isIdentifier(node.name) && node.name.originalKeywordKind === SyntaxKind.PrivateName) {
error(node, Diagnostics.Private_names_cannot_be_used_as_parameters);
}
const func = getContainingFunction(node)!;
if (hasModifier(node, ModifierFlags.ParameterPropertyModifier)) {
if (!(func.kind === SyntaxKind.Constructor && nodeIsPresent(func.body))) {
Expand Down Expand Up @@ -23523,9 +23528,9 @@ namespace ts {
}
}

function getIdentifierFromEntityNameExpression(node: Identifier | PropertyAccessExpression): Identifier;
function getIdentifierFromEntityNameExpression(node: Expression): Identifier | undefined;
function getIdentifierFromEntityNameExpression(node: Expression): Identifier | undefined {
function getIdentifierFromEntityNameExpression(node: Identifier | PropertyAccessExpression): Identifier | PrivateName;
function getIdentifierFromEntityNameExpression(node: Expression): Identifier | PrivateName | undefined;
function getIdentifierFromEntityNameExpression(node: Expression): Identifier | PrivateName | undefined {
switch (node.kind) {
case SyntaxKind.Identifier:
return node as Identifier;
Expand Down Expand Up @@ -29288,6 +29293,10 @@ namespace ts {
checkESModuleMarker(node.name);
}

if (isIdentifier(node.name) && node.name.originalKeywordKind === SyntaxKind.PrivateName) {
return grammarErrorOnNode(node.name, Diagnostics.Private_names_are_not_allowed_in_variable_declarations);
}

const checkLetConstNames = (isLet(node) || isVarConst(node));

// 1. LexicalDeclaration : LetOrConst BindingList ;
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -4166,6 +4166,14 @@
"category": "Error",
"code": 18003
},
"Private names are not allowed in variable declarations.": {
"category": "Error",
"code": 18004
},
"Private names cannot be used as parameters": {
"category": "Error",
"code": 18005
},

"File is a CommonJS module; it may be converted to an ES6 module.": {
"category": "Suggestion",
Expand Down
16 changes: 15 additions & 1 deletion src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,10 @@ namespace ts {
case SyntaxKind.Identifier:
return emitIdentifier(<Identifier>node);

// PrivateNames
case SyntaxKind.PrivateName:
return emitPrivateName(node as PrivateName);

// Parse tree nodes

// Names
Expand Down Expand Up @@ -878,6 +882,10 @@ namespace ts {
case SyntaxKind.Identifier:
return emitIdentifier(<Identifier>node);

// Private Names
case SyntaxKind.PrivateName:
return emitPrivateName(node as PrivateName);

// Reserved words
case SyntaxKind.FalseKeyword:
case SyntaxKind.NullKeyword:
Expand Down Expand Up @@ -1067,6 +1075,12 @@ namespace ts {
emitList(node, node.typeArguments, ListFormat.TypeParameters); // Call emitList directly since it could be an array of TypeParameterDeclarations _or_ type arguments
}

function emitPrivateName(node: PrivateName) {
const writeText = node.symbol ? writeSymbol : write;
writeText(getTextOfNode(node, /*includeTrivia*/ false), node.symbol);
emitList(node, /*typeArguments*/ undefined, ListFormat.TypeParameters); // Call emitList directly since it could be an array of TypeParameterDeclarations _or_ type arguments
}

//
// Names
//
Expand Down Expand Up @@ -3302,7 +3316,7 @@ namespace ts {
function getLiteralTextOfNode(node: LiteralLikeNode): string {
if (node.kind === SyntaxKind.StringLiteral && (<StringLiteral>node).textSourceNode) {
const textSourceNode = (<StringLiteral>node).textSourceNode!;
if (isIdentifier(textSourceNode)) {
if (isIdentifierOrPrivateName(textSourceNode)) {
return getEmitFlags(node) & EmitFlags.NoAsciiEscaping ?
`"${escapeString(getTextOfNode(textSourceNode))}"` :
`"${escapeNonAsciiString(getTextOfNode(textSourceNode))}"`;
Expand Down
22 changes: 14 additions & 8 deletions src/compiler/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ namespace ts {
return node;
}

function createLiteralFromNode(sourceNode: PropertyNameLiteral): StringLiteral {
function createLiteralFromNode(sourceNode: Exclude<PropertyNameLiteral, PrivateName>): StringLiteral {
const node = createStringLiteral(getTextOfIdentifierOrLiteral(sourceNode));
node.textSourceNode = sourceNode;
return node;
Expand Down Expand Up @@ -995,15 +995,15 @@ namespace ts {
: node;
}

export function createPropertyAccess(expression: Expression, name: string | Identifier | undefined) {
export function createPropertyAccess(expression: Expression, name: string | Identifier | PrivateName | undefined) {
const node = <PropertyAccessExpression>createSynthesizedNode(SyntaxKind.PropertyAccessExpression);
node.expression = parenthesizeForAccess(expression);
node.name = asName(name)!; // TODO: GH#18217
setEmitFlags(node, EmitFlags.NoIndentation);
return node;
}

export function updatePropertyAccess(node: PropertyAccessExpression, expression: Expression, name: Identifier) {
export function updatePropertyAccess(node: PropertyAccessExpression, expression: Expression, name: Identifier | PrivateName) {
// Because we are updating existed propertyAccess we want to inherit its emitFlags
// instead of using the default from createPropertyAccess
return node.expression !== expression
Expand Down Expand Up @@ -2714,7 +2714,7 @@ namespace ts {

// Utilities

function asName<T extends Identifier | BindingName | PropertyName | EntityName | ThisTypeNode | undefined>(name: string | T): T | Identifier {
function asName<T extends Identifier | PrivateName | BindingName | PropertyName | EntityName | ThisTypeNode | undefined>(name: string | T): T | Identifier {
return isString(name) ? createIdentifier(name) : name;
}

Expand Down Expand Up @@ -3099,7 +3099,7 @@ namespace ts {
}
else {
const expression = setTextRange(
isIdentifier(memberName)
(isIdentifier(memberName) || isPrivateName(memberName))
? createPropertyAccess(target, memberName)
: createElementAccess(target, memberName),
memberName
Expand Down Expand Up @@ -3529,7 +3529,7 @@ namespace ts {
}
}

export function createExpressionForPropertyName(memberName: PropertyName): Expression {
export function createExpressionForPropertyName(memberName: Exclude<PropertyName, PrivateName>): Expression {
if (isIdentifier(memberName)) {
return createLiteral(memberName);
}
Expand All @@ -3541,11 +3541,17 @@ namespace ts {
}
}

/**
* accessor declaration that can be converted to an expression (`name` field cannot be a `PrivateName`)
*/
type ExpressionableAccessorDeclaration = AccessorDeclaration & {name: Exclude<PropertyName, PrivateName>};

export function createExpressionForObjectLiteralElementLike(node: ObjectLiteralExpression, property: ObjectLiteralElementLike, receiver: Expression): Expression | undefined {
switch (property.kind) {
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
return createExpressionForAccessorDeclaration(node.properties, property, receiver, !!node.multiLine);
// type assertion `as ExpressionableAccessorDeclaration` is safe because PrivateNames are not allowed in object literals
return createExpressionForAccessorDeclaration(node.properties, property as ExpressionableAccessorDeclaration, receiver, !!node.multiLine);
case SyntaxKind.PropertyAssignment:
return createExpressionForPropertyAssignment(property, receiver);
case SyntaxKind.ShorthandPropertyAssignment:
Expand All @@ -3555,7 +3561,7 @@ namespace ts {
}
}

function createExpressionForAccessorDeclaration(properties: NodeArray<Declaration>, property: AccessorDeclaration, receiver: Expression, multiLine: boolean) {
function createExpressionForAccessorDeclaration(properties: NodeArray<Declaration>, property: ExpressionableAccessorDeclaration, receiver: Expression, multiLine: boolean) {
const { firstAccessor, getAccessor, setAccessor } = getAllAccessorDeclarations(properties, property);
if (property === firstAccessor) {
const properties: ObjectLiteralElementLike[] = [];
Expand Down
Loading