@@ -23590,6 +23590,32 @@ namespace ts {
2359023590 return type;
2359123591 }
2359223592
23593+ function narrowTypeByPrivateIdentifierInInExpression(type: Type, expr: PrivateIdentifierInInExpression, assumeTrue: boolean): Type {
23594+ const target = getReferenceCandidate(expr.expression);
23595+ if (!isMatchingReference(reference, target)) {
23596+ return type;
23597+ }
23598+
23599+ const privateId = expr.name;
23600+ const symbol = lookupSymbolForPrivateIdentifierDeclaration(privateId.escapedText, privateId);
23601+ if (symbol === undefined) {
23602+ return type;
23603+ }
23604+ const classSymbol = symbol.parent!;
23605+ const classType = getTypeOfSymbol(classSymbol) as InterfaceType;
23606+ const classDecl = symbol.valueDeclaration;
23607+ Debug.assert(classDecl, "should always have a declaration");
23608+ let targetType: Type;
23609+ if (hasStaticModifier(classDecl)) {
23610+ targetType = classType;
23611+ }
23612+ else {
23613+ const classInstanceType = getDeclaredTypeOfSymbol(classSymbol);
23614+ targetType = classInstanceType;
23615+ }
23616+ return getNarrowedType(type, targetType, assumeTrue, isTypeDerivedFrom);
23617+ }
23618+
2359323619 function narrowTypeByOptionalChainContainment(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type {
2359423620 // We are in a branch of obj?.foo === value (or any one of the other equality operators). We narrow obj as follows:
2359523621 // When operator is === and type of value excludes undefined, null and undefined is removed from type of obj in true branch.
@@ -24022,6 +24048,8 @@ namespace ts {
2402224048 return narrowType(type, (expr as ParenthesizedExpression | NonNullExpression).expression, assumeTrue);
2402324049 case SyntaxKind.BinaryExpression:
2402424050 return narrowTypeByBinaryExpression(type, expr as BinaryExpression, assumeTrue);
24051+ case SyntaxKind.PrivateIdentifierInInExpression:
24052+ return narrowTypeByPrivateIdentifierInInExpression(type, expr as PrivateIdentifierInInExpression, assumeTrue);
2402524053 case SyntaxKind.PrefixUnaryExpression:
2402624054 if ((expr as PrefixUnaryExpression).operator === SyntaxKind.ExclamationToken) {
2402724055 return narrowType(type, (expr as PrefixUnaryExpression).operand, !assumeTrue);
@@ -27726,6 +27754,7 @@ namespace ts {
2772627754 }
2772727755
2772827756 function getSuggestedSymbolForNonexistentProperty(name: Identifier | PrivateIdentifier | string, containingType: Type): Symbol | undefined {
27757+ const originalName = name;
2772927758 let props = getPropertiesOfType(containingType);
2773027759 if (typeof name !== "string") {
2773127760 const parent = name.parent;
@@ -27734,7 +27763,23 @@ namespace ts {
2773427763 }
2773527764 name = idText(name);
2773627765 }
27737- return getSpellingSuggestionForName(name, props, SymbolFlags.Value);
27766+ const suggestion = getSpellingSuggestionForName(name, props, SymbolFlags.Value);
27767+ if (suggestion) {
27768+ return suggestion;
27769+ }
27770+ // If we have `#typo in expr` then we can still look up potential privateIdentifiers from the surrounding classes
27771+ if (typeof originalName !== "string" && isPrivateIdentifierInInExpression(originalName.parent)) {
27772+ const privateIdentifiers: Symbol[] = [];
27773+ forEachEnclosingClass(originalName, (klass: ClassLikeDeclaration) => {
27774+ forEach(klass.members, member => {
27775+ if (isPrivateIdentifierClassElementDeclaration(member)) {
27776+ privateIdentifiers.push(member.symbol);
27777+ }
27778+ });
27779+ });
27780+ return getSpellingSuggestionForName(name, privateIdentifiers, SymbolFlags.Value);
27781+ }
27782+ return undefined;
2773827783 }
2773927784
2774027785 function getSuggestedSymbolForNonexistentJSXAttribute(name: Identifier | PrivateIdentifier | string, containingType: Type): Symbol | undefined {
@@ -31494,6 +31539,11 @@ namespace ts {
3149431539 isTypeAssignableToKind(leftType, TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping | TypeFlags.TypeParameter))) {
3149531540 error(left, Diagnostics.The_left_hand_side_of_an_in_expression_must_be_of_type_any_string_number_or_symbol);
3149631541 }
31542+ checkInExpressionRHS(right, rightType);
31543+ return booleanType;
31544+ }
31545+
31546+ function checkInExpressionRHS(right: Expression, rightType: Type) {
3149731547 const rightTypeConstraint = getConstraintOfType(rightType);
3149831548 if (!allTypesAssignableToKind(rightType, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive) ||
3149931549 rightTypeConstraint && (
@@ -31503,7 +31553,6 @@ namespace ts {
3150331553 ) {
3150431554 error(right, Diagnostics.The_right_hand_side_of_an_in_expression_must_not_be_a_primitive);
3150531555 }
31506- return booleanType;
3150731556 }
3150831557
3150931558 function checkObjectLiteralAssignment(node: ObjectLiteralExpression, sourceType: Type, rightIsThis?: boolean): Type {
@@ -31740,6 +31789,40 @@ namespace ts {
3174031789 return (target.flags & TypeFlags.Nullable) !== 0 || isTypeComparableTo(source, target);
3174131790 }
3174231791
31792+ function checkPrivateIdentifierInInExpression(node: PrivateIdentifierInInExpression, checkMode?: CheckMode) {
31793+ const privateId = node.name;
31794+ const exp = node.expression;
31795+ let rightType = checkExpression(exp, checkMode);
31796+
31797+ const lexicallyScopedSymbol = lookupSymbolForPrivateIdentifierDeclaration(privateId.escapedText, privateId);
31798+ if (lexicallyScopedSymbol === undefined) {
31799+ if (!getContainingClass(node)) {
31800+ error(privateId, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies);
31801+ }
31802+ else {
31803+ const suggestion = getSuggestedSymbolForNonexistentProperty(privateId, rightType);
31804+ if (suggestion) {
31805+ const suggestedName = symbolName(suggestion);
31806+ error(privateId, Diagnostics.Cannot_find_name_0_Did_you_mean_1, diagnosticName(privateId), suggestedName);
31807+ }
31808+ else {
31809+ error(privateId, Diagnostics.Cannot_find_name_0, diagnosticName(privateId));
31810+ }
31811+ }
31812+ return anyType;
31813+ }
31814+
31815+ markPropertyAsReferenced(lexicallyScopedSymbol, /* nodeForCheckWriteOnly: */ undefined, /* isThisAccess: */ false);
31816+ getNodeLinks(node).resolvedSymbol = lexicallyScopedSymbol;
31817+
31818+ if (rightType === silentNeverType) {
31819+ return silentNeverType;
31820+ }
31821+ rightType = checkNonNullType(rightType, exp);
31822+ checkInExpressionRHS(exp, rightType);
31823+ return booleanType;
31824+ }
31825+
3174331826 function createCheckBinaryExpression() {
3174431827 interface WorkArea {
3174531828 readonly checkMode: CheckMode | undefined;
@@ -32921,6 +33004,8 @@ namespace ts {
3292133004 return checkPostfixUnaryExpression(node as PostfixUnaryExpression);
3292233005 case SyntaxKind.BinaryExpression:
3292333006 return checkBinaryExpression(node as BinaryExpression, checkMode);
33007+ case SyntaxKind.PrivateIdentifierInInExpression:
33008+ return checkPrivateIdentifierInInExpression(node as PrivateIdentifierInInExpression, checkMode);
3292433009 case SyntaxKind.ConditionalExpression:
3292533010 return checkConditionalExpression(node as ConditionalExpression, checkMode);
3292633011 case SyntaxKind.SpreadElement:
@@ -39416,6 +39501,15 @@ namespace ts {
3941639501 return resolveEntityName(name as Identifier, /*meaning*/ SymbolFlags.FunctionScopedVariable);
3941739502 }
3941839503
39504+ if (isPrivateIdentifier(name) && isPrivateIdentifierInInExpression(name.parent)) {
39505+ const links = getNodeLinks(name.parent);
39506+ if (links.resolvedSymbol) {
39507+ return links.resolvedSymbol;
39508+ }
39509+ checkPrivateIdentifierInInExpression(name.parent);
39510+ return links.resolvedSymbol;
39511+ }
39512+
3941939513 return undefined;
3942039514 }
3942139515
0 commit comments