Repository navigation
Not assignable Type #34967
Description
Activity
To me, Type
[string | undefined, string | undefined]is assignable to type[string, undefined] | [undefined, string] | [string, string] | [undefined, undefined].Except, it isn't.
declare const a : [ string | undefined, string | undefined ]; //Assume this is okay const b : ( | [string, undefined] | [undefined, string] | [string, string] | [undefined, undefined] ) = a; if (b[0] == undefined) { //b is now narrowed to //[undefined, string] | [undefined, undefined] a[0] = "hello, world"; //this is allowed console.log(b[0]); //"hello, world" }
jack-williams commented
on Nov 7, 2019 CollaboratorMore actionsThis feels like a bug. As per this comment, recent changes to discriminant assignment should probably handle this. For example, this works:
const message: string = 'hello world'; console.log(message); const b = message ? message : undefined; const c = message ? message : undefined; interface Pair<L, R> { 0: L, 1: R; } const a: Pair<string, undefined> | Pair<undefined, undefined> | Pair<string, string> | Pair<undefined, string> = [b, c];
Sounds like a bad idea to allow the assignment, since it's unsound.
Somewhat related to #33205
I keep telling everyone assignments like these aren't sound and shouldn't be allowed but it's like I'm talking to the wind >.>
And everyone just goes on like I haven't said anythingjack-williams commented
on Nov 7, 2019 CollaboratorMore actionsI keep telling everyone assignments like these aren't sound and shouldn't be allowed but it's like I'm talking to the wind >.>
And everyone just goes on like I haven't said anythingTypeScript has always allowed covariant references, much like many other languages, which are known to be unsound in the presence of mutation and aliasing.
Except, in this case, it's a contravariant assigment, isn't it?
We're trying to assign
string|undefinedtostring, orstring|undefinedtoundefineddeclare const a: (string | undefined)[]; //Fails because contravariant assignment const b: string[] = a; declare const c: (string | undefined)[]; //Fails because contravariant assignment const d: undefined[] = c; declare const e: (string | undefined)[]; //Fails because contravariant assignment const f: (string[]) | (undefined[]) = e; declare const g: [string | undefined]; //Fails because contravariant assignment const h: [string] = g; declare const i: [string | undefined]; //Fails because contravariant assignment const j: [undefined] = i; declare const k: [string | undefined]; //Fails because contravariant assignment const l: [string]|[undefined] = k;
Also related,
declare const a : [ string | undefined, string | undefined ]; //This satisfies the type checker const b : ( | [string, undefined] | [undefined, string] | [string, string] | [undefined, undefined] ) = ( a[0] == undefined ? ( a[1] == undefined ? [a[0], a[1]] : [a[0], a[1]] ) : ( a[1] == undefined ? [a[0], a[1]] : [a[0], a[1]] ) ); //Would be nice if this satisfied the type checker //New object literal should be okay const c : ( | [string, undefined] | [undefined, string] | [string, string] | [undefined, undefined] ) = ( [a[0], a[1]] ); //Would be nice if this satisfied the type checker //New object literal should be okay const d : ( | [string, undefined] | [undefined, string] | [string, string] | [undefined, undefined] ) = ( [...a] );
jack-williams commented
on Nov 7, 2019 CollaboratorMore actionsIt's covariant:
[string | undefined, string | undefined]is a subtype of[string, undefined] | [undefined, string] | [string, string] | [undefined, undefined]because the set of values denoted by the former are a subset of the values denoted by the latter.Syntax directed subtyping algorithms commonly suffer from the problem that they can't prove this relation using the normal decomposition rules, exactly because you decompose into things that try to related
string | undefinedtostring.If mutations are disallowed, I agree with you about the former being a subset of the latter.
If mutations are allowed, then I disagree.But I guess TS pretends mutations don't happen.
Given a value
xof type[string|undefined, string|undefined], you can always change the value ofx[0]to astring, or change it toundefined.But,
- Given a value
yof type[string, undefined], you cannot changey[0]toundefined. - Given a value
yof type[undefined, string], you cannot changey[0]tostring. - Given a value
yof type[string, string], you cannot changey[0]toundefined. - Given a value
yof type[undefined, undefined], you cannot changey[0]tostring.
So, I probably should not consider mutations, since TS pretends they don't happen (in general), but it's hard for me to do so =x
- Given a value
RyanCavanaugh commented
on Nov 8, 2019 MemberMore actionsRon Buckton (@rbuckton) n.b. there's a typo (missing the
[undefined, undefined]case in the OP) but this looks like a case that should have been handled by your PR to look for matching unions- addedRescheduledThis issue was previously scheduled to an earlier milestoneThis issue was previously scheduled to an earlier milestone
on Aug 31, 2020 It looks like this was fixed by #39393, and has been working since 4.0.
- addedFixedA PR has been merged for this issueA PR has been merged for this issue
on Dec 16, 2020 - locked as resolved and limited conversation to collaborators
on Oct 21, 2025
TypeScript 3.7.2
Playground link
Compiler Options:
{ "compilerOptions": { "noImplicitAny": true, "strictNullChecks": true, "strictFunctionTypes": true, "strictPropertyInitialization": true, "strictBindCallApply": true, "noImplicitThis": true, "noImplicitReturns": true, "useDefineForClassFields": false, "alwaysStrict": true, "allowUnreachableCode": false, "allowUnusedLabels": false, "downlevelIteration": false, "noEmitHelpers": false, "noLib": false, "noStrictGenericChecks": false, "noUnusedLocals": false, "noUnusedParameters": false, "esModuleInterop": true, "preserveConstEnums": false, "removeComments": false, "skipLibCheck": false, "checkJs": false, "allowJs": false, "experimentalDecorators": false, "emitDecoratorMetadata": false, "target": "ES2017", "module": "ESNext" } }Input:
Output:
Expected behavior:
To me, Type
[string | undefined, string | undefined]is assignable to type[string, undefined] | [undefined, string] | [string, string] | [undefined, undefined].