Skip to content

Commit a7d6825

Browse files
authored
Fix temp vars referenced in parameter (#38130)
* Fix temp vars referenced in parameter * Update error message
1 parent 815dc90 commit a7d6825

File tree

82 files changed

+1283
-330
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+1283
-330
lines changed

src/compiler/checker.ts

Lines changed: 76 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1529,6 +1529,63 @@ namespace ts {
15291529
}
15301530
}
15311531

1532+
function useOuterVariableScopeInParameter(result: Symbol, location: Node, lastLocation: Node) {
1533+
const target = getEmitScriptTarget(compilerOptions);
1534+
const functionLocation = <FunctionLikeDeclaration>location;
1535+
if (isParameter(lastLocation) && functionLocation.body && result.valueDeclaration.pos >= functionLocation.body.pos && result.valueDeclaration.end <= functionLocation.body.end) {
1536+
// check for several cases where we introduce temporaries that require moving the name/initializer of the parameter to the body
1537+
// - static field in a class expression
1538+
// - optional chaining pre-es2020
1539+
// - nullish coalesce pre-es2020
1540+
// - spread assignment in binding pattern pre-es2017
1541+
if (target >= ScriptTarget.ES2015) {
1542+
const links = getNodeLinks(functionLocation);
1543+
if (links.declarationRequiresScopeChange === undefined) {
1544+
links.declarationRequiresScopeChange = forEach(functionLocation.parameters, requiresScopeChange) || false;
1545+
}
1546+
return !links.declarationRequiresScopeChange;
1547+
}
1548+
}
1549+
return false;
1550+
1551+
function requiresScopeChange(node: ParameterDeclaration): boolean {
1552+
return requiresScopeChangeWorker(node.name)
1553+
|| !!node.initializer && requiresScopeChangeWorker(node.initializer);
1554+
}
1555+
1556+
function requiresScopeChangeWorker(node: Node): boolean {
1557+
switch (node.kind) {
1558+
case SyntaxKind.ArrowFunction:
1559+
case SyntaxKind.FunctionExpression:
1560+
case SyntaxKind.FunctionDeclaration:
1561+
case SyntaxKind.Constructor:
1562+
// do not descend into these
1563+
return false;
1564+
case SyntaxKind.MethodDeclaration:
1565+
case SyntaxKind.GetAccessor:
1566+
case SyntaxKind.SetAccessor:
1567+
case SyntaxKind.PropertyAssignment:
1568+
return requiresScopeChangeWorker((node as MethodDeclaration | AccessorDeclaration | PropertyAssignment).name);
1569+
case SyntaxKind.PropertyDeclaration:
1570+
// static properties in classes introduce temporary variables
1571+
if (hasStaticModifier(node)) {
1572+
return target < ScriptTarget.ESNext || !compilerOptions.useDefineForClassFields;
1573+
}
1574+
return requiresScopeChangeWorker((node as PropertyDeclaration).name);
1575+
default:
1576+
// null coalesce and optional chain pre-es2020 produce temporary variables
1577+
if (isNullishCoalesce(node) || isOptionalChain(node)) {
1578+
return target < ScriptTarget.ES2020;
1579+
}
1580+
if (isBindingElement(node) && node.dotDotDotToken && isObjectBindingPattern(node.parent)) {
1581+
return target < ScriptTarget.ES2017;
1582+
}
1583+
if (isTypeNode(node)) return false;
1584+
return forEachChild(node, requiresScopeChangeWorker) || false;
1585+
}
1586+
}
1587+
}
1588+
15321589
/**
15331590
* Resolve a given name for a given meaning at a given location. An error is reported if the name was not found and
15341591
* the nameNotFoundMessage argument is not undefined. Returns the resolved symbol, or undefined if no symbol with
@@ -1563,7 +1620,7 @@ namespace ts {
15631620
let lastLocation: Node | undefined;
15641621
let lastSelfReferenceLocation: Node | undefined;
15651622
let propertyWithInvalidInitializer: Node | undefined;
1566-
let associatedDeclarationForContainingInitializer: ParameterDeclaration | BindingElement | undefined;
1623+
let associatedDeclarationForContainingInitializerOrBindingName: ParameterDeclaration | BindingElement | undefined;
15671624
let withinDeferredContext = false;
15681625
const errorLocation = location;
15691626
let grandparent: Node;
@@ -1592,9 +1649,7 @@ namespace ts {
15921649
}
15931650
if (meaning & result.flags & SymbolFlags.Variable) {
15941651
// expression inside parameter will lookup as normal variable scope when targeting es2015+
1595-
const functionLocation = <FunctionLikeDeclaration>location;
1596-
if (compilerOptions.target && compilerOptions.target >= ScriptTarget.ES2015 && isParameter(lastLocation) &&
1597-
functionLocation.body && result.valueDeclaration.pos >= functionLocation.body.pos && result.valueDeclaration.end <= functionLocation.body.end) {
1652+
if (useOuterVariableScopeInParameter(result, location, lastLocation)) {
15981653
useResult = false;
15991654
}
16001655
else if (result.flags & SymbolFlags.FunctionScopedVariable) {
@@ -1822,15 +1877,21 @@ namespace ts {
18221877
location = getJSDocHost(location);
18231878
break;
18241879
case SyntaxKind.Parameter:
1825-
if (lastLocation && lastLocation === (location as ParameterDeclaration).initializer) {
1826-
associatedDeclarationForContainingInitializer = location as ParameterDeclaration;
1880+
if (lastLocation && (
1881+
lastLocation === (location as ParameterDeclaration).initializer ||
1882+
lastLocation === (location as ParameterDeclaration).name && isBindingPattern(lastLocation))) {
1883+
if (!associatedDeclarationForContainingInitializerOrBindingName) {
1884+
associatedDeclarationForContainingInitializerOrBindingName = location as ParameterDeclaration;
1885+
}
18271886
}
18281887
break;
18291888
case SyntaxKind.BindingElement:
1830-
if (lastLocation && lastLocation === (location as BindingElement).initializer) {
1889+
if (lastLocation && (
1890+
lastLocation === (location as BindingElement).initializer ||
1891+
lastLocation === (location as BindingElement).name && isBindingPattern(lastLocation))) {
18311892
const root = getRootDeclaration(location);
18321893
if (root.kind === SyntaxKind.Parameter) {
1833-
associatedDeclarationForContainingInitializer = location as BindingElement;
1894+
associatedDeclarationForContainingInitializerOrBindingName = location as BindingElement;
18341895
}
18351896
}
18361897
break;
@@ -1941,17 +2002,17 @@ namespace ts {
19412002
}
19422003
}
19432004

1944-
// If we're in a parameter initializer, we can't reference the values of the parameter whose initializer we're within or parameters to the right
1945-
if (result && associatedDeclarationForContainingInitializer && !withinDeferredContext && (meaning & SymbolFlags.Value) === SymbolFlags.Value) {
2005+
// If we're in a parameter initializer or binding name, we can't reference the values of the parameter whose initializer we're within or parameters to the right
2006+
if (result && associatedDeclarationForContainingInitializerOrBindingName && !withinDeferredContext && (meaning & SymbolFlags.Value) === SymbolFlags.Value) {
19462007
const candidate = getMergedSymbol(getLateBoundSymbol(result));
1947-
const root = (getRootDeclaration(associatedDeclarationForContainingInitializer) as ParameterDeclaration);
2008+
const root = (getRootDeclaration(associatedDeclarationForContainingInitializerOrBindingName) as ParameterDeclaration);
19482009
// A parameter initializer or binding pattern initializer within a parameter cannot refer to itself
1949-
if (candidate === getSymbolOfNode(associatedDeclarationForContainingInitializer)) {
1950-
error(errorLocation, Diagnostics.Parameter_0_cannot_be_referenced_in_its_initializer, declarationNameToString(associatedDeclarationForContainingInitializer.name));
2010+
if (candidate === getSymbolOfNode(associatedDeclarationForContainingInitializerOrBindingName)) {
2011+
error(errorLocation, Diagnostics.Parameter_0_cannot_reference_itself, declarationNameToString(associatedDeclarationForContainingInitializerOrBindingName.name));
19512012
}
19522013
// And it cannot refer to any declarations which come after it
1953-
else if (candidate.valueDeclaration && candidate.valueDeclaration.pos > associatedDeclarationForContainingInitializer.pos && root.parent.locals && lookup(root.parent.locals, candidate.escapedName, meaning) === candidate) {
1954-
error(errorLocation, Diagnostics.Initializer_of_parameter_0_cannot_reference_identifier_1_declared_after_it, declarationNameToString(associatedDeclarationForContainingInitializer.name), declarationNameToString(<Identifier>errorLocation));
2014+
else if (candidate.valueDeclaration && candidate.valueDeclaration.pos > associatedDeclarationForContainingInitializerOrBindingName.pos && root.parent.locals && lookup(root.parent.locals, candidate.escapedName, meaning) === candidate) {
2015+
error(errorLocation, Diagnostics.Parameter_0_cannot_reference_identifier_1_declared_after_it, declarationNameToString(associatedDeclarationForContainingInitializerOrBindingName.name), declarationNameToString(<Identifier>errorLocation));
19552016
}
19562017
}
19572018
if (result && errorLocation && meaning & SymbolFlags.Value && result.flags & SymbolFlags.Alias) {

src/compiler/diagnosticMessages.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1477,11 +1477,11 @@
14771477
"category": "Error",
14781478
"code": 2371
14791479
},
1480-
"Parameter '{0}' cannot be referenced in its initializer.": {
1480+
"Parameter '{0}' cannot reference itself.": {
14811481
"category": "Error",
14821482
"code": 2372
14831483
},
1484-
"Initializer of parameter '{0}' cannot reference identifier '{1}' declared after it.": {
1484+
"Parameter '{0}' cannot reference identifier '{1}' declared after it.": {
14851485
"category": "Error",
14861486
"code": 2373
14871487
},

src/compiler/factory.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ namespace ts {
44
enableEmitNotification: noop,
55
enableSubstitution: noop,
66
endLexicalEnvironment: returnUndefined,
7-
getCompilerOptions: notImplemented,
7+
getCompilerOptions: () => ({}),
88
getEmitHost: notImplemented,
99
getEmitResolver: notImplemented,
10+
setLexicalEnvironmentFlags: noop,
11+
getLexicalEnvironmentFlags: () => 0,
1012
hoistFunctionDeclaration: noop,
1113
hoistVariableDeclaration: noop,
14+
addInitializationStatement: noop,
1215
isEmitNotificationEnabled: notImplemented,
1316
isSubstitutionEnabled: notImplemented,
1417
onEmitNode: noop,
@@ -852,13 +855,13 @@ namespace ts {
852855
* This function needs to be called whenever we transform the statement
853856
* list of a source file, namespace, or function-like body.
854857
*/
855-
export function addCustomPrologue(target: Statement[], source: readonly Statement[], statementOffset: number, visitor?: (node: Node) => VisitResult<Node>): number;
856-
export function addCustomPrologue(target: Statement[], source: readonly Statement[], statementOffset: number | undefined, visitor?: (node: Node) => VisitResult<Node>): number | undefined;
857-
export function addCustomPrologue(target: Statement[], source: readonly Statement[], statementOffset: number | undefined, visitor?: (node: Node) => VisitResult<Node>): number | undefined {
858+
export function addCustomPrologue(target: Statement[], source: readonly Statement[], statementOffset: number, visitor?: (node: Node) => VisitResult<Node>, filter?: (node: Node) => boolean): number;
859+
export function addCustomPrologue(target: Statement[], source: readonly Statement[], statementOffset: number | undefined, visitor?: (node: Node) => VisitResult<Node>, filter?: (node: Node) => boolean): number | undefined;
860+
export function addCustomPrologue(target: Statement[], source: readonly Statement[], statementOffset: number | undefined, visitor?: (node: Node) => VisitResult<Node>, filter: (node: Node) => boolean = returnTrue): number | undefined {
858861
const numStatements = source.length;
859862
while (statementOffset !== undefined && statementOffset < numStatements) {
860863
const statement = source[statementOffset];
861-
if (getEmitFlags(statement) & EmitFlags.CustomPrologue) {
864+
if (getEmitFlags(statement) & EmitFlags.CustomPrologue && filter(statement)) {
862865
append(target, visitor ? visitNode(statement, visitor, isStatement) : statement);
863866
}
864867
else {

0 commit comments

Comments
 (0)