镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Not assignable Type #34967

Description

@yuanfux

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:

const message: string = 'hello world';
console.log(message);

const b = message ? message : undefined;
const c = message ? message : undefined;

const a: [string, undefined] | [undefined, string] | [string, string] | [undefined, string] = [b, c];

Output:

"use strict";
const message = 'hello world';
console.log(message);
const b = message ? message : undefined;
const c = message ? message : undefined;
const a = [b, c];

Expected behavior:
To me, Type [string | undefined, string | undefined] is assignable to type [string, undefined] | [undefined, string] | [string, string] | [undefined, undefined].

Activity

  1. AnyhowStep commented on Nov 7, 2019

    @AnyhowStep
    Contributor

    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"
    }
  2. jack-williams commented on Nov 7, 2019

    @jack-williams
    Collaborator

    This 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];
  3. AnyhowStep commented on Nov 7, 2019

    @AnyhowStep
    Contributor

    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 anything

  4. jack-williams commented on Nov 7, 2019

    @jack-williams
    Collaborator

    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 anything

    TypeScript has always allowed covariant references, much like many other languages, which are known to be unsound in the presence of mutation and aliasing.

  5. AnyhowStep commented on Nov 7, 2019

    @AnyhowStep
    Contributor

    Except, in this case, it's a contravariant assigment, isn't it?

    We're trying to assign string|undefined to string, or string|undefined to undefined

    declare 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;

    Playground


    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]
    );

    Playground

  6. jack-williams commented on Nov 7, 2019

    @jack-williams
    Collaborator

    It'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 | undefined to string.

  7. AnyhowStep commented on Nov 7, 2019

    @AnyhowStep
    Contributor

    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 x of type [string|undefined, string|undefined], you can always change the value of x[0] to a string, or change it to undefined.

    But,

    • Given a value y of type [string, undefined], you cannot change y[0] to undefined.
    • Given a value y of type [undefined, string], you cannot change y[0] to string.
    • Given a value y of type [string, string], you cannot change y[0] to undefined.
    • Given a value y of type [undefined, undefined], you cannot change y[0] to string.

    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

  8. RyanCavanaugh commented on Nov 8, 2019

    @RyanCavanaugh
    Member

    Ron 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

  9. rbuckton commented on Dec 16, 2020

    @rbuckton
    Contributor

    It looks like this was fixed by #39393, and has been working since 4.0.

  10. locked as resolved and limited conversation to collaborators on Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

BugA bug in TypeScriptFixedA PR has been merged for this issueRescheduledThis issue was previously scheduled to an earlier milestone

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions