Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
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
  • Loading branch information
ahejlsberg committed Sep 29, 2022
1 parent d1586de commit ecf50e8
Show file tree
Hide file tree
Showing 11 changed files with 1,004 additions and 42 deletions.
19 changes: 11 additions & 8 deletions src/compiler/checker.ts
Expand Up @@ -12566,7 +12566,7 @@ namespace ts {
let indexTypes: Type[] | undefined;
const isUnion = containingType.flags & TypeFlags.Union;
// Flags we want to propagate to the result if they exist in all source symbols
let optionalFlag = isUnion ? SymbolFlags.None : SymbolFlags.Optional;
let optionalFlag: SymbolFlags | undefined;
let syntheticFlag = CheckFlags.SyntheticMethod;
let checkFlags = isUnion ? 0 : CheckFlags.Readonly;
let mergedInstantiations = false;
Expand All @@ -12576,11 +12576,14 @@ namespace ts {
const prop = getPropertyOfType(type, name, skipObjectFunctionPropertyAugment);
const modifiers = prop ? getDeclarationModifierFlagsFromSymbol(prop) : 0;
if (prop) {
if (isUnion) {
optionalFlag |= (prop.flags & SymbolFlags.Optional);
}
else {
optionalFlag &= prop.flags;
if (prop.flags & SymbolFlags.ClassMember) {
optionalFlag ??= isUnion ? SymbolFlags.None : SymbolFlags.Optional;
if (isUnion) {
optionalFlag |= (prop.flags & SymbolFlags.Optional);
}
else {
optionalFlag &= prop.flags;
}
}
if (!singleProp) {
singleProp = prop;
Expand Down Expand Up @@ -12699,7 +12702,7 @@ namespace ts {
propTypes.push(type);
}
addRange(propTypes, indexTypes);
const result = createSymbol(SymbolFlags.Property | optionalFlag, name, syntheticFlag | checkFlags);
const result = createSymbol(SymbolFlags.Property | (optionalFlag ?? 0), name, syntheticFlag | checkFlags);
result.containingType = containingType;
if (!hasNonUniformValueDeclaration && firstValueDeclaration) {
result.valueDeclaration = firstValueDeclaration;
Expand Down Expand Up @@ -20455,7 +20458,7 @@ namespace ts {
return Ternary.False;
}
// When checking for comparability, be more lenient with optional properties.
if (!skipOptional && sourceProp.flags & SymbolFlags.Optional && !(targetProp.flags & SymbolFlags.Optional)) {
if (!skipOptional && sourceProp.flags & SymbolFlags.Optional && targetProp.flags & SymbolFlags.ClassMember && !(targetProp.flags & SymbolFlags.Optional)) {
// TypeScript 1.0 spec (April 2014): 3.8.3
// S is a subtype of a type T, and T is a supertype of S if ...
// S' and T are object types and, for each member M in T..
Expand Down
Expand Up @@ -21,14 +21,13 @@ tests/cases/compiler/inKeywordTypeguard.ts(74,32): error TS2339: Property 'a' do
tests/cases/compiler/inKeywordTypeguard.ts(82,39): error TS2339: Property 'b' does not exist on type 'A'.
tests/cases/compiler/inKeywordTypeguard.ts(84,39): error TS2339: Property 'a' does not exist on type 'B'.
tests/cases/compiler/inKeywordTypeguard.ts(94,26): error TS2339: Property 'a' does not exist on type 'never'.
tests/cases/compiler/inKeywordTypeguard.ts(150,16): error TS2339: Property 'ontouchstart' does not exist on type 'never'.
tests/cases/compiler/inKeywordTypeguard.ts(155,16): error TS2322: Type 'unknown' is not assignable to type 'object'.
tests/cases/compiler/inKeywordTypeguard.ts(158,21): error TS2322: Type 'unknown' is not assignable to type 'object'.
tests/cases/compiler/inKeywordTypeguard.ts(183,16): error TS2322: Type 'T' is not assignable to type 'object'.
tests/cases/compiler/inKeywordTypeguard.ts(186,21): error TS2322: Type 'T' is not assignable to type 'object'.


==== tests/cases/compiler/inKeywordTypeguard.ts (22 errors) ====
==== tests/cases/compiler/inKeywordTypeguard.ts (21 errors) ====
class A { a: string; }
class B { b: string; }

Expand Down Expand Up @@ -219,8 +218,6 @@ tests/cases/compiler/inKeywordTypeguard.ts(186,21): error TS2322: Type 'T' is no
window.ontouchstart
} else {
window.ontouchstart
~~~~~~~~~~~~
!!! error TS2339: Property 'ontouchstart' does not exist on type 'never'.
}
}

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

function f10(x: { a: unknown }) {
if ("a" in x) {
x;
}
else {
x;
}
}

function f11(x: { a: any }) {
if ("a" in x) {
x;
}
else {
x;
}
}

function f12(x: { a: string }) {
if ("a" in x) {
x;
}
else {
x;
}
}

function f13(x: { a?: string }) {
if ("a" in x) {
x;
}
else {
x;
}
}

function f14(x: { a: string | undefined }) {
if ("a" in x) {
x;
}
else {
x;
}
}

function f15(x: { a?: string | undefined }) {
if ("a" in x) {
x;
}
else {
x;
}
}

function f16(x: typeof globalThis, y: Window & typeof globalThis) {
x = y;
}

// Repro from #50639

function foo<A>(value: A) {
if (typeof value === "object" && value !== null && "prop" in value) {
value; // A & object & Record<"prop", unknown>
}
}

// Repro from #50954

const checkIsTouchDevice = () =>
"ontouchstart" in window || "msMaxTouchPoints" in window.navigator;

116 changes: 116 additions & 0 deletions tests/baselines/reference/inKeywordTypeguard(strict=false).js
Expand Up @@ -271,13 +271,76 @@ function f9(x: object) {
}
}

function f10(x: { a: unknown }) {
if ("a" in x) {
x;
}
else {
x;
}
}

function f11(x: { a: any }) {
if ("a" in x) {
x;
}
else {
x;
}
}

function f12(x: { a: string }) {
if ("a" in x) {
x;
}
else {
x;
}
}

function f13(x: { a?: string }) {
if ("a" in x) {
x;
}
else {
x;
}
}

function f14(x: { a: string | undefined }) {
if ("a" in x) {
x;
}
else {
x;
}
}

function f15(x: { a?: string | undefined }) {
if ("a" in x) {
x;
}
else {
x;
}
}

function f16(x: typeof globalThis, y: Window & typeof globalThis) {
x = y;
}

// Repro from #50639

function foo<A>(value: A) {
if (typeof value === "object" && value !== null && "prop" in value) {
value; // A & object & Record<"prop", unknown>
}
}

// Repro from #50954

const checkIsTouchDevice = () =>
"ontouchstart" in window || "msMaxTouchPoints" in window.navigator;


//// [inKeywordTypeguard.js]
Expand Down Expand Up @@ -533,9 +596,62 @@ function f9(x) {
x[sym];
}
}
function f10(x) {
if ("a" in x) {
x;
}
else {
x;
}
}
function f11(x) {
if ("a" in x) {
x;
}
else {
x;
}
}
function f12(x) {
if ("a" in x) {
x;
}
else {
x;
}
}
function f13(x) {
if ("a" in x) {
x;
}
else {
x;
}
}
function f14(x) {
if ("a" in x) {
x;
}
else {
x;
}
}
function f15(x) {
if ("a" in x) {
x;
}
else {
x;
}
}
function f16(x, y) {
x = y;
}
// Repro from #50639
function foo(value) {
if (typeof value === "object" && value !== null && "prop" in value) {
value; // A & object & Record<"prop", unknown>
}
}
// Repro from #50954
const checkIsTouchDevice = () => "ontouchstart" in window || "msMaxTouchPoints" in window.navigator;

0 comments on commit ecf50e8

Please sign in to comment.