Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Accept better baseline from reduced expression check, defer/reuse pro…
…p expression check more maximally
  • Loading branch information
weswigham committed Apr 30, 2024
commit e466071585fbf5d6b4d1fb1d9f660aa59b7bf23c
36 changes: 23 additions & 13 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29139,7 +29139,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
});
}

function markLinkedReferences(location: Node) {
/**
* @param location The location to mark js import refernces for
* @param propSymbol The optional symbol of the property we're looking up - this is used for property accesses when `const enum`s do not count as references (no `isolatedModules`, no `preserveConstEnums` + export). It will be calculated if not provided.
* @param parentType The optional type of the parent of the LHS of the property access - this will be recalculated if not provided.
*/
function markLinkedReferences(location: Node, propSymbol?: Symbol, parentType?: Type) {
if (!canCollectSymbolAliasAccessabilityData) {
return;
}
Expand All @@ -29161,12 +29166,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
topProp = topProp.parent;
}
const left = isPropertyAccessExpression(location) ? location.expression : location.left;
if (isThisIdentifier(left)) {
if (isThisIdentifier(left) || !isIdentifier(left)) {
return;
}
const right = isPropertyAccessExpression(location) ? location.name : location.right;
const assignmentKind = getAssignmentTargetKind(location);
const leftType = checkExpression(left); // cannot use checkExpressionCached - causes stack overflow in control flow due to resetting the control flow state
const leftType = parentType || checkExpression(left); // cannot use checkExpressionCached - causes stack overflow in control flow due to resetting the control flow state
const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(location) ? getWidenedType(leftType) : leftType);
const parentSymbol = getNodeLinks(left).resolvedSymbol;
if (!parentSymbol || parentSymbol === unknownSymbol) {
Expand All @@ -29176,24 +29181,29 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
markAliasReferenced(parentSymbol, location);
return;
}
const lexicallyScopedSymbol = isPrivateIdentifier(right) && lookupSymbolForPrivateIdentifierDeclaration(right.escapedText, right);
const prop = isPrivateIdentifier(right) ? lexicallyScopedSymbol && getPrivateIdentifierPropertyOfType(apparentType, lexicallyScopedSymbol) : getPropertyOfType(apparentType, right.escapedText);
const node = location;
// In `Foo.Bar.Baz`, 'Foo' is not referenced if 'Bar' is a const enum or a module containing only const enums.
// `Foo` is also not referenced in `enum FooCopy { Bar = Foo.Bar }`, because the enum member value gets inlined
// here even if `Foo` is not a const enum.
//
// The exceptions are:
// 1. if 'isolatedModules' is enabled, because the const enum value will not be inlined, and
// 2. if 'preserveConstEnums' is enabled and the expression is itself an export, e.g. `export = Foo.Bar.Baz`.
//
// The property lookup is deferred as much as possible, in as many situations as possible, to avoid alias marking
// pulling on types/symbols it doesn't strictly need to.
if (getIsolatedModules(compilerOptions) || (shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(location))) {
markAliasReferenced(parentSymbol, location);
return;
}
let prop = propSymbol;
if (!prop) {
const lexicallyScopedSymbol = isPrivateIdentifier(right) && lookupSymbolForPrivateIdentifierDeclaration(right.escapedText, right);
prop = isPrivateIdentifier(right) ? lexicallyScopedSymbol && getPrivateIdentifierPropertyOfType(apparentType, lexicallyScopedSymbol) || undefined : getPropertyOfType(apparentType, right.escapedText);
}
if (
isIdentifier(left) && parentSymbol && (
getIsolatedModules(compilerOptions) ||
!(prop && (isConstEnumOrConstEnumOnlyModule(prop) || prop.flags & SymbolFlags.EnumMember && node.parent.kind === SyntaxKind.EnumMember)) ||
shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(node)
)
!(prop && (isConstEnumOrConstEnumOnlyModule(prop) || prop.flags & SymbolFlags.EnumMember && location.parent.kind === SyntaxKind.EnumMember))
) {
markAliasReferenced(parentSymbol, node);
markAliasReferenced(parentSymbol, location);
}
return;
}
Expand Down Expand Up @@ -33297,7 +33307,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
prop = getPropertyOfType(apparentType, right.escapedText, /*skipObjectFunctionPropertyAugment*/ isConstEnumObjectType(apparentType), /*includeTypeOnlyMembers*/ node.kind === SyntaxKind.QualifiedName);
}
markLinkedReferences(node);
markLinkedReferences(node, prop, leftType);

let propType: Type;
if (!prop) {
Expand Down
5 changes: 1 addition & 4 deletions tests/baselines/reference/moduleImport.errors.txt
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
moduleImport.ts(2,17): error TS2339: Property 'Y' does not exist on type 'typeof X'.
moduleImport.ts(2,17): error TS2694: Namespace 'X' has no exported member 'Y'.


==== moduleImport.ts (2 errors) ====
==== moduleImport.ts (1 errors) ====
module A.B.C {
import XYZ = X.Y.Z;
~
!!! error TS2339: Property 'Y' does not exist on type 'typeof X'.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We drop this (duplicate-ish) error because now, rather than relying on checkExpressionCached to paint the reference recursively for the import = statement, we directly use markIdentifierAliasReferenced on the leftmost identifier in the entity name. Which should be faster, and, as this test shows, shouldn't cause us to drop any real errors, since any missing members get reported when we actually resolve the name (with a more sensible error, even).

~
!!! error TS2694: Namespace 'X' has no exported member 'Y'.
export function ping(x: number) {
if (x>0) XYZ.pong (x-1);
Expand Down