diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9a71a6a0277e8..90b3a844a8c5d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -722,6 +722,7 @@ namespace ts { const templateLiteralTypes = new Map(); const stringMappingTypes = new Map(); const substitutionTypes = new Map(); + const propertyWitnessRecordTypes = new Map(); const evolvingArrayTypes: EvolvingArrayType[] = []; const undefinedProperties: SymbolTable = new Map(); @@ -915,6 +916,7 @@ namespace ts { let deferredGlobalExtractSymbol: Symbol; let deferredGlobalOmitSymbol: Symbol; let deferredGlobalBigIntType: ObjectType; + let deferredGlobalRecordSymbol: Symbol; const allPotentiallyUnusedIdentifiers = new Map(); // key is file name @@ -12658,6 +12660,10 @@ namespace ts { return deferredGlobalBigIntType || (deferredGlobalBigIntType = getGlobalType("BigInt" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; } + function getGlobalRecordSymbol(): Symbol { + return deferredGlobalRecordSymbol || (deferredGlobalRecordSymbol = getGlobalSymbol("Record" as __String, SymbolFlags.TypeAlias, Diagnostics.Cannot_find_global_type_0)!); + } + /** * Instantiates a global type that is generic with some element type, and returns that instantiation. */ @@ -22169,7 +22175,20 @@ namespace ts { || isThisTypeParameter(type) || type.flags & TypeFlags.Intersection && every((type as IntersectionType).types, t => t.symbol !== globalThisSymbol)) { const propName = escapeLeadingUnderscores(literal.text); - return filterType(type, t => isTypePresencePossible(t, propName, assumeTrue)); + const narrowed = filterType(type, t => isTypePresencePossible(t, propName, assumeTrue)); + if (assumeTrue && (narrowed.flags & TypeFlags.Never)) { + const recordTypeAlias = getGlobalRecordSymbol(); + if (!recordTypeAlias) { + return errorType; + } + let record = propertyWitnessRecordTypes.get(literal.text); + if (!record) { + record = getTypeAliasInstantiation(recordTypeAlias, [getLiteralType(literal.text), unknownType]); + propertyWitnessRecordTypes.set(literal.text, record); + } + return getIntersectionType([type, record]); + } + return narrowed; } return type; } diff --git a/tests/baselines/reference/inKeywordTypeguard.errors.txt b/tests/baselines/reference/inKeywordTypeguard.errors.txt index bb7d823d99d43..dc58b2c2fcbb7 100644 --- a/tests/baselines/reference/inKeywordTypeguard.errors.txt +++ b/tests/baselines/reference/inKeywordTypeguard.errors.txt @@ -5,8 +5,10 @@ tests/cases/compiler/inKeywordTypeguard.ts(16,11): error TS2339: Property 'a' do tests/cases/compiler/inKeywordTypeguard.ts(27,11): error TS2339: Property 'b' does not exist on type 'AWithOptionalProp | BWithOptionalProp'. Property 'b' does not exist on type 'AWithOptionalProp'. tests/cases/compiler/inKeywordTypeguard.ts(42,11): error TS2339: Property 'b' does not exist on type 'AWithMethod'. -tests/cases/compiler/inKeywordTypeguard.ts(49,11): error TS2339: Property 'a' does not exist on type 'never'. -tests/cases/compiler/inKeywordTypeguard.ts(50,11): error TS2339: Property 'b' does not exist on type 'never'. +tests/cases/compiler/inKeywordTypeguard.ts(49,11): error TS2339: Property 'a' does not exist on type '(AWithMethod & Record<"c", unknown>) | (BWithMethod & Record<"c", unknown>)'. + Property 'a' does not exist on type 'BWithMethod & Record<"c", unknown>'. +tests/cases/compiler/inKeywordTypeguard.ts(50,11): error TS2339: Property 'b' does not exist on type '(AWithMethod & Record<"c", unknown>) | (BWithMethod & Record<"c", unknown>)'. + Property 'b' does not exist on type 'AWithMethod & Record<"c", unknown>'. tests/cases/compiler/inKeywordTypeguard.ts(52,11): error TS2339: Property 'a' does not exist on type 'AWithMethod | BWithMethod'. Property 'a' does not exist on type 'BWithMethod'. tests/cases/compiler/inKeywordTypeguard.ts(53,11): error TS2339: Property 'b' does not exist on type 'AWithMethod | BWithMethod'. @@ -85,10 +87,12 @@ tests/cases/compiler/inKeywordTypeguard.ts(94,26): error TS2339: Property 'a' do if ("c" in x) { x.a(); ~ -!!! error TS2339: Property 'a' does not exist on type 'never'. +!!! error TS2339: Property 'a' does not exist on type '(AWithMethod & Record<"c", unknown>) | (BWithMethod & Record<"c", unknown>)'. +!!! error TS2339: Property 'a' does not exist on type 'BWithMethod & Record<"c", unknown>'. x.b(); ~ -!!! error TS2339: Property 'b' does not exist on type 'never'. +!!! error TS2339: Property 'b' does not exist on type '(AWithMethod & Record<"c", unknown>) | (BWithMethod & Record<"c", unknown>)'. +!!! error TS2339: Property 'b' does not exist on type 'AWithMethod & Record<"c", unknown>'. } else { x.a(); ~ diff --git a/tests/baselines/reference/inKeywordTypeguard.types b/tests/baselines/reference/inKeywordTypeguard.types index c5b735a51d9c1..a99380c413c2d 100644 --- a/tests/baselines/reference/inKeywordTypeguard.types +++ b/tests/baselines/reference/inKeywordTypeguard.types @@ -146,13 +146,13 @@ function negativeTestClassesWithMemberMissingInBothClasses(x: AWithMethod | BWit x.a(); >x.a() : any >x.a : any ->x : never +>x : (AWithMethod & Record<"c", unknown>) | (BWithMethod & Record<"c", unknown>) >a : any x.b(); >x.b() : any >x.b : any ->x : never +>x : (AWithMethod & Record<"c", unknown>) | (BWithMethod & Record<"c", unknown>) >b : any } else {