Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add type-guard for void #1806

Closed
fdecampredon opened this issue Jan 26, 2015 · 13 comments
Closed

add type-guard for void #1806

fdecampredon opened this issue Jan 26, 2015 · 13 comments
Labels
Duplicate An existing issue was already created Suggestion An idea for TypeScript

Comments

@fdecampredon
Copy link

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
@danquirk
Copy link
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.

@fdecampredon
Copy link
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.

@danquirk
Copy link
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?

@fdecampredon
Copy link
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.

@jbondc
Copy link
Contributor

jbondc commented Feb 3, 2015

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

@jbondc
Copy link
Contributor

jbondc commented Feb 4, 2015

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
   }
}

@mhegazy mhegazy added the Suggestion An idea for TypeScript label Mar 24, 2015
@mhegazy
Copy link
Contributor

mhegazy commented Sep 22, 2015

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.

@mhegazy
Copy link
Contributor

mhegazy commented Sep 22, 2015

so what would be the use cases here?

@mhegazy
Copy link
Contributor

mhegazy commented Sep 30, 2015

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

@jameskeane
Copy link
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.

@danquirk
Copy link
Member

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

@jameskeane
Copy link
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.

@mhegazy
Copy link
Contributor

mhegazy commented Dec 9, 2015

looks like a duplicate of #3076.

@mhegazy mhegazy closed this as completed Dec 9, 2015
@mhegazy mhegazy added the Duplicate An existing issue was already created label Dec 9, 2015
@microsoft microsoft locked and limited conversation to collaborators Jun 18, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Duplicate An existing issue was already created Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

5 participants