Skip to content

Compute writeType from set accessors for union and intersection properties #47674

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
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
46 changes: 45 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9702,6 +9702,41 @@ namespace ts {
return links.type;
}

function getWriteTypeOfSymbolWithDeferredType(symbol: Symbol): Type | undefined {
const links = getSymbolLinks(symbol);
if (!links.writeType && links.deferralWriteConstituents) {
Debug.assertIsDefined(links.deferralParent);
Debug.assertIsDefined(links.deferralConstituents);
links.writeType = links.deferralParent.flags & TypeFlags.Union ? getUnionType(links.deferralWriteConstituents) : getIntersectionType(links.deferralWriteConstituents);
Copy link

Choose a reason for hiding this comment

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

Shouldn't the write type of a union be resolved into an intersection type? Just like the way mentioned in #30769

Copy link
Member Author

Choose a reason for hiding this comment

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

If you come up with a code example that doesn’t work the way you expect, feel free to file an issue.

Copy link

Choose a reason for hiding this comment

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

If you come up with a code example that doesn’t work the way you expect, feel free to file an issue.

Please have a look at the unsound behavior described in #50142. A comment indicates that it should be treated using the same rule of #30769.

I'm not quite sure but I guess it could have relation to this PR, where the write type of a union is not resolved into an intersection, but a union.

Choose a reason for hiding this comment

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

FWIW there’s a second complication in play in #50142: subtype reduction. Subtype reduction means that NullableProp | NonNullableProp is likely to be reduced to just the former, making #30769 a moot point.

}
return links.writeType;
}

/**
* Distinct write types come only from set accessors, but union and intersection
* properties deriving from set accessors will either pre-compute or defer the
* union or intersection of the writeTypes of their constituents. To account for
* this, we will assume that any deferred type or transient symbol may have a
* `writeType` (or a deferred write type ready to be computed) that should be
* used before looking for set accessor declarations.
*/
function getWriteTypeOfSymbol(symbol: Symbol): Type {
const checkFlags = getCheckFlags(symbol);
if (checkFlags & CheckFlags.DeferredType) {
const writeType = getWriteTypeOfSymbolWithDeferredType(symbol);
if (writeType) {
return writeType;
}
}
if (symbol.flags & SymbolFlags.Transient) {
const { writeType } = symbol as TransientSymbol;
if (writeType) {
return writeType;
}
}
return getSetAccessorTypeOfSymbol(symbol);
}

