Skip to content
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

feat: support __proto__ in object literal (type-check only) #42359

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
38 changes: 38 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25103,6 +25103,26 @@ namespace ts {
return links.immediateTarget;
}

/**
* If the node is a `__proto__` literal declaration.
* According to ECMA262 B.3.1 only this syntax counted in
* PropertyName ":" AssignmentExpression
*
* It works in thoes forms:
* { __proto__: value }; { "__proto__": value }; { __proto_\u005f: value };
* It does not work in thoes forms:
* { ["__proto__"]: value }; { __proto__ }
*/
function getProtoLiteralDeclarationInitializer(node: ObjectLiteralElementLike): false | Expression {
if (node.kind !== SyntaxKind.PropertyAssignment) return false;
const name = node.name;
if (!name) return false;
// Identifier.escapedText says if the identifier starts with __, it will starts with 3 "_".
if (name.kind === SyntaxKind.Identifier && name.escapedText === "___proto__") return node.initializer;
if (name.kind === SyntaxKind.StringLiteral && name.text === "__proto__") return node.initializer;
return false;
}

function checkObjectLiteral(node: ObjectLiteralExpression, checkMode?: CheckMode): Type {
const inDestructuringPattern = isAssignmentTarget(node);
// Grammar checking
Expand Down Expand Up @@ -25135,8 +25155,26 @@ namespace ts {
}
}

const skippedProperties = new Set<ObjectLiteralElementLike>();
// It should be the left-most spread element so we loop this first
for (const memberDecl of node.properties) {
const item = getProtoLiteralDeclarationInitializer(memberDecl);
if (!item) continue;
// Don't add __proto__ literal to the properties table
skippedProperties.add(memberDecl);
// it's prototype is null so it works like normal object (despite Object.prototype things).
if (item.kind === SyntaxKind.NullKeyword) continue;
const type = getReducedType(checkExpression(item));
if (!(type.flags & TypeFlags.Object)) {
error(memberDecl, Diagnostics.Type_0_is_not_assignable_to_type_1, typeToString(type), "object | null");
}
else {
spread = getSpreadType(spread, type, node.symbol, objectFlags, inConstContext);
}
}
let offset = 0;
for (const memberDecl of node.properties) {
if (skippedProperties.has(memberDecl)) continue;
let member = getSymbolOfNode(memberDecl);
const computedNameType = memberDecl.name && memberDecl.name.kind === SyntaxKind.ComputedPropertyName && !isWellKnownSymbolSyntactically(memberDecl.name.expression) ?
checkComputedPropertyName(memberDecl.name) : undefined;
Expand Down
38 changes: 38 additions & 0 deletions tests/baselines/reference/__proto__literal.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
tests/cases/compiler/__proto__literal.ts(10,28): error TS2300: Duplicate identifier '__proto_\u005f'.
tests/cases/compiler/__proto__literal.ts(20,13): error TS2322: Type '1' is not assignable to type 'object | null'.


==== tests/cases/compiler/__proto__literal.ts (2 errors) ====
const o = { a: 1 }
const __proto__ = o
// Should
const x1 = { __proto__: o }
const x2 = { __proto_\u005f: o }
const x3 = { "__proto__": o }
const x4 = { "__proto_\u005f": o }

// Duplicate
const y1 = { __proto__: o, __proto_\u005f: o }
~~~~~~~~~~~~~~
!!! error TS2300: Duplicate identifier '__proto_\u005f'.

// Spread order
const z1 = { ...({a: ''}), __proto__: o }
const z2 = { __proto__: o, ...({a: ''}) }

// Null
const w = { __proto__: null }

// Non-object
const q = { __proto__: 1, x: 1 }
~~~~~~~~~~~~
!!! error TS2322: Type '1' is not assignable to type 'object | null'.

// Should not
const x5 = { ["__proto__"]: o }
const x6 = { __proto__ }
const x7 = { __proto__() {} }
enum e { __proto__ = 1 }
{
const { __proto__ } = { ['__proto__']: 1 }
}
71 changes: 71 additions & 0 deletions tests/baselines/reference/__proto__literal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//// [__proto__literal.ts]
const o = { a: 1 }
const __proto__ = o
// Should
const x1 = { __proto__: o }
const x2 = { __proto_\u005f: o }
const x3 = { "__proto__": o }
const x4 = { "__proto_\u005f": o }

// Duplicate
const y1 = { __proto__: o, __proto_\u005f: o }

// Spread order
const z1 = { ...({a: ''}), __proto__: o }
const z2 = { __proto__: o, ...({a: ''}) }

// Null
const w = { __proto__: null }

// Non-object
const q = { __proto__: 1, x: 1 }

// Should not
const x5 = { ["__proto__"]: o }
const x6 = { __proto__ }
const x7 = { __proto__() {} }
enum e { __proto__ = 1 }
{
const { __proto__ } = { ['__proto__']: 1 }
}

//// [__proto__literal.js]
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var _a, _b;
var o = { a: 1 };
var __proto__ = o;
// Should
var x1 = { __proto__: o };
var x2 = { __proto_\u005f: o };
var x3 = { "__proto__": o };
var x4 = { "__proto_\u005f": o };
// Duplicate
var y1 = { __proto__: o, __proto_\u005f: o };
// Spread order
var z1 = __assign(__assign({}, ({ a: '' })), { __proto__: o });
var z2 = __assign({ __proto__: o }, ({ a: '' }));
Comment on lines +55 to +56
Copy link
Contributor

@ExE-Boss ExE-Boss Jan 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The __proto__ property initialiser is supposed to set the [[Prototype]] of the first object, so this has to be:

var z1 = Object.setPrototypeOf(__assign({}, ({ a: '' })), o);
var z2 = __assign({ __proto__: o }, ({ a: '' }));

Although engines most likely do something like:

var z1 = __assign({ __proto__: o }, ({ a: '' }));
var z2 = __assign({ __proto__: o }, ({ a: '' }));

for both.

// Null
var w = { __proto__: null };
// Non-object
var q = { __proto__: 1, x: 1 };
// Should not
var x5 = (_a = {}, _a["__proto__"] = o, _a);
var x6 = { __proto__: __proto__ };
var x7 = { __proto__: function () { } };
Comment on lines +63 to +64
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two have to be transpiled as:

var x6 = {};
Object.defineProperty(x6, "__proto__", { value: __proto__, configurable: true, enumerable: true, writable: true });
var x7 = {};
Object.defineProperty(x7, "__proto__", { value: function () { }, configurable: true, enumerable: true, writable: true });

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does ES5 support { ['__proto__']: val}?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, it doesn’t.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, not lucky

var e;
(function (e) {
e[e["__proto__"] = 1] = "__proto__";
})(e || (e = {}));
Comment on lines +66 to +68
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is incorrect because of the legacy __proto__ accessor, so it has to use Object.defineProperty.

{
var __proto__1 = (_b = {}, _b['__proto__'] = 1, _b).__proto__;
}
Comment on lines +69 to +71
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here for the definition of _b.

86 changes: 86 additions & 0 deletions tests/baselines/reference/__proto__literal.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
=== tests/cases/compiler/__proto__literal.ts ===
const o = { a: 1 }
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))
>a : Symbol(a, Decl(__proto__literal.ts, 0, 11))

