Skip to content

narrowing doesn't work for literal strings in switch when united with unconstrained string #10417

Description

nightly build, August 18, 2016

function useA(value: 'a'): void { }
function useB(value: 'b'): void { }
function useSomethingElse(value: string): void { }
declare var kind: string | 'a' | 'b';
switch (kind) {
    case 'a': useA(kind); break; // expected kind to be 'a', actual 'a' | string
    case 'b': useB(kind); break;
    default: useSomethingElse(kind); break;
}

Activity

  1. RyanCavanaugh commented on Aug 18, 2016

    @RyanCavanaugh
    Member

    Side question: what was your intent declaring something string | 'a' | 'b' ?

  2. zpdDG4gta8XKpMCd commented on Aug 18, 2016

    @zpdDG4gta8XKpMCd
    Author

    example1: DB guys introduced a new possible value to a set of well-known values, without letting anyone know, this new case has to be treated separately as a failure/default case/you name it

    example2: this one is lengthy, gradual open-ended narrowing via composing resolving functions

    SomeStuff is a union of One and Another why not to take advantage of their resolver functions

    type One = 'a' | 'b';
    interface ViaOne<r> {
       caseOfA: (value: 'a') => r;
       caseOfB: (value: 'b') => r;
    }
    function viaOne<r>(kind: One | string, via: ViaOne<r>) : Optional<r> {
       switch (kind) {
           case 'a': return someFrom(via.caseOfA(kind));
           case 'b': return someFrom(via.caseOfB(kind));
           default: return none;
       }
    }
    type Another = 'c' | 'd';
    interface ViaAnother<r> {
        caseOfC: (value: 'c') => r;
        caseOfD: (value: 'd') => r;
    }
    function viaAnother<r>(kind: Another | string, via: ViaAnother<r>): Optional<r> {
       switch (kind) {
           case 'c': return someFrom(via.caseOfA(kind));
           case 'd': return someFrom(via.caseOfB(kind));
           default: return none;
       }
    }
    
    type CrazyStuff = One | Another;
    interface ViaCrazyStuff<r> extends ViaOne<r>, ViaAnother<r> {}
    
    function viaCrazyStuff<r>(value: CrazyStuff, via: ViaCrazyStuff<r>): Optional<r> {
       const tryOne = viaOne(value, via);
       if (isSome(tryOne)) {
           return tryOne;
       } else {
           return viaAnother(value, via);
       }
    }
  3. ahejlsberg commented on Aug 18, 2016

    @ahejlsberg
    Member

    This is working as intended, so I'm changing the label to a suggestion. We currently only narrow in a switch statement when the type of the switch expression is a union of unit types. In your example, there's really no difference between the type string and string | 'a' | 'b'.

  4. zpdDG4gta8XKpMCd commented on Aug 19, 2016

    @zpdDG4gta8XKpMCd
    Author

    fyi, there is a workaround using T extends string rather than plain string

    function useA(value: 'a'): void { }
    function useB(value: 'b'): void { }
    function useSomethingElse(value: string): void { }
    
    function test<c extends string>(kind: 'a' | 'b' | c) {
        switch (kind) {
            case 'a': useA(kind); break;
            case 'b': useB(kind); break;
            default: useSomethingElse(kind);
        }
    }
  5. RyanCavanaugh commented on Aug 19, 2016

    @RyanCavanaugh
    Member

    Anders Hejlsberg (@ahejlsberg) why wouldn't we narrow whenever a literal value matches a literal in a union?

  6. added
    FixedA PR has been merged for this issue
    and removed on Oct 14, 2016
  7. locked and limited conversation to collaborators on Jun 19, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    FixedA PR has been merged for this issueSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions