Skip to content

Commit ecf50e8

Browse files
authoredSep 29, 2022
Properly compute SymbolFlags.Optional for intersected properties (#50958)
* `in` proves property presence only if property can't be undefined * Accept new baselines * Add tests * Accept new baselines * Properly compute SymbolFlags.Optional for intersected properties * Accept new baselines * Check optionality only for property-like declarations * Add more tests
1 parent d1586de commit ecf50e8

11 files changed

+1004
-42
lines changed
 

‎src/compiler/checker.ts

+11-8
Original file line numberDiff line numberDiff line change
@@ -12566,7 +12566,7 @@ namespace ts {
1256612566
let indexTypes: Type[] | undefined;
1256712567
const isUnion = containingType.flags & TypeFlags.Union;
1256812568
// Flags we want to propagate to the result if they exist in all source symbols
12569-
let optionalFlag = isUnion ? SymbolFlags.None : SymbolFlags.Optional;
12569+
let optionalFlag: SymbolFlags | undefined;
1257012570
let syntheticFlag = CheckFlags.SyntheticMethod;
1257112571
let checkFlags = isUnion ? 0 : CheckFlags.Readonly;
1257212572
let mergedInstantiations = false;
@@ -12576,11 +12576,14 @@ namespace ts {
1257612576
const prop = getPropertyOfType(type, name, skipObjectFunctionPropertyAugment);
1257712577
const modifiers = prop ? getDeclarationModifierFlagsFromSymbol(prop) : 0;
1257812578
if (prop) {
12579-
if (isUnion) {
12580-
optionalFlag |= (prop.flags & SymbolFlags.Optional);
12581-
}
12582-
else {
12583-
optionalFlag &= prop.flags;
12579+
if (prop.flags & SymbolFlags.ClassMember) {
12580+
optionalFlag ??= isUnion ? SymbolFlags.None : SymbolFlags.Optional;
12581+
if (isUnion) {
12582+
optionalFlag |= (prop.flags & SymbolFlags.Optional);
12583+
}
12584+
else {
12585+
optionalFlag &= prop.flags;
12586+
}
1258412587
}
1258512588
if (!singleProp) {
1258612589
singleProp = prop;
@@ -12699,7 +12702,7 @@ namespace ts {
1269912702
propTypes.push(type);
1270012703
}
1270112704
addRange(propTypes, indexTypes);
12702-
const result = createSymbol(SymbolFlags.Property | optionalFlag, name, syntheticFlag | checkFlags);
12705+
const result = createSymbol(SymbolFlags.Property | (optionalFlag ?? 0), name, syntheticFlag | checkFlags);
1270312706
result.containingType = containingType;
1270412707
if (!hasNonUniformValueDeclaration && firstValueDeclaration) {
1270512708
result.valueDeclaration = firstValueDeclaration;
@@ -20455,7 +20458,7 @@ namespace ts {
2045520458
return Ternary.False;
2045620459
}
2045720460
// When checking for comparability, be more lenient with optional properties.
20458-
if (!skipOptional && sourceProp.flags & SymbolFlags.Optional && !(targetProp.flags & SymbolFlags.Optional)) {
20461+
if (!skipOptional && sourceProp.flags & SymbolFlags.Optional && targetProp.flags & SymbolFlags.ClassMember && !(targetProp.flags & SymbolFlags.Optional)) {
2045920462
// TypeScript 1.0 spec (April 2014): 3.8.3
2046020463
// S is a subtype of a type T, and T is a supertype of S if ...
2046120464
// S' and T are object types and, for each member M in T..

‎tests/baselines/reference/inKeywordTypeguard(strict=false).errors.txt

+64-4
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,13 @@ tests/cases/compiler/inKeywordTypeguard.ts(74,32): error TS2339: Property 'a' do
2121
tests/cases/compiler/inKeywordTypeguard.ts(82,39): error TS2339: Property 'b' does not exist on type 'A'.
2222
tests/cases/compiler/inKeywordTypeguard.ts(84,39): error TS2339: Property 'a' does not exist on type 'B'.
2323
tests/cases/compiler/inKeywordTypeguard.ts(94,26): error TS2339: Property 'a' does not exist on type 'never'.
24-
tests/cases/compiler/inKeywordTypeguard.ts(150,16): error TS2339: Property 'ontouchstart' does not exist on type 'never'.
2524
tests/cases/compiler/inKeywordTypeguard.ts(155,16): error TS2322: Type 'unknown' is not assignable to type 'object'.
2625
tests/cases/compiler/inKeywordTypeguard.ts(158,21): error TS2322: Type 'unknown' is not assignable to type 'object'.
2726
tests/cases/compiler/inKeywordTypeguard.ts(183,16): error TS2322: Type 'T' is not assignable to type 'object'.
2827
tests/cases/compiler/inKeywordTypeguard.ts(186,21): error TS2322: Type 'T' is not assignable to type 'object'.
2928

3029

31-
==== tests/cases/compiler/inKeywordTypeguard.ts (22 errors) ====
30+
==== tests/cases/compiler/inKeywordTypeguard.ts (21 errors) ====
3231
class A { a: string; }
3332
class B { b: string; }
3433

@@ -219,8 +218,6 @@ tests/cases/compiler/inKeywordTypeguard.ts(186,21): error TS2322: Type 'T' is no
219218
window.ontouchstart
220219
} else {
221220
window.ontouchstart
222-
~~~~~~~~~~~~
223-
!!! error TS2339: Property 'ontouchstart' does not exist on type 'never'.
224221
}
225222
}
226223

@@ -353,11 +350,74 @@ tests/cases/compiler/inKeywordTypeguard.ts(186,21): error TS2322: Type 'T' is no
353350
}
354351
}
355352

353+
function f10(x: { a: unknown }) {
354+
if ("a" in x) {
355+
x;
356+
}
357+
else {
358+
x;
359+
}
360+
}
361+
362+
function f11(x: { a: any }) {
363+
if ("a" in x) {
364+
x;
365+
}
366+
else {
367+
x;
368+
}
369+
}
370+
371+
function f12(x: { a: string }) {
372+
if ("a" in x) {
373+
x;
374+
}
375+
else {
376+
x;
377+
}
378+
}
379+
380+
function f13(x: { a?: string }) {
381+
if ("a" in x) {
382+
x;
383+
}
384+
else {
385+
x;
386+
}
387+
}
388+
389+
function f14(x: { a: string | undefined }) {
390+
if ("a" in x) {
391+
x;
392+
}
393+
else {
394+
x;
395+
}
396+
}
397+
398+
function f15(x: { a?: string | undefined }) {
399+
if ("a" in x) {
400+
x;
401+
}
402+
else {
403+
x;
404+
}
405+
}
406+
407+
function f16(x: typeof globalThis, y: Window & typeof globalThis) {
408+
x = y;
409+
}
410+
356411
// Repro from #50639
357412

358413
function foo<A>(value: A) {
359414
if (typeof value === "object" && value !== null && "prop" in value) {
360415
value; // A & object & Record<"prop", unknown>
361416
}
362417
}
418+
419+
// Repro from #50954
420+
421+
const checkIsTouchDevice = () =>
422+
"ontouchstart" in window || "msMaxTouchPoints" in window.navigator;
363423

‎tests/baselines/reference/inKeywordTypeguard(strict=false).js

+116
Original file line numberDiff line numberDiff line change
@@ -271,13 +271,76 @@ function f9(x: object) {
271271
}
272272
}
273273

274+
function f10(x: { a: unknown }) {
275+
if ("a" in x) {
276+
x;
277+
}
278+
else {
279+
x;
280+
}
281+
}
282+
283+
function f11(x: { a: any }) {
284+
if ("a" in x) {
285+
x;
286+
}
287+
else {
288+
x;
289+
}
290+
}
291+
292+
function f12(x: { a: string }) {
293+
if ("a" in x) {
294+
x;
295+
}
296+
else {
297+
x;
298+
}
299+
}
300+
301+
function f13(x: { a?: string }) {
302+
if ("a" in x) {
303+
x;
304+
}
305+
else {
306+
x;
307+
}
308+
}
309+
310+
function f14(x: { a: string | undefined }) {
311+
if ("a" in x) {
312+
x;
313+
}
314+
else {
315+
x;
316+
}
317+
}
318+
319+
function f15(x: { a?: string | undefined }) {
320+
if ("a" in x) {
321+
x;
322+
}
323+
else {
324+
x;
325+
}
326+
}
327+
328+
function f16(x: typeof globalThis, y: Window & typeof globalThis) {
329+
x = y;
330+
}
331+
274332
// Repro from #50639
275333

276334
function foo<A>(value: A) {
277335
if (typeof value === "object" && value !== null && "prop" in value) {
278336
value; // A & object & Record<"prop", unknown>
279337
}
280338
}
339+
340+
// Repro from #50954
341+
342+
const checkIsTouchDevice = () =>
343+
"ontouchstart" in window || "msMaxTouchPoints" in window.navigator;
281344

282345

283346
//// [inKeywordTypeguard.js]
@@ -533,9 +596,62 @@ function f9(x) {
533596
x[sym];
534597
}
535598
}
599+
function f10(x) {
600+
if ("a" in x) {
601+
x;
602+
}
603+
else {
604+
x;
605+
}
606+
}
607+
function f11(x) {
608+
if ("a" in x) {
609+
x;
610+
}
611+
else {
612+
x;
613+
}
614+
}
615+
function f12(x) {
616+
if ("a" in x) {
617+
x;
618+
}
619+
else {
620+
x;
621+
}
622+
}
623+
function f13(x) {
624+
if ("a" in x) {
625+
x;
626+
}
627+
else {
628+
x;
629+
}
630+
}
631+
function f14(x) {
632+
if ("a" in x) {
633+
x;
634+
}
635+
else {
636+
x;
637+
}
638+
}
639+
function f15(x) {
640+
if ("a" in x) {
641+
x;
642+
}
643+
else {
644+
x;
645+
}
646+
}
647+
function f16(x, y) {
648+
x = y;
649+
}
536650
// Repro from #50639
537651
function foo(value) {
538652
if (typeof value === "object" && value !== null && "prop" in value) {
539653
value; // A & object & Record<"prop", unknown>
540654
}
541655
}
656+
// Repro from #50954
657+
const checkIsTouchDevice = () => "ontouchstart" in window || "msMaxTouchPoints" in window.navigator;

0 commit comments

Comments
 (0)
Please sign in to comment.