function getSetAccessorTypeOfSymbol(symbol: Symbol): Type {
if (symbol.flags & SymbolFlags.Accessor) {
const type = getTypeOfSetAccessor(symbol);
Expand Down Expand Up @@ -12204,6 +12239,7 @@ namespace ts {
let firstType: Type | undefined;
let nameType: Type | undefined;
const propTypes: Type[] = [];
let writeTypes: Type[] | undefined;
let firstValueDeclaration: Declaration | undefined;
let hasNonUniformValueDeclaration = false;
for (const prop of props) {
Expand All @@ -12219,6 +12255,10 @@ namespace ts {
firstType = type;
nameType = getSymbolLinks(prop).nameType;
}
const writeType = getWriteTypeOfSymbol(prop);
if (writeTypes || writeType !== type) {
writeTypes = append(!writeTypes ? propTypes.slice() : writeTypes, writeType);
}
else if (type !== firstType) {
checkFlags |= CheckFlags.HasNonUniformType;
}
Expand Down Expand Up @@ -12249,9 +12289,13 @@ namespace ts {
result.checkFlags |= CheckFlags.DeferredType;
result.deferralParent = containingType;
result.deferralConstituents = propTypes;
result.deferralWriteConstituents = writeTypes;
}
else {
result.type = isUnion ? getUnionType(propTypes) : getIntersectionType(propTypes);
if (writeTypes) {
result.writeType = isUnion ? getUnionType(writeTypes) : getIntersectionType(writeTypes);
}
}
return result;
}
Expand Down Expand Up @@ -28530,7 +28574,7 @@ namespace ts {
return errorType;
}

propType = isThisPropertyAccessInConstructor(node, prop) ? autoType : writing ? getSetAccessorTypeOfSymbol(prop) : getTypeOfSymbol(prop);
propType = isThisPropertyAccessInConstructor(node, prop) ? autoType : writing ? getWriteTypeOfSymbol(prop) : getTypeOfSymbol(prop);
}

return getFlowTypeOfAccessExpression(node, prop, propType, right, checkMode);
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4952,6 +4952,7 @@ namespace ts {
extendedContainersByFile?: ESMap<NodeId, Symbol[]>; // Containers (other than the parent) which this symbol is aliased in
variances?: VarianceFlags[]; // Alias symbol type argument variance cache
deferralConstituents?: Type[]; // Calculated list of constituents for a deferred type
deferralWriteConstituents?: Type[]; // Constituents of a deferred `writeType`
deferralParent?: Type; // Source union/intersection of a deferred type
cjsExportMerged?: Symbol; // Version of the symbol with all non export= exports merged with the export= target
typeOnlyDeclaration?: TypeOnlyAliasDeclaration | false; // First resolved alias declaration that makes the symbol only usable in type constructs
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ namespace ts {
return undefined;
}

export function getDeclarationsOfKind<T extends Declaration>(symbol: Symbol, kind: T["kind"]): T[] {
return filter(symbol.declarations || emptyArray, d => d.kind === kind) as T[];
}

export function createSymbolTable(symbols?: readonly Symbol[]): SymbolTable {
const result = new Map<__String, Symbol>();
if (symbols) {
Expand Down
108 changes: 108 additions & 0 deletions tests/baselines/reference/divergentAccessorsTypes3.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
//// [divergentAccessorsTypes3.ts]
class One {
get prop1(): string { return ""; }
set prop1(s: string | number) { }

get prop2(): string { return ""; }
set prop2(s: string | number) { }

prop3: number;

get prop4(): string { return ""; }
set prop4(s: string | number) { }
}

class Two {
get prop1(): string { return ""; }
set prop1(s: string | number) { }

get prop2(): string { return ""; }
set prop2(s: string) { }

get prop3(): string { return ""; }
set prop3(s: string | boolean) { }

get prop4(): string { return ""; }
set prop4(s: string | boolean) { }
}

declare const u1: One|Two;

u1.prop1 = 42;
u1.prop1 = "hello";

u1.prop2 = 42;
u1.prop2 = "hello";

u1.prop3 = 42;
u1.prop3 = "hello";
u1.prop3 = true;

u1.prop4 = 42;
u1.prop4 = "hello";
u1.prop4 = true;


//// [divergentAccessorsTypes3.js]
var One = /** @class */ (function () {
function One() {
}
Object.defineProperty(One.prototype, "prop1", {
get: function () { return ""; },
set: function (s) { },
enumerable: false,
configurable: true
});
Object.defineProperty(One.prototype, "prop2", {
get: function () { return ""; },
set: function (s) { },
enumerable: false,
configurable: true
});
Object.defineProperty(One.prototype, "prop4", {
get: function () { return ""; },
set: function (s) { },
enumerable: false,
configurable: true
});
return One;
}());
var Two = /** @class */ (function () {
function Two() {
}
Object.defineProperty(Two.prototype, "prop1", {
get: function () { return ""; },
set: function (s) { },
enumerable: false,
configurable: true
});
Object.defineProperty(Two.prototype, "prop2", {
get: function () { return ""; },
set: function (s) { },
enumerable: false,
configurable: true
});
Object.defineProperty(Two.prototype, "prop3", {
get: function () { return ""; },
set: function (s) { },
enumerable: false,
configurable: true
});
Object.defineProperty(Two.prototype, "prop4", {
get: function () { return ""; },
set: function (s) { },
enumerable: false,
configurable: true
});
return Two;
}());
u1.prop1 = 42;
u1.prop1 = "hello";
u1.prop2 = 42;
u1.prop2 = "hello";
u1.prop3 = 42;
u1.prop3 = "hello";
u1.prop3 = true;
u1.prop4 = 42;
u1.prop4 = "hello";
u1.prop4 = true;
116 changes: 116 additions & 0 deletions tests/baselines/reference/divergentAccessorsTypes3.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
=== tests/cases/compiler/divergentAccessorsTypes3.ts ===
class One {
>One : Symbol(One, Decl(divergentAccessorsTypes3.ts, 0, 0))

get prop1(): string { return ""; }
>prop1 : Symbol(One.prop1, Decl(divergentAccessorsTypes3.ts, 0, 11), Decl(divergentAccessorsTypes3.ts, 1, 36))

set prop1(s: string | number) { }
>prop1 : Symbol(One.prop1, Decl(divergentAccessorsTypes3.ts, 0, 11), Decl(divergentAccessorsTypes3.ts, 1, 36))
>s : Symbol(s, Decl(divergentAccessorsTypes3.ts, 2, 12))

get prop2(): string { return ""; }
>prop2 : Symbol(One.prop2, Decl(divergentAccessorsTypes3.ts, 2, 35), Decl(divergentAccessorsTypes3.ts, 4, 36))

set prop2(s: string | number) { }
>prop2 : Symbol(One.prop2, Decl(divergentAccessorsTypes3.ts, 2, 35), Decl(divergentAccessorsTypes3.ts, 4, 36))
>s : Symbol(s, Decl(divergentAccessorsTypes3.ts, 5, 12))

prop3: number;
>prop3 : Symbol(One.prop3, Decl(divergentAccessorsTypes3.ts, 5, 35))

get prop4(): string { return ""; }
>prop4 : Symbol(One.prop4, Decl(divergentAccessorsTypes3.ts, 7, 16), Decl(divergentAccessorsTypes3.ts, 9, 36))

set prop4(s: string | number) { }
>prop4 : Symbol(One.prop4, Decl(divergentAccessorsTypes3.ts, 7, 16), Decl(divergentAccessorsTypes3.ts, 9, 36))
>s : Symbol(s, Decl(divergentAccessorsTypes3.ts, 10, 12))
}

class Two {
>Two : Symbol(Two, Decl(divergentAccessorsTypes3.ts, 11, 1))

get prop1(): string { return ""; }
>prop1 : Symbol(Two.prop1, Decl(divergentAccessorsTypes3.ts, 13, 11), Decl(divergentAccessorsTypes3.ts, 14, 36))

set prop1(s: string | number) { }
>prop1 : Symbol(Two.prop1, Decl(divergentAccessorsTypes3.ts, 13, 11), Decl(divergentAccessorsTypes3.ts, 14, 36))
>s : Symbol(s, Decl(divergentAccessorsTypes3.ts, 15, 12))

get prop2(): string { return ""; }
>prop2 : Symbol(Two.prop2, Decl(divergentAccessorsTypes3.ts, 15, 35), Decl(divergentAccessorsTypes3.ts, 17, 36))

set prop2(s: string) { }
>prop2 : Symbol(Two.prop2, Decl(divergentAccessorsTypes3.ts, 15, 35), Decl(divergentAccessorsTypes3.ts, 17, 36))
>s : Symbol(s, Decl(divergentAccessorsTypes3.ts, 18, 12))

get prop3(): string { return ""; }
>prop3 : Symbol(Two.prop3, Decl(divergentAccessorsTypes3.ts, 18, 26), Decl(divergentAccessorsTypes3.ts, 20, 36))

set prop3(s: string | boolean) { }
>prop3 : Symbol(Two.prop3, Decl(divergentAccessorsTypes3.ts, 18, 26), Decl(divergentAccessorsTypes3.ts, 20, 36))
>s : Symbol(s, Decl(divergentAccessorsTypes3.ts, 21, 12))

get prop4(): string { return ""; }
>prop4 : Symbol(Two.prop4, Decl(divergentAccessorsTypes3.ts, 21, 36), Decl(divergentAccessorsTypes3.ts, 23, 36))

set prop4(s: string | boolean) { }
>prop4 : Symbol(Two.prop4, Decl(divergentAccessorsTypes3.ts, 21, 36), Decl(divergentAccessorsTypes3.ts, 23, 36))
>s : Symbol(s, Decl(divergentAccessorsTypes3.ts, 24, 12))
}

declare const u1: One|Two;
>u1 : Symbol(u1, Decl(divergentAccessorsTypes3.ts, 27, 13))
>One : Symbol(One, Decl(divergentAccessorsTypes3.ts, 0, 0))
>Two : Symbol(Two, Decl(divergentAccessorsTypes3.ts, 11, 1))

u1.prop1 = 42;
>u1.prop1 : Symbol(prop1, Decl(divergentAccessorsTypes3.ts, 0, 11), Decl(divergentAccessorsTypes3.ts, 1, 36), Decl(divergentAccessorsTypes3.ts, 13, 11), Decl(divergentAccessorsTypes3.ts, 14, 36))
>u1 : Symbol(u1, Decl(divergentAccessorsTypes3.ts, 27, 13))
>prop1 : Symbol(prop1, Decl(divergentAccessorsTypes3.ts, 0, 11), Decl(divergentAccessorsTypes3.ts, 1, 36), Decl(divergentAccessorsTypes3.ts, 13, 11), Decl(divergentAccessorsTypes3.ts, 14, 36))

u1.prop1 = "hello";
>u1.prop1 : Symbol(prop1, Decl(divergentAccessorsTypes3.ts, 0, 11), Decl(divergentAccessorsTypes3.ts, 1, 36), Decl(divergentAccessorsTypes3.ts, 13, 11), Decl(divergentAccessorsTypes3.ts, 14, 36))
>u1 : Symbol(u1, Decl(divergentAccessorsTypes3.ts, 27, 13))
>prop1 : Symbol(prop1, Decl(divergentAccessorsTypes3.ts, 0, 11), Decl(divergentAccessorsTypes3.ts, 1, 36), Decl(divergentAccessorsTypes3.ts, 13, 11), Decl(divergentAccessorsTypes3.ts, 14, 36))

u1.prop2 = 42;
>u1.prop2 : Symbol(prop2, Decl(divergentAccessorsTypes3.ts, 2, 35), Decl(divergentAccessorsTypes3.ts, 4, 36), Decl(divergentAccessorsTypes3.ts, 15, 35), Decl(divergentAccessorsTypes3.ts, 17, 36))
>u1 : Symbol(u1, Decl(divergentAccessorsTypes3.ts, 27, 13))
>prop2 : Symbol(prop2, Decl(divergentAccessorsTypes3.ts, 2, 35), Decl(divergentAccessorsTypes3.ts, 4, 36), Decl(divergentAccessorsTypes3.ts, 15, 35), Decl(divergentAccessorsTypes3.ts, 17, 36))

u1.prop2 = "hello";
>u1.prop2 : Symbol(prop2, Decl(divergentAccessorsTypes3.ts, 2, 35), Decl(divergentAccessorsTypes3.ts, 4, 36), Decl(divergentAccessorsTypes3.ts, 15, 35), Decl(divergentAccessorsTypes3.ts, 17, 36))
>u1 : Symbol(u1, Decl(divergentAccessorsTypes3.ts, 27, 13))
>prop2 : Symbol(prop2, Decl(divergentAccessorsTypes3.ts, 2, 35), Decl(divergentAccessorsTypes3.ts, 4, 36), Decl(divergentAccessorsTypes3.ts, 15, 35), Decl(divergentAccessorsTypes3.ts, 17, 36))

u1.prop3 = 42;
>u1.prop3 : Symbol(prop3, Decl(divergentAccessorsTypes3.ts, 5, 35), Decl(divergentAccessorsTypes3.ts, 18, 26), Decl(divergentAccessorsTypes3.ts, 20, 36))
>u1 : Symbol(u1, Decl(divergentAccessorsTypes3.ts, 27, 13))
>prop3 : Symbol(prop3, Decl(divergentAccessorsTypes3.ts, 5, 35), Decl(divergentAccessorsTypes3.ts, 18, 26), Decl(divergentAccessorsTypes3.ts, 20, 36))

u1.prop3 = "hello";
>u1.prop3 : Symbol(prop3, Decl(divergentAccessorsTypes3.ts, 5, 35), Decl(divergentAccessorsTypes3.ts, 18, 26), Decl(divergentAccessorsTypes3.ts, 20, 36))
>u1 : Symbol(u1, Decl(divergentAccessorsTypes3.ts, 27, 13))
>prop3 : Symbol(prop3, Decl(divergentAccessorsTypes3.ts, 5, 35), Decl(divergentAccessorsTypes3.ts, 18, 26), Decl(divergentAccessorsTypes3.ts, 20, 36))

u1.prop3 = true;
>u1.prop3 : Symbol(prop3, Decl(divergentAccessorsTypes3.ts, 5, 35), Decl(divergentAccessorsTypes3.ts, 18, 26), Decl(divergentAccessorsTypes3.ts, 20, 36))
>u1 : Symbol(u1, Decl(divergentAccessorsTypes3.ts, 27, 13))
>prop3 : Symbol(prop3, Decl(divergentAccessorsTypes3.ts, 5, 35), Decl(divergentAccessorsTypes3.ts, 18, 26), Decl(divergentAccessorsTypes3.ts, 20, 36))

u1.prop4 = 42;
>u1.prop4 : Symbol(prop4, Decl(divergentAccessorsTypes3.ts, 7, 16), Decl(divergentAccessorsTypes3.ts, 9, 36), Decl(divergentAccessorsTypes3.ts, 21, 36), Decl(divergentAccessorsTypes3.ts, 23, 36))
>u1 : Symbol(u1, Decl(divergentAccessorsTypes3.ts, 27, 13))
>prop4 : Symbol(prop4, Decl(divergentAccessorsTypes3.ts, 7, 16), Decl(divergentAccessorsTypes3.ts, 9, 36), Decl(divergentAccessorsTypes3.ts, 21, 36), Decl(divergentAccessorsTypes3.ts, 23, 36))

u1.prop4 = "hello";
>u1.prop4 : Symbol(prop4, Decl(divergentAccessorsTypes3.ts, 7, 16), Decl(divergentAccessorsTypes3.ts, 9, 36), Decl(divergentAccessorsTypes3.ts, 21, 36), Decl(divergentAccessorsTypes3.ts, 23, 36))
>u1 : Symbol(u1, Decl(divergentAccessorsTypes3.ts, 27, 13))
>prop4 : Symbol(prop4, Decl(divergentAccessorsTypes3.ts, 7, 16), Decl(divergentAccessorsTypes3.ts, 9, 36), Decl(divergentAccessorsTypes3.ts, 21, 36), Decl(divergentAccessorsTypes3.ts, 23, 36))

u1.prop4 = true;
>u1.prop4 : Symbol(prop4, Decl(divergentAccessorsTypes3.ts, 7, 16), Decl(divergentAccessorsTypes3.ts, 9, 36), Decl(divergentAccessorsTypes3.ts, 21, 36), Decl(divergentAccessorsTypes3.ts, 23, 36))
>u1 : Symbol(u1, Decl(divergentAccessorsTypes3.ts, 27, 13))
>prop4 : Symbol(prop4, Decl(divergentAccessorsTypes3.ts, 7, 16), Decl(divergentAccessorsTypes3.ts, 9, 36), Decl(divergentAccessorsTypes3.ts, 21, 36), Decl(divergentAccessorsTypes3.ts, 23, 36))

Loading