From 6046ffbdd96be6e851b96468d429e10c2b5e4faa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 17 May 2022 10:54:15 +0200 Subject: [PATCH 1/2] Avoid binding `length` property as special property on a function symbol --- src/compiler/binder.ts | 6 ++- .../functionReadonlyLength.errors.txt | 27 +++++++++++ .../reference/functionReadonlyLength.symbols | 34 ++++++++++++++ .../reference/functionReadonlyLength.types | 45 +++++++++++++++++++ .../cases/compiler/functionReadonlyLength.ts | 13 ++++++ 5 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/functionReadonlyLength.errors.txt create mode 100644 tests/baselines/reference/functionReadonlyLength.symbols create mode 100644 tests/baselines/reference/functionReadonlyLength.types create mode 100644 tests/cases/compiler/functionReadonlyLength.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 66a9f6393dc75..07ab0d0797c97 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -3049,7 +3049,11 @@ namespace ts { function bindSpecialPropertyAssignment(node: BindablePropertyAssignmentExpression) { // Class declarations in Typescript do not allow property declarations const parentSymbol = lookupSymbolForPropertyAccess(node.left.expression, container) || lookupSymbolForPropertyAccess(node.left.expression, blockScopeContainer) ; - if (!isInJSFile(node) && !isFunctionSymbol(parentSymbol)) { + const isFunctionSym = isFunctionSymbol(parentSymbol); + if (!isInJSFile(node) && !isFunctionSym) { + return; + } + if (isFunctionSym && isBindableStaticNameExpression(node.left) && getDeclarationName(node.left) === "length") { return; } const rootExpr = getLeftmostAccessExpression(node.left); diff --git a/tests/baselines/reference/functionReadonlyLength.errors.txt b/tests/baselines/reference/functionReadonlyLength.errors.txt new file mode 100644 index 0000000000000..bbfcb616ace27 --- /dev/null +++ b/tests/baselines/reference/functionReadonlyLength.errors.txt @@ -0,0 +1,27 @@ +tests/cases/compiler/functionReadonlyLength.ts(2,4): error TS2540: Cannot assign to 'length' because it is a read-only property. +tests/cases/compiler/functionReadonlyLength.ts(5,4): error TS2540: Cannot assign to 'length' because it is a read-only property. +tests/cases/compiler/functionReadonlyLength.ts(8,4): error TS2540: Cannot assign to 'length' because it is a read-only property. +tests/cases/compiler/functionReadonlyLength.ts(11,4): error TS2540: Cannot assign to 'length' because it is a read-only property. + + +==== tests/cases/compiler/functionReadonlyLength.ts (4 errors) ==== + const f0 = new Function() + f0.length = 1; + ~~~~~~ +!!! error TS2540: Cannot assign to 'length' because it is a read-only property. + + function f1() {}; + f1.length = 1; + ~~~~~~ +!!! error TS2540: Cannot assign to 'length' because it is a read-only property. + + const f2 = function () {}; + f2.length = 1 + ~~~~~~ +!!! error TS2540: Cannot assign to 'length' because it is a read-only property. + + const f3 = () => {} + f3.length = 1 + ~~~~~~ +!!! error TS2540: Cannot assign to 'length' because it is a read-only property. + \ No newline at end of file diff --git a/tests/baselines/reference/functionReadonlyLength.symbols b/tests/baselines/reference/functionReadonlyLength.symbols new file mode 100644 index 0000000000000..8c35a48c09303 --- /dev/null +++ b/tests/baselines/reference/functionReadonlyLength.symbols @@ -0,0 +1,34 @@ +=== tests/cases/compiler/functionReadonlyLength.ts === +const f0 = new Function() +>f0 : Symbol(f0, Decl(functionReadonlyLength.ts, 0, 5)) +>Function : Symbol(Function, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + +f0.length = 1; +>f0.length : Symbol(Function.length, Decl(lib.es5.d.ts, --, --)) +>f0 : Symbol(f0, Decl(functionReadonlyLength.ts, 0, 5)) +>length : Symbol(Function.length, Decl(lib.es5.d.ts, --, --)) + +function f1() {}; +>f1 : Symbol(f1, Decl(functionReadonlyLength.ts, 1, 14)) + +f1.length = 1; +>f1.length : Symbol(Function.length, Decl(lib.es5.d.ts, --, --)) +>f1 : Symbol(f1, Decl(functionReadonlyLength.ts, 1, 14)) +>length : Symbol(Function.length, Decl(lib.es5.d.ts, --, --)) + +const f2 = function () {}; +>f2 : Symbol(f2, Decl(functionReadonlyLength.ts, 6, 5)) + +f2.length = 1 +>f2.length : Symbol(Function.length, Decl(lib.es5.d.ts, --, --)) +>f2 : Symbol(f2, Decl(functionReadonlyLength.ts, 6, 5)) +>length : Symbol(Function.length, Decl(lib.es5.d.ts, --, --)) + +const f3 = () => {} +>f3 : Symbol(f3, Decl(functionReadonlyLength.ts, 9, 5)) + +f3.length = 1 +>f3.length : Symbol(Function.length, Decl(lib.es5.d.ts, --, --)) +>f3 : Symbol(f3, Decl(functionReadonlyLength.ts, 9, 5)) +>length : Symbol(Function.length, Decl(lib.es5.d.ts, --, --)) + diff --git a/tests/baselines/reference/functionReadonlyLength.types b/tests/baselines/reference/functionReadonlyLength.types new file mode 100644 index 0000000000000..dc094a3c14538 --- /dev/null +++ b/tests/baselines/reference/functionReadonlyLength.types @@ -0,0 +1,45 @@ +=== tests/cases/compiler/functionReadonlyLength.ts === +const f0 = new Function() +>f0 : Function +>new Function() : Function +>Function : FunctionConstructor + +f0.length = 1; +>f0.length = 1 : 1 +>f0.length : any +>f0 : Function +>length : any +>1 : 1 + +function f1() {}; +>f1 : () => void + +f1.length = 1; +>f1.length = 1 : 1 +>f1.length : any +>f1 : () => void +>length : any +>1 : 1 + +const f2 = function () {}; +>f2 : () => void +>function () {} : () => void + +f2.length = 1 +>f2.length = 1 : 1 +>f2.length : any +>f2 : () => void +>length : any +>1 : 1 + +const f3 = () => {} +>f3 : () => void +>() => {} : () => void + +f3.length = 1 +>f3.length = 1 : 1 +>f3.length : any +>f3 : () => void +>length : any +>1 : 1 + diff --git a/tests/cases/compiler/functionReadonlyLength.ts b/tests/cases/compiler/functionReadonlyLength.ts new file mode 100644 index 0000000000000..1872f6a58ff63 --- /dev/null +++ b/tests/cases/compiler/functionReadonlyLength.ts @@ -0,0 +1,13 @@ +// @noEmit: true + +const f0 = new Function() +f0.length = 1; + +function f1() {}; +f1.length = 1; + +const f2 = function () {}; +f2.length = 1 + +const f3 = () => {} +f3.length = 1 From 9438d2bd084317c870fa70c7425fe2804dec3549 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Wed, 18 May 2022 00:40:34 +0200 Subject: [PATCH 2/2] Avoid binding `name` property as special property on a function symbol --- src/compiler/binder.ts | 8 +++- .../reference/functionReadonlyName.errors.txt | 27 +++++++++++ .../reference/functionReadonlyName.symbols | 26 +++++++++++ .../reference/functionReadonlyName.types | 45 +++++++++++++++++++ tests/cases/compiler/functionReadonlyName.ts | 13 ++++++ 5 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/functionReadonlyName.errors.txt create mode 100644 tests/baselines/reference/functionReadonlyName.symbols create mode 100644 tests/baselines/reference/functionReadonlyName.types create mode 100644 tests/cases/compiler/functionReadonlyName.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 07ab0d0797c97..3537a7d98371b 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -3053,8 +3053,12 @@ namespace ts { if (!isInJSFile(node) && !isFunctionSym) { return; } - if (isFunctionSym && isBindableStaticNameExpression(node.left) && getDeclarationName(node.left) === "length") { - return; + if (isFunctionSym && isBindableStaticNameExpression(node.left)) { + const declarationName = getDeclarationName(node.left); + // specialcase readonly properties of functions as we don't have access to type info here + if (declarationName === "length" || declarationName === "name") { + return; + } } const rootExpr = getLeftmostAccessExpression(node.left); if (isIdentifier(rootExpr) && lookupSymbolForName(container, rootExpr.escapedText)!?.flags & SymbolFlags.Alias) { diff --git a/tests/baselines/reference/functionReadonlyName.errors.txt b/tests/baselines/reference/functionReadonlyName.errors.txt new file mode 100644 index 0000000000000..ac2482f10e9cc --- /dev/null +++ b/tests/baselines/reference/functionReadonlyName.errors.txt @@ -0,0 +1,27 @@ +tests/cases/compiler/functionReadonlyName.ts(2,4): error TS2339: Property 'name' does not exist on type 'Function'. +tests/cases/compiler/functionReadonlyName.ts(5,4): error TS2339: Property 'name' does not exist on type '() => void'. +tests/cases/compiler/functionReadonlyName.ts(8,4): error TS2339: Property 'name' does not exist on type '() => void'. +tests/cases/compiler/functionReadonlyName.ts(11,4): error TS2339: Property 'name' does not exist on type '() => void'. + + +==== tests/cases/compiler/functionReadonlyName.ts (4 errors) ==== + const f0 = new Function() + f0.name = 'foo'; + ~~~~ +!!! error TS2339: Property 'name' does not exist on type 'Function'. + + function f1() {}; + f1.name = 'foo'; + ~~~~ +!!! error TS2339: Property 'name' does not exist on type '() => void'. + + const f2 = function () {}; + f2.name = 'foo' + ~~~~ +!!! error TS2339: Property 'name' does not exist on type '() => void'. + + const f3 = () => {} + f3.name = 'foo' + ~~~~ +!!! error TS2339: Property 'name' does not exist on type '() => void'. + \ No newline at end of file diff --git a/tests/baselines/reference/functionReadonlyName.symbols b/tests/baselines/reference/functionReadonlyName.symbols new file mode 100644 index 0000000000000..3833386bb001b --- /dev/null +++ b/tests/baselines/reference/functionReadonlyName.symbols @@ -0,0 +1,26 @@ +=== tests/cases/compiler/functionReadonlyName.ts === +const f0 = new Function() +>f0 : Symbol(f0, Decl(functionReadonlyName.ts, 0, 5)) +>Function : Symbol(Function, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + +f0.name = 'foo'; +>f0 : Symbol(f0, Decl(functionReadonlyName.ts, 0, 5)) + +function f1() {}; +>f1 : Symbol(f1, Decl(functionReadonlyName.ts, 1, 16)) + +f1.name = 'foo'; +>f1 : Symbol(f1, Decl(functionReadonlyName.ts, 1, 16)) + +const f2 = function () {}; +>f2 : Symbol(f2, Decl(functionReadonlyName.ts, 6, 5)) + +f2.name = 'foo' +>f2 : Symbol(f2, Decl(functionReadonlyName.ts, 6, 5)) + +const f3 = () => {} +>f3 : Symbol(f3, Decl(functionReadonlyName.ts, 9, 5)) + +f3.name = 'foo' +>f3 : Symbol(f3, Decl(functionReadonlyName.ts, 9, 5)) + diff --git a/tests/baselines/reference/functionReadonlyName.types b/tests/baselines/reference/functionReadonlyName.types new file mode 100644 index 0000000000000..3143396030803 --- /dev/null +++ b/tests/baselines/reference/functionReadonlyName.types @@ -0,0 +1,45 @@ +=== tests/cases/compiler/functionReadonlyName.ts === +const f0 = new Function() +>f0 : Function +>new Function() : Function +>Function : FunctionConstructor + +f0.name = 'foo'; +>f0.name = 'foo' : "foo" +>f0.name : any +>f0 : Function +>name : any +>'foo' : "foo" + +function f1() {}; +>f1 : () => void + +f1.name = 'foo'; +>f1.name = 'foo' : "foo" +>f1.name : any +>f1 : () => void +>name : any +>'foo' : "foo" + +const f2 = function () {}; +>f2 : () => void +>function () {} : () => void + +f2.name = 'foo' +>f2.name = 'foo' : "foo" +>f2.name : any +>f2 : () => void +>name : any +>'foo' : "foo" + +const f3 = () => {} +>f3 : () => void +>() => {} : () => void + +f3.name = 'foo' +>f3.name = 'foo' : "foo" +>f3.name : any +>f3 : () => void +>name : any +>'foo' : "foo" + diff --git a/tests/cases/compiler/functionReadonlyName.ts b/tests/cases/compiler/functionReadonlyName.ts new file mode 100644 index 0000000000000..36fdf0749d08d --- /dev/null +++ b/tests/cases/compiler/functionReadonlyName.ts @@ -0,0 +1,13 @@ +// @noEmit: true + +const f0 = new Function() +f0.name = 'foo'; + +function f1() {}; +f1.name = 'foo'; + +const f2 = function () {}; +f2.name = 'foo' + +const f3 = () => {} +f3.name = 'foo'