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

add type-guard for void #1806

Description

There is a type that is quite interesting in typescript void | {}, this type has some unique behavior, everything can be assigned to this type, but the compiler does not infer any property for this type unlike for {} that inherits the basic object properties, the difference with {} is subtile but still pretty interesting:

function func1(val: {} ) {
    val.hasOwnProperty('hello'); // no error
}
function func2(val: {} | void) {
    val.hasOwnProperty('hello');  // error:  Property 'hasOwnProperty' does not exist on type 'void | {}'.
}

However there is not typeguard for void, it could be interesting to add some type guards for this case:

  • typeof val !== 'undefined'
  • val !== undefined
  • val != undefined
  • val != null

Activity

  1. danquirk commented on Jan 26, 2015

    @danquirk
    Member

    My impression is this is an attempt to hack in making an implementation require null checks. This probably isn't the route we'd go if we want to enable that sort of feature (which I understand the desire for). As far the unique behavior of void note this bit from the language spec on void that has persisted for many versions now (I thought we'd removed this note):

    NOTE: We might consider disallowing declaring variables of type Void as they serve no useful purpose. However, because Void is permitted as a type argument to a generic type or function it is not feasible to disallow Void properties or parameters.

  2. fdecampredon commented on Jan 26, 2015

    @fdecampredon
    Author

    The point is not really to force null check (and in my example the typeguard that compare to undefined would not prevent us from null) but to have a better subtype than {}, {} which still inherit from the Object behavior, while this feature is not invaluable I still like the effect.

  3. danquirk commented on Jan 27, 2015

    @danquirk
    Member

    So how would this differ from your proposal for object as a primitive? #1809 It seems like you either want to type func2's argument as object or be able to use {} | void for a similar effect?

  4. fdecampredon commented on Jan 27, 2015

    @fdecampredon
    Author

    It's quite different, let me give an example:

    type mixed = {} | void;
    
    function acceptMixed(arg: mixed): void {
      if (typeof arg === 'string') {
          arg.split(...); //valid
      }
      arg.hasOwnProperty('hello') // error
      console.log(arg) // no error 
    }
    
    acceptMixed('string') // no error 
    acceptMixed(3) //no error

    anything can be assigned to mixed ({} | void), but unlike {} the compiler does not implies any method from Object for this type , allowing a certain safety (for example such method would accept result Object.create(null) with safety).
    Note that this is actually the 1.4 typescript behavior.

    the object type is quite different, every primitive type (except any,null, void) are not assignable to object, and like for other type the compiler infer that it has Object prototype methods :

    function acceptObject(arg: object): void {
      arg.hasOwnProperty('hello') // no error
    }
    
    acceptMixed('string') // error 
    acceptMixed(3) // error

    If we added type guard for 'undefined', we could narrow type to {} instead of narrowing it to a subtype like string etc ...

    However like I said that's not something that I absolutely want, I just think that the type mixed itself has quite interesting property, and it's more a nice trick than anything else.

  5. jbondc commented on Feb 3, 2015

    @jbondc
    Contributor

    Agree there should be a nullish type guard:

    function accept(arg: string): void {
      if (arg == null) {
          // arg narrows to void
      } else {
           // arg narrows to string!null!undefined
           arg = null // error
       }
    }

    Some related thoughts at the bottom of #1892

  6. jbondc commented on Feb 4, 2015

    @jbondc
    Contributor

    The reasoning for removing 'void' is it's already a type that is included in every union type.

    type uniona = string | number; // infers string|number|void
    type unionb = string | number | void; // void here is *not* needed since 'string | number' -> implies string|number|void
    
    function accept(arg: string): void {
      if (arg == null) {
          // arg narrows to void
      } else {
           // arg narrows to string!null!undefined
           arg = null 
       }
    }
     function a(arg: uniona): void {
       'use strict'
      if (arg == null) {
          // arg narrows to void
      } else {
           // arg narrows to string|number&!null!undefined
           arg = null // error
       }
    }
     function b(arg: unionb): void {
      if (arg == null) {
          // arg narrows to void
      } else {
           // arg narrows to string|number&!null!undefined
           arg = null
       }
    }
  7. mhegazy commented on Sep 22, 2015

    @mhegazy
    Contributor

    I would see this as part of nullable/non-nullable types support (#185); non-nullable types is something that we did not dismiss, but it is not on the roadmap for the short term.

  8. mhegazy commented on Sep 22, 2015

    @mhegazy
    Contributor

    so what would be the use cases here?

  9. mhegazy commented on Sep 30, 2015

    @mhegazy
    Contributor

    Dan Quirk (@danquirk)'s reply in #1806 (comment) would be my opinion as well. I do not think this is the intention of void as a type; and I have not seen compelling use cases so far. I would say the issue should be closed.

  10. jameskeane commented on Oct 15, 2015

    @jameskeane
    Contributor

    Without a void type guard, how would you satisfy this:

    declare var k: (() => string)|void;
    
    if (k !== null || k !== undefined) k();
    if (typeof k !== 'undefined') k();
    if (typeof k == 'Function') k();

    Every attempt to invoke k above fails with: Cannot invoke an expression whose type lacks a call signature.

  11. danquirk commented on Oct 15, 2015

    @danquirk
    Member

    That seems like exactly the type of pattern I described in #1806 (comment)

  12. jameskeane commented on Oct 15, 2015

    @jameskeane
    Contributor

    I think you are right, the Cannot invoke expression ... error message doesn't specify if the type can be reduced to a function or not (too general vs. not a function).

    Unlike my former example where the type can be reduced, the following (if I am not mistaken) can not be reduced because generics do not apply arguments to specialized overloads?

    declare function foo(name: 'bar'): (i: number) => string;
    declare function foo(name: 'manchu'): (key: string) => string;
    declare function foo(name: string): void;
    
    foo('bar')(1);            // no error, returns string
    foo('manchu')('wok');     // no error, returns string
    foo('bar')('?');          // "Arg of type string ... not a number"
    foo('manchu')(1);         // "Arg of type number ... not a string" 
    foo('?')();               // "Cannot invoke expr ... no call sig"
    
    function factory<N extends string, T>(name: N, arg: T): string {
      return foo(name)(arg);  // "Cannot invoke expr ... no call sig"
    }

    It does seem quite contrived, but I would like to be able to guarantee (at compile time) that calling factory with bar requires a number, with manchu requires a string and anything else is a compile error.

  13. mhegazy commented on Dec 9, 2015

    @mhegazy
    Contributor

    looks like a duplicate of #3076.

  14. locked and limited conversation to collaborators on Jun 18, 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 createdSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions