Skip to content

Support __proto__ in object literal (type-check only) #48816

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
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 @@ -27633,6 +27633,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 @@ -27666,8 +27686,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 ?
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 TS1117: An object literal cannot have multiple properties with the same name.
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 TS1117: An object literal cannot have multiple properties with the same name.

// 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: '' }));
// 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 () { } };
var e;
(function (e) {
e[e["__proto__"] = 1] = "__proto__";
})(e || (e = {}));
{
var __proto__1 = (_b = {}, _b['__proto__'] = 1, _b).__proto__;
}
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