const __proto__ = o
>__proto__ : Symbol(__proto__, Decl(__proto__literal.ts, 1, 5))
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))

// Should
const x1 = { __proto__: o }
>x1 : Symbol(x1, Decl(__proto__literal.ts, 3, 5))
>__proto__ : Symbol(__proto__, Decl(__proto__literal.ts, 3, 12))
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))

const x2 = { __proto_\u005f: o }
>x2 : Symbol(x2, Decl(__proto__literal.ts, 4, 5))
>__proto_\u005f : Symbol(__proto_\u005f, Decl(__proto__literal.ts, 4, 12))
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))

const x3 = { "__proto__": o }
>x3 : Symbol(x3, Decl(__proto__literal.ts, 5, 5))
>"__proto__" : Symbol("__proto__", Decl(__proto__literal.ts, 5, 12))
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))

const x4 = { "__proto_\u005f": o }
>x4 : Symbol(x4, Decl(__proto__literal.ts, 6, 5))
>"__proto_\u005f" : Symbol("__proto_\u005f", Decl(__proto__literal.ts, 6, 12))
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))

// Duplicate
const y1 = { __proto__: o, __proto_\u005f: o }
>y1 : Symbol(y1, Decl(__proto__literal.ts, 9, 5))
>__proto__ : Symbol(__proto__, Decl(__proto__literal.ts, 9, 12), Decl(__proto__literal.ts, 9, 26))
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))
>__proto_\u005f : Symbol(__proto__, Decl(__proto__literal.ts, 9, 12), Decl(__proto__literal.ts, 9, 26))
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))

// Spread order
const z1 = { ...({a: ''}), __proto__: o }
>z1 : Symbol(z1, Decl(__proto__literal.ts, 12, 5))
>a : Symbol(a, Decl(__proto__literal.ts, 12, 18))
>__proto__ : Symbol(__proto__, Decl(__proto__literal.ts, 12, 26))
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))

const z2 = { __proto__: o, ...({a: ''}) }
>z2 : Symbol(z2, Decl(__proto__literal.ts, 13, 5))
>__proto__ : Symbol(__proto__, Decl(__proto__literal.ts, 13, 12))
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))
>a : Symbol(a, Decl(__proto__literal.ts, 13, 32))

// Null
const w = { __proto__: null }
>w : Symbol(w, Decl(__proto__literal.ts, 16, 5))
>__proto__ : Symbol(__proto__, Decl(__proto__literal.ts, 16, 11))

// Non-object
const q = { __proto__: 1, x: 1 }
>q : Symbol(q, Decl(__proto__literal.ts, 19, 5))
>__proto__ : Symbol(__proto__, Decl(__proto__literal.ts, 19, 11))
>x : Symbol(x, Decl(__proto__literal.ts, 19, 25))

// Should not
const x5 = { ["__proto__"]: o }
>x5 : Symbol(x5, Decl(__proto__literal.ts, 22, 5))
>["__proto__"] : Symbol(["__proto__"], Decl(__proto__literal.ts, 22, 12))
>"__proto__" : Symbol(["__proto__"], Decl(__proto__literal.ts, 22, 12))
>o : Symbol(o, Decl(__proto__literal.ts, 0, 5))

const x6 = { __proto__ }
>x6 : Symbol(x6, Decl(__proto__literal.ts, 23, 5))
>__proto__ : Symbol(__proto__, Decl(__proto__literal.ts, 23, 12))

const x7 = { __proto__() {} }
>x7 : Symbol(x7, Decl(__proto__literal.ts, 24, 5))
>__proto__ : Symbol(__proto__, Decl(__proto__literal.ts, 24, 12))

enum e { __proto__ = 1 }
>e : Symbol(e, Decl(__proto__literal.ts, 24, 29))
>__proto__ : Symbol(e.__proto__, Decl(__proto__literal.ts, 25, 8))
{
const { __proto__ } = { ['__proto__']: 1 }
>__proto__ : Symbol(__proto__, Decl(__proto__literal.ts, 27, 11))
>['__proto__'] : Symbol(['__proto__'], Decl(__proto__literal.ts, 27, 27))
>'__proto__' : Symbol(['__proto__'], Decl(__proto__literal.ts, 27, 27))
}
112 changes: 112 additions & 0 deletions tests/baselines/reference/__proto__literal.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
=== tests/cases/compiler/__proto__literal.ts ===
const o = { a: 1 }
>o : { a: number; }
>{ a: 1 } : { a: number; }
>a : number
>1 : 1

const __proto__ = o
>__proto__ : { a: number; }
>o : { a: number; }

// Should
const x1 = { __proto__: o }
>x1 : { a: number; }
>{ __proto__: o } : { a: number; }
>__proto__ : { a: number; }
>o : { a: number; }

const x2 = { __proto_\u005f: o }
>x2 : { a: number; }
>{ __proto_\u005f: o } : { a: number; }
>__proto_\u005f : { a: number; }
>o : { a: number; }

const x3 = { "__proto__": o }
>x3 : { a: number; }
>{ "__proto__": o } : { a: number; }
>"__proto__" : { a: number; }
>o : { a: number; }

const x4 = { "__proto_\u005f": o }
>x4 : { a: number; }
>{ "__proto_\u005f": o } : { a: number; }
>"__proto_\u005f" : { a: number; }
>o : { a: number; }

// Duplicate
const y1 = { __proto__: o, __proto_\u005f: o }
>y1 : { a: number; }
>{ __proto__: o, __proto_\u005f: o } : { a: number; }
>__proto__ : { a: number; }
>o : { a: number; }
>__proto_\u005f : { a: number; }
>o : { a: number; }

// Spread order
const z1 = { ...({a: ''}), __proto__: o }
>z1 : { a: string; }
>{ ...({a: ''}), __proto__: o } : { a: string; }
>({a: ''}) : { a: string; }
>{a: ''} : { a: string; }
>a : string
>'' : ""
>__proto__ : { a: number; }
>o : { a: number; }

const z2 = { __proto__: o, ...({a: ''}) }
>z2 : { a: string; }
>{ __proto__: o, ...({a: ''}) } : { a: string; }
>__proto__ : { a: number; }
>o : { a: number; }
>({a: ''}) : { a: string; }
>{a: ''} : { a: string; }
>a : string
>'' : ""

// Null
const w = { __proto__: null }
>w : {}
>{ __proto__: null } : {}
>__proto__ : null
>null : null

// Non-object
const q = { __proto__: 1, x: 1 }
>q : { x: number; }
>{ __proto__: 1, x: 1 } : { x: number; }
>__proto__ : number
>1 : 1
>x : number
>1 : 1

// Should not
const x5 = { ["__proto__"]: o }
>x5 : { __proto__: { a: number; }; }
>{ ["__proto__"]: o } : { __proto__: { a: number; }; }
>["__proto__"] : { a: number; }
>"__proto__" : "__proto__"
>o : { a: number; }

const x6 = { __proto__ }
>x6 : { __proto__: { a: number; }; }
>{ __proto__ } : { __proto__: { a: number; }; }
>__proto__ : { a: number; }

const x7 = { __proto__() {} }
>x7 : { __proto__(): void; }
>{ __proto__() {} } : { __proto__(): void; }
>__proto__ : () => void

enum e { __proto__ = 1 }
>e : e
>__proto__ : e.__proto__
>1 : 1
{
const { __proto__ } = { ['__proto__']: 1 }
>__proto__ : number
>{ ['__proto__']: 1 } : { __proto__: number; }
>['__proto__'] : number
>'__proto__' : "__proto__"
>1 : 1
}
Loading