Ensure subtype relation ordering for readonly properties (#47069)

* Ensure subtype relation ordering for readonly properties

* Probably fix post-LKG assignability error
This commit is contained in:
Wesley Wigham
2022-02-11 14:59:01 -08:00
committed by GitHub
parent 033f9e0081
commit e204acfa26
7 changed files with 705 additions and 1 deletions
+12
View File
@@ -19732,6 +19732,18 @@ namespace ts {
}
return Ternary.False;
}
// Ensure {readonly a: whatever} is not a subtype of {a: whatever},
// while {a: whatever} is a subtype of {readonly a: whatever}.
// This ensures the subtype relationship is ordered, and preventing declaration order
// from deciding which type "wins" in union subtype reduction.
// They're still assignable to one another, since `readonly` doesn't affect assignability.
if (
(relation === subtypeRelation || relation === strictSubtypeRelation) &&
!!(sourcePropFlags & ModifierFlags.Readonly) && !(targetPropFlags & ModifierFlags.Readonly)
) {
return Ternary.False;
}
// If the target comes from a partial union prop, allow `undefined` in the target type
const related = isPropertySymbolTypeRelated(sourceProp, targetProp, getTypeOfSourceProperty, reportErrors, intersectionState);
if (!related) {
+1 -1
View File
@@ -303,7 +303,7 @@ namespace ts.textChanges {
export class ChangeTracker {
private readonly changes: Change[] = [];
private readonly newFiles: { readonly oldFile: SourceFile | undefined, readonly fileName: string, readonly statements: readonly (Statement | SyntaxKind.NewLineTrivia)[] }[] = [];
private readonly classesWithNodesInsertedAtStart = new Map<number, { readonly node: ClassDeclaration | InterfaceDeclaration | ObjectLiteralExpression, readonly sourceFile: SourceFile }>(); // Set<ClassDeclaration> implemented as Map<node id, ClassDeclaration>
private readonly classesWithNodesInsertedAtStart = new Map<number, { readonly node: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression, readonly sourceFile: SourceFile }>(); // Set<ClassDeclaration> implemented as Map<node id, ClassDeclaration>
private readonly deletedNodes: { readonly sourceFile: SourceFile, readonly node: Node | NodeArray<TypeParameterDeclaration> }[] = [];
public static fromContext(context: TextChangesContext): ChangeTracker {
@@ -0,0 +1,104 @@
tests/cases/compiler/four.ts(11,11): error TS2540: Cannot assign to 'a' because it is a read-only property.
tests/cases/compiler/four.ts(15,11): error TS2540: Cannot assign to 'a' because it is a read-only property.
tests/cases/compiler/one.ts(11,11): error TS2540: Cannot assign to 'a' because it is a read-only property.
tests/cases/compiler/one.ts(15,11): error TS2540: Cannot assign to 'a' because it is a read-only property.
tests/cases/compiler/three.ts(11,11): error TS2540: Cannot assign to 'a' because it is a read-only property.
tests/cases/compiler/three.ts(15,11): error TS2540: Cannot assign to 'a' because it is a read-only property.
tests/cases/compiler/two.ts(11,11): error TS2540: Cannot assign to 'a' because it is a read-only property.
tests/cases/compiler/two.ts(15,11): error TS2540: Cannot assign to 'a' because it is a read-only property.
==== tests/cases/compiler/one.ts (2 errors) ====
export {};
// When the non-readonly type is declared first, the unioned type of `three` in `doSomething` is never treated as readonly
const two: { a: string } = { a: 'two' };
const one: { readonly a: string } = { a: 'one' };
function doSomething(condition: boolean) {
// when `one` comes first in the conditional check, the return type of `doSomething` is inferred as `a` is readonly, but `a` is
// only treated as readonly (i.e. it will produce a diagnostic if you try to assign to it) based on the order of declarations of `one` and `two` above
const three = (condition) ? one : two;
three.a = 'foo';
~
!!! error TS2540: Cannot assign to 'a' because it is a read-only property.
// the inferred (displayed?) type of `a` also depends on the order of the condition above. When `one` comes first, the displayed type is `any`
// when `two` comes first, the displayed type is `string`, but the diagnostic will always correctly find that it's string
three.a = 'foo2';
~
!!! error TS2540: Cannot assign to 'a' because it is a read-only property.
return three;
}
==== tests/cases/compiler/two.ts (2 errors) ====
export {};
// When the non-readonly type is declared first, the unioned type of `three` in `doSomething` is never treated as readonly
const two: { a: string } = { a: 'two' };
const one: { readonly a: string } = { a: 'one' };
function doSomething(condition: boolean) {
// when `two` comes first in the conditional check, the return type of `doSomething` is inferred as not readonly but produces the same diagnostics as above
// based on the declaration order of `one` and `two`
const three = (condition) ? two : one;
three.a = 'foo';
~
!!! error TS2540: Cannot assign to 'a' because it is a read-only property.
// the inferred (displayed?) type of `a` also depends on the order of the condition above. When `one` comes first, the displayed type is `any`
// when `two` comes first, the displayed type is `string`, but the diagnostic will always correctly find that it's string
three.a = 'foo2';
~
!!! error TS2540: Cannot assign to 'a' because it is a read-only property.
return three;
}
==== tests/cases/compiler/three.ts (2 errors) ====
export {};
// When the readonly type is declared first, the unioned type of `three` in `doSomething` is always treated as readonly by the compiler
const one: { readonly a: string } = { a: 'one' };
const two: { a: string } = { a: 'two' };
function doSomething(condition: boolean) {
// when `one` comes first in the conditional check, the return type of `doSomething` is inferred as `a` is readonly, but `a` is
// only treated as readonly (i.e. it will produce a diagnostic if you try to assign to it) based on the order of declarations of `one` and `two` above
const three = (condition) ? one : two;
three.a = 'foo';
~
!!! error TS2540: Cannot assign to 'a' because it is a read-only property.
// the inferred (displayed?) type of `a` also depends on the order of the condition above. When `one` comes first, the displayed type is `any`
// when `two` comes first, the displayed type is `string`, but the diagnostic will always correctly find that it's string
three.a = 'foo2';
~
!!! error TS2540: Cannot assign to 'a' because it is a read-only property.
return three;
}
==== tests/cases/compiler/four.ts (2 errors) ====
export {};
// When the readonly type is declared first, the unioned type of `three` in `doSomething` is always treated as readonly by the compiler
const one: { readonly a: string } = { a: 'one' };
const two: { a: string } = { a: 'two' };
function doSomething(condition: boolean) {
// when `two` comes first in the conditional check, the return type of `doSomething` is inferred as not readonly but produces the same diagnostics as above
// based on the declaration order of `one` and `two`
const three = (condition) ? two : one;
three.a = 'foo';
~
!!! error TS2540: Cannot assign to 'a' because it is a read-only property.
// the inferred (displayed?) type of `a` also depends on the order of the condition above. When `one` comes first, the displayed type is `any`
// when `two` comes first, the displayed type is `string`, but the diagnostic will always correctly find that it's string
three.a = 'foo2';
~
!!! error TS2540: Cannot assign to 'a' because it is a read-only property.
return three;
}
@@ -0,0 +1,145 @@
//// [tests/cases/compiler/readonlyPropertySubtypeRelationDirected.ts] ////
//// [one.ts]
export {};
// When the non-readonly type is declared first, the unioned type of `three` in `doSomething` is never treated as readonly
const two: { a: string } = { a: 'two' };
const one: { readonly a: string } = { a: 'one' };
function doSomething(condition: boolean) {
// when `one` comes first in the conditional check, the return type of `doSomething` is inferred as `a` is readonly, but `a` is
// only treated as readonly (i.e. it will produce a diagnostic if you try to assign to it) based on the order of declarations of `one` and `two` above
const three = (condition) ? one : two;
three.a = 'foo';
// the inferred (displayed?) type of `a` also depends on the order of the condition above. When `one` comes first, the displayed type is `any`
// when `two` comes first, the displayed type is `string`, but the diagnostic will always correctly find that it's string
three.a = 'foo2';
return three;
}
//// [two.ts]
export {};
// When the non-readonly type is declared first, the unioned type of `three` in `doSomething` is never treated as readonly
const two: { a: string } = { a: 'two' };
const one: { readonly a: string } = { a: 'one' };
function doSomething(condition: boolean) {
// when `two` comes first in the conditional check, the return type of `doSomething` is inferred as not readonly but produces the same diagnostics as above
// based on the declaration order of `one` and `two`
const three = (condition) ? two : one;
three.a = 'foo';
// the inferred (displayed?) type of `a` also depends on the order of the condition above. When `one` comes first, the displayed type is `any`
// when `two` comes first, the displayed type is `string`, but the diagnostic will always correctly find that it's string
three.a = 'foo2';
return three;
}
//// [three.ts]
export {};
// When the readonly type is declared first, the unioned type of `three` in `doSomething` is always treated as readonly by the compiler
const one: { readonly a: string } = { a: 'one' };
const two: { a: string } = { a: 'two' };
function doSomething(condition: boolean) {
// when `one` comes first in the conditional check, the return type of `doSomething` is inferred as `a` is readonly, but `a` is
// only treated as readonly (i.e. it will produce a diagnostic if you try to assign to it) based on the order of declarations of `one` and `two` above
const three = (condition) ? one : two;
three.a = 'foo';
// the inferred (displayed?) type of `a` also depends on the order of the condition above. When `one` comes first, the displayed type is `any`
// when `two` comes first, the displayed type is `string`, but the diagnostic will always correctly find that it's string
three.a = 'foo2';
return three;
}
//// [four.ts]
export {};
// When the readonly type is declared first, the unioned type of `three` in `doSomething` is always treated as readonly by the compiler
const one: { readonly a: string } = { a: 'one' };
const two: { a: string } = { a: 'two' };
function doSomething(condition: boolean) {
// when `two` comes first in the conditional check, the return type of `doSomething` is inferred as not readonly but produces the same diagnostics as above
// based on the declaration order of `one` and `two`
const three = (condition) ? two : one;
three.a = 'foo';
// the inferred (displayed?) type of `a` also depends on the order of the condition above. When `one` comes first, the displayed type is `any`
// when `two` comes first, the displayed type is `string`, but the diagnostic will always correctly find that it's string
three.a = 'foo2';
return three;
}
//// [one.js]
"use strict";
exports.__esModule = true;
// When the non-readonly type is declared first, the unioned type of `three` in `doSomething` is never treated as readonly
var two = { a: 'two' };
var one = { a: 'one' };
function doSomething(condition) {
// when `one` comes first in the conditional check, the return type of `doSomething` is inferred as `a` is readonly, but `a` is
// only treated as readonly (i.e. it will produce a diagnostic if you try to assign to it) based on the order of declarations of `one` and `two` above
var three = (condition) ? one : two;
three.a = 'foo';
// the inferred (displayed?) type of `a` also depends on the order of the condition above. When `one` comes first, the displayed type is `any`
// when `two` comes first, the displayed type is `string`, but the diagnostic will always correctly find that it's string
three.a = 'foo2';
return three;
}
//// [two.js]
"use strict";
exports.__esModule = true;
// When the non-readonly type is declared first, the unioned type of `three` in `doSomething` is never treated as readonly
var two = { a: 'two' };
var one = { a: 'one' };
function doSomething(condition) {
// when `two` comes first in the conditional check, the return type of `doSomething` is inferred as not readonly but produces the same diagnostics as above
// based on the declaration order of `one` and `two`
var three = (condition) ? two : one;
three.a = 'foo';
// the inferred (displayed?) type of `a` also depends on the order of the condition above. When `one` comes first, the displayed type is `any`
// when `two` comes first, the displayed type is `string`, but the diagnostic will always correctly find that it's string
three.a = 'foo2';
return three;
}
//// [three.js]
"use strict";
exports.__esModule = true;
// When the readonly type is declared first, the unioned type of `three` in `doSomething` is always treated as readonly by the compiler
var one = { a: 'one' };
var two = { a: 'two' };
function doSomething(condition) {
// when `one` comes first in the conditional check, the return type of `doSomething` is inferred as `a` is readonly, but `a` is
// only treated as readonly (i.e. it will produce a diagnostic if you try to assign to it) based on the order of declarations of `one` and `two` above
var three = (condition) ? one : two;
three.a = 'foo';
// the inferred (displayed?) type of `a` also depends on the order of the condition above. When `one` comes first, the displayed type is `any`
// when `two` comes first, the displayed type is `string`, but the diagnostic will always correctly find that it's string
three.a = 'foo2';
return three;
}
//// [four.js]
"use strict";
exports.__esModule = true;
// When the readonly type is declared first, the unioned type of `three` in `doSomething` is always treated as readonly by the compiler
var one = { a: 'one' };
var two = { a: 'two' };
function doSomething(condition) {
// when `two` comes first in the conditional check, the return type of `doSomething` is inferred as not readonly but produces the same diagnostics as above
// based on the declaration order of `one` and `two`
var three = (condition) ? two : one;
three.a = 'foo';
// the inferred (displayed?) type of `a` also depends on the order of the condition above. When `one` comes first, the displayed type is `any`
// when `two` comes first, the displayed type is `string`, but the diagnostic will always correctly find that it's string
three.a = 'foo2';
return three;
}
@@ -0,0 +1,162 @@
=== tests/cases/compiler/one.ts ===
export {};
// When the non-readonly type is declared first, the unioned type of `three` in `doSomething` is never treated as readonly
const two: { a: string } = { a: 'two' };
>two : Symbol(two, Decl(one.ts, 2, 5))
>a : Symbol(a, Decl(one.ts, 2, 12))
>a : Symbol(a, Decl(one.ts, 2, 28))
const one: { readonly a: string } = { a: 'one' };
>one : Symbol(one, Decl(one.ts, 3, 5))
>a : Symbol(a, Decl(one.ts, 3, 12))
>a : Symbol(a, Decl(one.ts, 3, 37))
function doSomething(condition: boolean) {
>doSomething : Symbol(doSomething, Decl(one.ts, 3, 49))
>condition : Symbol(condition, Decl(one.ts, 5, 21))
// when `one` comes first in the conditional check, the return type of `doSomething` is inferred as `a` is readonly, but `a` is
// only treated as readonly (i.e. it will produce a diagnostic if you try to assign to it) based on the order of declarations of `one` and `two` above
const three = (condition) ? one : two;
>three : Symbol(three, Decl(one.ts, 8, 9))
>condition : Symbol(condition, Decl(one.ts, 5, 21))
>one : Symbol(one, Decl(one.ts, 3, 5))
>two : Symbol(two, Decl(one.ts, 2, 5))
three.a = 'foo';
>three.a : Symbol(a, Decl(one.ts, 3, 12))
>three : Symbol(three, Decl(one.ts, 8, 9))
>a : Symbol(a, Decl(one.ts, 3, 12))
// the inferred (displayed?) type of `a` also depends on the order of the condition above. When `one` comes first, the displayed type is `any`
// when `two` comes first, the displayed type is `string`, but the diagnostic will always correctly find that it's string
three.a = 'foo2';
>three.a : Symbol(a, Decl(one.ts, 3, 12))
>three : Symbol(three, Decl(one.ts, 8, 9))
>a : Symbol(a, Decl(one.ts, 3, 12))
return three;
>three : Symbol(three, Decl(one.ts, 8, 9))
}
=== tests/cases/compiler/two.ts ===
export {};
// When the non-readonly type is declared first, the unioned type of `three` in `doSomething` is never treated as readonly
const two: { a: string } = { a: 'two' };
>two : Symbol(two, Decl(two.ts, 2, 5))
>a : Symbol(a, Decl(two.ts, 2, 12))
>a : Symbol(a, Decl(two.ts, 2, 28))
const one: { readonly a: string } = { a: 'one' };
>one : Symbol(one, Decl(two.ts, 3, 5))
>a : Symbol(a, Decl(two.ts, 3, 12))
>a : Symbol(a, Decl(two.ts, 3, 37))
function doSomething(condition: boolean) {
>doSomething : Symbol(doSomething, Decl(two.ts, 3, 49))
>condition : Symbol(condition, Decl(two.ts, 5, 21))
// when `two` comes first in the conditional check, the return type of `doSomething` is inferred as not readonly but produces the same diagnostics as above
// based on the declaration order of `one` and `two`
const three = (condition) ? two : one;
>three : Symbol(three, Decl(two.ts, 8, 9))
>condition : Symbol(condition, Decl(two.ts, 5, 21))
>two : Symbol(two, Decl(two.ts, 2, 5))
>one : Symbol(one, Decl(two.ts, 3, 5))
three.a = 'foo';
>three.a : Symbol(a, Decl(two.ts, 3, 12))
>three : Symbol(three, Decl(two.ts, 8, 9))
>a : Symbol(a, Decl(two.ts, 3, 12))
// the inferred (displayed?) type of `a` also depends on the order of the condition above. When `one` comes first, the displayed type is `any`
// when `two` comes first, the displayed type is `string`, but the diagnostic will always correctly find that it's string
three.a = 'foo2';
>three.a : Symbol(a, Decl(two.ts, 3, 12))
>three : Symbol(three, Decl(two.ts, 8, 9))
>a : Symbol(a, Decl(two.ts, 3, 12))
return three;
>three : Symbol(three, Decl(two.ts, 8, 9))
}
=== tests/cases/compiler/three.ts ===
export {};
// When the readonly type is declared first, the unioned type of `three` in `doSomething` is always treated as readonly by the compiler
const one: { readonly a: string } = { a: 'one' };
>one : Symbol(one, Decl(three.ts, 2, 5))
>a : Symbol(a, Decl(three.ts, 2, 12))
>a : Symbol(a, Decl(three.ts, 2, 37))
const two: { a: string } = { a: 'two' };
>two : Symbol(two, Decl(three.ts, 3, 5))
>a : Symbol(a, Decl(three.ts, 3, 12))
>a : Symbol(a, Decl(three.ts, 3, 28))
function doSomething(condition: boolean) {
>doSomething : Symbol(doSomething, Decl(three.ts, 3, 40))
>condition : Symbol(condition, Decl(three.ts, 5, 21))
// when `one` comes first in the conditional check, the return type of `doSomething` is inferred as `a` is readonly, but `a` is
// only treated as readonly (i.e. it will produce a diagnostic if you try to assign to it) based on the order of declarations of `one` and `two` above
const three = (condition) ? one : two;
>three : Symbol(three, Decl(three.ts, 8, 9))
>condition : Symbol(condition, Decl(three.ts, 5, 21))
>one : Symbol(one, Decl(three.ts, 2, 5))
>two : Symbol(two, Decl(three.ts, 3, 5))
three.a = 'foo';
>three.a : Symbol(a, Decl(three.ts, 2, 12))
>three : Symbol(three, Decl(three.ts, 8, 9))
>a : Symbol(a, Decl(three.ts, 2, 12))
// the inferred (displayed?) type of `a` also depends on the order of the condition above. When `one` comes first, the displayed type is `any`
// when `two` comes first, the displayed type is `string`, but the diagnostic will always correctly find that it's string
three.a = 'foo2';
>three.a : Symbol(a, Decl(three.ts, 2, 12))
>three : Symbol(three, Decl(three.ts, 8, 9))
>a : Symbol(a, Decl(three.ts, 2, 12))
return three;
>three : Symbol(three, Decl(three.ts, 8, 9))
}
=== tests/cases/compiler/four.ts ===
export {};
// When the readonly type is declared first, the unioned type of `three` in `doSomething` is always treated as readonly by the compiler
const one: { readonly a: string } = { a: 'one' };
>one : Symbol(one, Decl(four.ts, 2, 5))
>a : Symbol(a, Decl(four.ts, 2, 12))
>a : Symbol(a, Decl(four.ts, 2, 37))
const two: { a: string } = { a: 'two' };
>two : Symbol(two, Decl(four.ts, 3, 5))
>a : Symbol(a, Decl(four.ts, 3, 12))
>a : Symbol(a, Decl(four.ts, 3, 28))
function doSomething(condition: boolean) {
>doSomething : Symbol(doSomething, Decl(four.ts, 3, 40))
>condition : Symbol(condition, Decl(four.ts, 5, 21))
// when `two` comes first in the conditional check, the return type of `doSomething` is inferred as not readonly but produces the same diagnostics as above
// based on the declaration order of `one` and `two`
const three = (condition) ? two : one;
>three : Symbol(three, Decl(four.ts, 8, 9))
>condition : Symbol(condition, Decl(four.ts, 5, 21))
>two : Symbol(two, Decl(four.ts, 3, 5))
>one : Symbol(one, Decl(four.ts, 2, 5))
three.a = 'foo';
>three.a : Symbol(a, Decl(four.ts, 2, 12))
>three : Symbol(three, Decl(four.ts, 8, 9))
>a : Symbol(a, Decl(four.ts, 2, 12))
// the inferred (displayed?) type of `a` also depends on the order of the condition above. When `one` comes first, the displayed type is `any`
// when `two` comes first, the displayed type is `string`, but the diagnostic will always correctly find that it's string
three.a = 'foo2';
>three.a : Symbol(a, Decl(four.ts, 2, 12))
>three : Symbol(three, Decl(four.ts, 8, 9))
>a : Symbol(a, Decl(four.ts, 2, 12))
return three;
>three : Symbol(three, Decl(four.ts, 8, 9))
}
@@ -0,0 +1,202 @@
=== tests/cases/compiler/one.ts ===
export {};
// When the non-readonly type is declared first, the unioned type of `three` in `doSomething` is never treated as readonly
const two: { a: string } = { a: 'two' };
>two : { a: string; }
>a : string
>{ a: 'two' } : { a: string; }
>a : string
>'two' : "two"
const one: { readonly a: string } = { a: 'one' };
>one : { readonly a: string; }
>a : string
>{ a: 'one' } : { a: string; }
>a : string
>'one' : "one"
function doSomething(condition: boolean) {
>doSomething : (condition: boolean) => { readonly a: string; }
>condition : boolean
// when `one` comes first in the conditional check, the return type of `doSomething` is inferred as `a` is readonly, but `a` is
// only treated as readonly (i.e. it will produce a diagnostic if you try to assign to it) based on the order of declarations of `one` and `two` above
const three = (condition) ? one : two;
>three : { readonly a: string; }
>(condition) ? one : two : { readonly a: string; }
>(condition) : boolean
>condition : boolean
>one : { readonly a: string; }
>two : { a: string; }
three.a = 'foo';
>three.a = 'foo' : "foo"
>three.a : any
>three : { readonly a: string; }
>a : any
>'foo' : "foo"
// the inferred (displayed?) type of `a` also depends on the order of the condition above. When `one` comes first, the displayed type is `any`
// when `two` comes first, the displayed type is `string`, but the diagnostic will always correctly find that it's string
three.a = 'foo2';
>three.a = 'foo2' : "foo2"
>three.a : any
>three : { readonly a: string; }
>a : any
>'foo2' : "foo2"
return three;
>three : { readonly a: string; }
}
=== tests/cases/compiler/two.ts ===
export {};
// When the non-readonly type is declared first, the unioned type of `three` in `doSomething` is never treated as readonly
const two: { a: string } = { a: 'two' };
>two : { a: string; }
>a : string
>{ a: 'two' } : { a: string; }
>a : string
>'two' : "two"
const one: { readonly a: string } = { a: 'one' };
>one : { readonly a: string; }
>a : string
>{ a: 'one' } : { a: string; }
>a : string
>'one' : "one"
function doSomething(condition: boolean) {
>doSomething : (condition: boolean) => { readonly a: string; }
>condition : boolean
// when `two` comes first in the conditional check, the return type of `doSomething` is inferred as not readonly but produces the same diagnostics as above
// based on the declaration order of `one` and `two`
const three = (condition) ? two : one;
>three : { readonly a: string; }
>(condition) ? two : one : { readonly a: string; }
>(condition) : boolean
>condition : boolean
>two : { a: string; }
>one : { readonly a: string; }
three.a = 'foo';
>three.a = 'foo' : "foo"
>three.a : any
>three : { readonly a: string; }
>a : any
>'foo' : "foo"
// the inferred (displayed?) type of `a` also depends on the order of the condition above. When `one` comes first, the displayed type is `any`
// when `two` comes first, the displayed type is `string`, but the diagnostic will always correctly find that it's string
three.a = 'foo2';
>three.a = 'foo2' : "foo2"
>three.a : any
>three : { readonly a: string; }
>a : any
>'foo2' : "foo2"
return three;
>three : { readonly a: string; }
}
=== tests/cases/compiler/three.ts ===
export {};
// When the readonly type is declared first, the unioned type of `three` in `doSomething` is always treated as readonly by the compiler
const one: { readonly a: string } = { a: 'one' };
>one : { readonly a: string; }
>a : string
>{ a: 'one' } : { a: string; }
>a : string
>'one' : "one"
const two: { a: string } = { a: 'two' };
>two : { a: string; }
>a : string
>{ a: 'two' } : { a: string; }
>a : string
>'two' : "two"
function doSomething(condition: boolean) {
>doSomething : (condition: boolean) => { readonly a: string; }
>condition : boolean
// when `one` comes first in the conditional check, the return type of `doSomething` is inferred as `a` is readonly, but `a` is
// only treated as readonly (i.e. it will produce a diagnostic if you try to assign to it) based on the order of declarations of `one` and `two` above
const three = (condition) ? one : two;
>three : { readonly a: string; }
>(condition) ? one : two : { readonly a: string; }
>(condition) : boolean
>condition : boolean
>one : { readonly a: string; }
>two : { a: string; }
three.a = 'foo';
>three.a = 'foo' : "foo"
>three.a : any
>three : { readonly a: string; }
>a : any
>'foo' : "foo"
// the inferred (displayed?) type of `a` also depends on the order of the condition above. When `one` comes first, the displayed type is `any`
// when `two` comes first, the displayed type is `string`, but the diagnostic will always correctly find that it's string
three.a = 'foo2';
>three.a = 'foo2' : "foo2"
>three.a : any
>three : { readonly a: string; }
>a : any
>'foo2' : "foo2"
return three;
>three : { readonly a: string; }
}
=== tests/cases/compiler/four.ts ===
export {};
// When the readonly type is declared first, the unioned type of `three` in `doSomething` is always treated as readonly by the compiler
const one: { readonly a: string } = { a: 'one' };
>one : { readonly a: string; }
>a : string
>{ a: 'one' } : { a: string; }
>a : string
>'one' : "one"
const two: { a: string } = { a: 'two' };
>two : { a: string; }
>a : string
>{ a: 'two' } : { a: string; }
>a : string
>'two' : "two"
function doSomething(condition: boolean) {
>doSomething : (condition: boolean) => { readonly a: string; }
>condition : boolean
// when `two` comes first in the conditional check, the return type of `doSomething` is inferred as not readonly but produces the same diagnostics as above
// based on the declaration order of `one` and `two`
const three = (condition) ? two : one;
>three : { readonly a: string; }
>(condition) ? two : one : { readonly a: string; }
>(condition) : boolean
>condition : boolean
>two : { a: string; }
>one : { readonly a: string; }
three.a = 'foo';
>three.a = 'foo' : "foo"
>three.a : any
>three : { readonly a: string; }
>a : any
>'foo' : "foo"
// the inferred (displayed?) type of `a` also depends on the order of the condition above. When `one` comes first, the displayed type is `any`
// when `two` comes first, the displayed type is `string`, but the diagnostic will always correctly find that it's string
three.a = 'foo2';
>three.a = 'foo2' : "foo2"
>three.a : any
>three : { readonly a: string; }
>a : any
>'foo2' : "foo2"
return three;
>three : { readonly a: string; }
}
@@ -0,0 +1,79 @@
// @strict: true
// @filename: one.ts
export {};
// When the non-readonly type is declared first, the unioned type of `three` in `doSomething` is never treated as readonly
const two: { a: string } = { a: 'two' };
const one: { readonly a: string } = { a: 'one' };
function doSomething(condition: boolean) {
// when `one` comes first in the conditional check, the return type of `doSomething` is inferred as `a` is readonly, but `a` is
// only treated as readonly (i.e. it will produce a diagnostic if you try to assign to it) based on the order of declarations of `one` and `two` above
const three = (condition) ? one : two;
three.a = 'foo';
// the inferred (displayed?) type of `a` also depends on the order of the condition above. When `one` comes first, the displayed type is `any`
// when `two` comes first, the displayed type is `string`, but the diagnostic will always correctly find that it's string
three.a = 'foo2';
return three;
}
// @filename: two.ts
export {};
// When the non-readonly type is declared first, the unioned type of `three` in `doSomething` is never treated as readonly
const two: { a: string } = { a: 'two' };
const one: { readonly a: string } = { a: 'one' };
function doSomething(condition: boolean) {
// when `two` comes first in the conditional check, the return type of `doSomething` is inferred as not readonly but produces the same diagnostics as above
// based on the declaration order of `one` and `two`
const three = (condition) ? two : one;
three.a = 'foo';
// the inferred (displayed?) type of `a` also depends on the order of the condition above. When `one` comes first, the displayed type is `any`
// when `two` comes first, the displayed type is `string`, but the diagnostic will always correctly find that it's string
three.a = 'foo2';
return three;
}
// @filename: three.ts
export {};
// When the readonly type is declared first, the unioned type of `three` in `doSomething` is always treated as readonly by the compiler
const one: { readonly a: string } = { a: 'one' };
const two: { a: string } = { a: 'two' };
function doSomething(condition: boolean) {
// when `one` comes first in the conditional check, the return type of `doSomething` is inferred as `a` is readonly, but `a` is
// only treated as readonly (i.e. it will produce a diagnostic if you try to assign to it) based on the order of declarations of `one` and `two` above
const three = (condition) ? one : two;
three.a = 'foo';
// the inferred (displayed?) type of `a` also depends on the order of the condition above. When `one` comes first, the displayed type is `any`
// when `two` comes first, the displayed type is `string`, but the diagnostic will always correctly find that it's string
three.a = 'foo2';
return three;
}
// @filename: four.ts
export {};
// When the readonly type is declared first, the unioned type of `three` in `doSomething` is always treated as readonly by the compiler
const one: { readonly a: string } = { a: 'one' };
const two: { a: string } = { a: 'two' };
function doSomething(condition: boolean) {
// when `two` comes first in the conditional check, the return type of `doSomething` is inferred as not readonly but produces the same diagnostics as above
// based on the declaration order of `one` and `two`
const three = (condition) ? two : one;
three.a = 'foo';
// the inferred (displayed?) type of `a` also depends on the order of the condition above. When `one` comes first, the displayed type is `any`
// when `two` comes first, the displayed type is `string`, but the diagnostic will always correctly find that it's string
three.a = 'foo2';
return three;
}