Skip to content

Add support for defining: string - 'a' (string minus string literal type) #12182

Closed

Description

@normalser

#11587 allows us to do this:

function test(a: 'a' | string) {
    if (a == 'a') { 
        a // <- type: 'a'
    }
}

It would be great if the following could also work:

type Z = {type:'a',a:string}|{type:string,b:string}
function test(a: Z) {
    if (a.type == 'a') { 
        a // <- type: Z but ideally if it could be {type:'a',a:string}
    }
}

Activity

  1. ahejlsberg commented on Nov 11, 2016

    @ahejlsberg
    Member

    The proposed narrowing of Z wouldn't be correct because a { type: string, b: string } could very well have a type property with the value 'a'. The only way it would be correct is if you could somehow express a type string - 'a' (i.e. a string with any value but 'a'), but we don't have that capability.

    BTW, #11587 is about narrowing string to a literal type, not about narrowing something like 'a' | string. In general it is not meaningful to have types like 'a' | string because it is really the same as just string and the compiler will reduce it to that the minute it performs subtype reduction.

  2. changed the title [-]Narrow string and number types in literal equality checks for nested objects[/-] [+]Add support for defining: string - 'a' (string minus string literal type)[/+] on Nov 12, 2016
  3. normalser commented on Nov 12, 2016

    @normalser
    Author

    Anders Hejlsberg (@ahejlsberg) Thanks for the response.

    I wonder if we could say that 'a' should take precedence over string in case of Z, but if not then

    string - 'a' would solve our use case - so I renamed that title to reflect this feature - but if you feel it's Out of scope - feel free to close it.

    We are trying to migrate Redux actions/reducers to TS and such a catch-all action would be very useful, for example:

    interface AddToDo {
      type: 'addToDo'
      data: {
         name: string
         completed: boolean
      }
    }
    // ... - other actions
    
    interface CatchAll {
      type: string  // `string - 'addToDo'` would help here
      data: any
    }
    
    type Actions = AddToDo | CatchAll
    
    function reduce(action: Actions) {
      switch (action.type) {
         case 'addToDo': 
              action // type: Actions -> ideally: AddToDo
         break; 
    
        /// any other actions that don't have any inteface defined
        case 'someOtherAction':
           action // type: Actions -> ideally: CatchAll, so that action.data is any
        break;
        case 'someOtherAction2':
           action // type: Actions -> ideally: CatchAll, so that action.data is any
        break;
        // ... more actions
      }
    }

    Thanks

  4. aluanhaddad commented on Nov 12, 2016

    @aluanhaddad
    Contributor

    @wallverb That is somewhat strange because the "any other actions" case doesn't do what it says. It only handles values that are tagged with 'someOtherAction'. If all other values are always so tagged, then using that tag as the literal type of CatchAll.type correctly describes the behavior of reduce.

  5. normalser commented on Nov 12, 2016

    @normalser
    Author

    Aluan Haddad (@aluanhaddad) Sorry if my example was not clear enough - I updated. The thing is that there are hundreds of such redux actions that don't have any type defined for them and only handful of actions (like AddToDo) that do have them.
    Having something like this:

    type DefinedActionTypes = 'addToDo' | 'removeToDo' // | .....
    type UndefinedActionTypes = string - DefinedActions
    
    interface CatchAll {
      type: UndefiendActionTypes
      data: any
    }

    would allow to achieve that (to narrow to CatchAll), but I understand if that would be Out of Scope / no interest by anyone else

  6. aluanhaddad commented on Nov 12, 2016

    @aluanhaddad
    Contributor

    @wallverb What I mean is that you need to handle that in the default clause. If they actually have no union tag at runtime, you can write

    interface CatchAll {
      type: undefined
      data: any
    }

    and type narrowing will actually work with case (if you are using --strictNullChecks then undefined is incompatible with string).

  7. mhegazy commented on Nov 14, 2016

    @mhegazy
    Contributor

    seems like a duplicate of #7993

  8. 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

    DuplicateAn existing issue was already created

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions