Skip to content
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

Assignability should check an index signature or rest type of the source against optional properties of the target. #27591

Closed
wants to merge 11 commits into from
29 changes: 28 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16494,13 +16494,40 @@ namespace ts {
const name = targetProp.escapedName;
if (!(targetProp.flags & SymbolFlags.Prototype) && (!numericNamesOnly || isNumericLiteralName(name) || name === "length")) {
const sourceProp = getPropertyOfType(source, name);
if (sourceProp && sourceProp !== targetProp) {
if (sourceProp === targetProp) {
continue;
}
if (sourceProp) {
const related = propertyRelatedTo(source, target, sourceProp, targetProp, getTypeOfSymbol, reportErrors, intersectionState);
if (!related) {
return Ternary.False;
}
result &= related;
}
else if (!targetProp.valueDeclaration || !isPrivateIdentifierPropertyDeclaration(targetProp.valueDeclaration)) {
Debug.assert(!!(targetProp.flags & SymbolFlags.Optional));
let sourcePropType, diagnostic;
if (isTupleType(source)) {
sourcePropType = isNumericLiteralName(targetProp.escapedName) && +targetProp.escapedName >= 0 ? getRestTypeOfTupleType(source) : undefined;
diagnostic = Diagnostics.Rest_element_type_is_incompatible_with_property_0;
}
else {
sourcePropType =
isNumericLiteralName(targetProp.escapedName) && getIndexTypeOfType(source, IndexKind.Number) ||
getIndexTypeOfType(source, IndexKind.String);
diagnostic = Diagnostics.Index_signature_is_incompatible_with_property_0;
}
if (sourcePropType) {
const related = isRelatedTo(sourcePropType, getTypeOfSymbol(targetProp), reportErrors);
if (!related) {
if (reportErrors) {
reportError(diagnostic, symbolToString(targetProp));
}
return Ternary.False;
}
result &= related;
}
}
}
}
return result;
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1196,15 +1196,15 @@ namespace ts {
export function parseCommandLineWorker(
diagnostics: ParseCommandLineWorkerDiagnostics,
commandLine: readonly string[],
readFile?: (path: string) => string | undefined) {
readFile?: (path: string) => string | undefined): ParsedCommandLine {
const options = {} as OptionsBase;
let watchOptions: WatchOptions | undefined;
const fileNames: string[] = [];
const errors: Diagnostic[] = [];

parseStrings(commandLine);
return {
options,
options: options as CompilerOptions,
watchOptions,
fileNames,
errors
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2907,6 +2907,14 @@
"category": "Error",
"code": 2784
},
"Index signature is incompatible with property '{0}'.": {
"category": "Error",
"code": 2785
},
"Rest element type is incompatible with property '{0}'.": {
"category": "Error",
"code": 2786
},

"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
Expand Down
2 changes: 1 addition & 1 deletion src/server/editorServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2641,7 +2641,7 @@ namespace ts.server {
}

if (args.watchOptions) {
this.hostConfiguration.watchOptions = convertWatchOptions(args.watchOptions);
this.hostConfiguration.watchOptions = convertWatchOptions(args.watchOptions as any);
this.logger.info(`Host watch options changed to ${JSON.stringify(this.hostConfiguration.watchOptions)}, it will be take effect for next watches.`);
}
}
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/APISample_WatchWithOwnWatchHost.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ function watchMain() {
readDirectory: ts.sys.readDirectory,
realpath: ts.sys.realpath,

watchFile: ts.sys.watchFile!,
watchDirectory: ts.sys.watchDirectory!,
watchFile: ts.sys.watchFile! as any,
Copy link
Member

Choose a reason for hiding this comment

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

what?

Copy link
Member

Choose a reason for hiding this comment

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

ts.sys.watchFile is a function with a final parameter of type WatchOptions, which has a property of type WatchFileKind, a numeric enum. CompilerHost's watchFile, the target signature, has a final parameter of type CompilerOptions, which does not have a matching property, but does have an index signature that includes number. But number isn't assignable to an enum.

This is technically sound, because people could call watchFile with an options bug containing any number at all (or a lot of other types), but ts.sys.watchFile can only handle WatchFileKind on certain of its properties.

However, I don't know how likely this error is to happen in practise. We haven't had trouble as far as we know.

watchDirectory: ts.sys.watchDirectory! as any,
createProgram: ts.createAbstractBuilder
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
tests/cases/compiler/assignIndexSignatureToOptionalProperty.ts(17,5): error TS2322: Type 'Bar' is not assignable to type 'Foo'.
Index signature is incompatible with property 'b'.
Type 'number' is not assignable to type 'string'.


==== tests/cases/compiler/assignIndexSignatureToOptionalProperty.ts (1 errors) ====
// #27144

interface Foo {
a: number;
b?: string;
}
interface Foo2 {
a: number;
b?: number;
}
interface Bar {
a: number;
[n: string]: number;
}
let b: Bar = { a: 42, b: 43 };
// Error, index signature does not match optional property `b`
let f: Foo = b;
~
!!! error TS2322: Type 'Bar' is not assignable to type 'Foo'.
!!! error TS2322: Index signature is incompatible with property 'b'.
!!! error TS2322: Type 'number' is not assignable to type 'string'.
// OK
let f2: Foo2 = b;

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//// [assignIndexSignatureToOptionalProperty.ts]
// #27144

interface Foo {
a: number;
b?: string;
}
interface Foo2 {
a: number;
b?: number;
}
interface Bar {
a: number;
[n: string]: number;
}
let b: Bar = { a: 42, b: 43 };
// Error, index signature does not match optional property `b`
let f: Foo = b;
// OK
let f2: Foo2 = b;


//// [assignIndexSignatureToOptionalProperty.js]
// #27144
var b = { a: 42, b: 43 };
// Error, index signature does not match optional property `b`
var f = b;
// OK
var f2 = b;
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
=== tests/cases/compiler/assignIndexSignatureToOptionalProperty.ts ===
// #27144

interface Foo {
>Foo : Symbol(Foo, Decl(assignIndexSignatureToOptionalProperty.ts, 0, 0))

a: number;
>a : Symbol(Foo.a, Decl(assignIndexSignatureToOptionalProperty.ts, 2, 15))

b?: string;
>b : Symbol(Foo.b, Decl(assignIndexSignatureToOptionalProperty.ts, 3, 14))
}
interface Foo2 {
>Foo2 : Symbol(Foo2, Decl(assignIndexSignatureToOptionalProperty.ts, 5, 1))

a: number;
>a : Symbol(Foo2.a, Decl(assignIndexSignatureToOptionalProperty.ts, 6, 16))

b?: number;
>b : Symbol(Foo2.b, Decl(assignIndexSignatureToOptionalProperty.ts, 7, 14))
}
interface Bar {
>Bar : Symbol(Bar, Decl(assignIndexSignatureToOptionalProperty.ts, 9, 1))

a: number;
>a : Symbol(Bar.a, Decl(assignIndexSignatureToOptionalProperty.ts, 10, 15))

[n: string]: number;
>n : Symbol(n, Decl(assignIndexSignatureToOptionalProperty.ts, 12, 5))
}
let b: Bar = { a: 42, b: 43 };
>b : Symbol(b, Decl(assignIndexSignatureToOptionalProperty.ts, 14, 3))
>Bar : Symbol(Bar, Decl(assignIndexSignatureToOptionalProperty.ts, 9, 1))
>a : Symbol(a, Decl(assignIndexSignatureToOptionalProperty.ts, 14, 14))
>b : Symbol(b, Decl(assignIndexSignatureToOptionalProperty.ts, 14, 21))

// Error, index signature does not match optional property `b`
let f: Foo = b;
>f : Symbol(f, Decl(assignIndexSignatureToOptionalProperty.ts, 16, 3))
>Foo : Symbol(Foo, Decl(assignIndexSignatureToOptionalProperty.ts, 0, 0))
>b : Symbol(b, Decl(assignIndexSignatureToOptionalProperty.ts, 14, 3))

// OK
let f2: Foo2 = b;
>f2 : Symbol(f2, Decl(assignIndexSignatureToOptionalProperty.ts, 18, 3))
>Foo2 : Symbol(Foo2, Decl(assignIndexSignatureToOptionalProperty.ts, 5, 1))
>b : Symbol(b, Decl(assignIndexSignatureToOptionalProperty.ts, 14, 3))

Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
=== tests/cases/compiler/assignIndexSignatureToOptionalProperty.ts ===
// #27144

interface Foo {
a: number;
>a : number

b?: string;
>b : string
}
interface Foo2 {
a: number;
>a : number

b?: number;
>b : number
}
interface Bar {
a: number;
>a : number

[n: string]: number;
>n : string
}
let b: Bar = { a: 42, b: 43 };
>b : Bar
>{ a: 42, b: 43 } : { a: number; b: number; }
>a : number
>42 : 42
>b : number
>43 : 43

// Error, index signature does not match optional property `b`
let f: Foo = b;
>f : Foo
>b : Bar

// OK
let f2: Foo2 = b;
>f2 : Foo2
>b : Bar

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
tests/cases/compiler/assignRestElementToOptionalProperty.ts(5,5): error TS2322: Type '[number, ...string[]]' is not assignable to type '[number, number?, ...string[]]'.
Rest element type is incompatible with property '1'.
Type 'string' is not assignable to type 'number'.


==== tests/cases/compiler/assignRestElementToOptionalProperty.ts (1 errors) ====
// Inspired by #27144

let t: [number, ...string[]];
// Error, rest type of `t` does not match element 1 of `t2`
let t2: [number, number?, ...string[]] = t;
~~
!!! error TS2322: Type '[number, ...string[]]' is not assignable to type '[number, number?, ...string[]]'.
!!! error TS2322: Rest element type is incompatible with property '1'.
!!! error TS2322: Type 'string' is not assignable to type 'number'.
// OK
let t3: [number, string?, ...string[]] = t;

17 changes: 17 additions & 0 deletions tests/baselines/reference/assignRestElementToOptionalProperty.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//// [assignRestElementToOptionalProperty.ts]
// Inspired by #27144

let t: [number, ...string[]];
// Error, rest type of `t` does not match element 1 of `t2`
let t2: [number, number?, ...string[]] = t;
// OK
let t3: [number, string?, ...string[]] = t;


//// [assignRestElementToOptionalProperty.js]
// Inspired by #27144
var t;
// Error, rest type of `t` does not match element 1 of `t2`
var t2 = t;
// OK
var t3 = t;
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
=== tests/cases/compiler/assignRestElementToOptionalProperty.ts ===
// Inspired by #27144

let t: [number, ...string[]];
>t : Symbol(t, Decl(assignRestElementToOptionalProperty.ts, 2, 3))

// Error, rest type of `t` does not match element 1 of `t2`
let t2: [number, number?, ...string[]] = t;
>t2 : Symbol(t2, Decl(assignRestElementToOptionalProperty.ts, 4, 3))
>t : Symbol(t, Decl(assignRestElementToOptionalProperty.ts, 2, 3))

// OK
let t3: [number, string?, ...string[]] = t;
>t3 : Symbol(t3, Decl(assignRestElementToOptionalProperty.ts, 6, 3))
>t : Symbol(t, Decl(assignRestElementToOptionalProperty.ts, 2, 3))

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
=== tests/cases/compiler/assignRestElementToOptionalProperty.ts ===
// Inspired by #27144

let t: [number, ...string[]];
>t : [number, ...string[]]

// Error, rest type of `t` does not match element 1 of `t2`
let t2: [number, number?, ...string[]] = t;
>t2 : [number, number?, ...string[]]
>t : [number, ...string[]]

// OK
let t3: [number, string?, ...string[]] = t;
>t3 : [number, string?, ...string[]]
>t : [number, ...string[]]

4 changes: 2 additions & 2 deletions tests/cases/compiler/APISample_WatchWithOwnWatchHost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ function watchMain() {
readDirectory: ts.sys.readDirectory,
realpath: ts.sys.realpath,

watchFile: ts.sys.watchFile!,
watchDirectory: ts.sys.watchDirectory!,
watchFile: ts.sys.watchFile! as any,
watchDirectory: ts.sys.watchDirectory! as any,
createProgram: ts.createAbstractBuilder
};

Expand Down
19 changes: 19 additions & 0 deletions tests/cases/compiler/assignIndexSignatureToOptionalProperty.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// #27144

interface Foo {
a: number;
b?: string;
}
interface Foo2 {
a: number;
b?: number;
}
interface Bar {
a: number;
[n: string]: number;
}
let b: Bar = { a: 42, b: 43 };
// Error, index signature does not match optional property `b`
let f: Foo = b;
// OK
let f2: Foo2 = b;
7 changes: 7 additions & 0 deletions tests/cases/compiler/assignRestElementToOptionalProperty.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Inspired by #27144

let t: [number, ...string[]];
// Error, rest type of `t` does not match element 1 of `t2`
let t2: [number, number?, ...string[]] = t;
// OK
let t3: [number, string?, ...string[]] = t;