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

Suggestion for optional parameter with type 'never' #14452

Closed
ikatyang opened this issue Mar 4, 2017 · 1 comment
Closed

Suggestion for optional parameter with type 'never' #14452

ikatyang opened this issue Mar 4, 2017 · 1 comment
Labels
Duplicate An existing issue was already created

Comments

@ikatyang
Copy link
Contributor

ikatyang commented Mar 4, 2017

The type never currently just mean the function return that will never arrive or the unexpected value after type inference.

I think the type never can be more powerful.

optional parameter: using type never

Since undefined has two meaning: not existed and existed but no value, it would be better to split it out to have the clearest meaning.

  • It currently uses the ? operator to define a optional parameter:

    interface IExample {
      a?: string;
    }
    const test1: IExample = {
      a: undefined, // expected error, but passed
    };
    
    function funcExample(a?: string) {}
    funcExample(undefined); // expected error, but passed

    It is expected to be error, but it is passed because it was resolved as the following code in strictNullChecks mode:

    interface IExample {
      a?: string | undefined
    }
    function funcExample(a?: string | undefined): void

    where the undefined should not appear since it just can be string or void, so I think it should be resolved as:

    interface Example {
      a: string | never
    }
    function funcExample(a: string | never) {}

    and expect its behavior as:

    const example1: Example = {
      a: undefined; // show error message
    };
    const example2: Example = {}; // expected passed, but error now
    
    funcExample(undefined); // show error message
  • For more about never with function optional parameter:

    function neverExample(a: never) {}
    neverExample(); // expected passed, but error now

    Why dont I just use the following code?

    function neverExample() {}

    Consider the following generic class:

    abstract class Matcher<T> {
      public abstract rawMatch(target: T): boolean;
      public match(target: T): boolean {
        return this.rawMatch(target);
      }
    }
    class TestMatcher extends Matcher<never> {
      constructor(public bool: boolean) {
        super();
      }
      public rawMatch(): boolean {
        return this.bool;
      }
    }
    const result = new TestMatcher(true).match(); // expected passed, but error now

    It will be more convenient if we can use never with function in generic class.

    The only way to solve this problem now is to override the method:

    class TestMatcher extends Matcher<null> {
      constructor(public bool: boolean) {
        super();
      }
      public rawMatch(): boolean {
        return this.bool;
      }
      public match(): boolean {
        return super.rawMatch(null);
      }
    }

alias for { [key: string]: T; }

{ [key: string]: T } is a type that we use it frequently, but it looks not comfortable like number, object, etc.
I'd like to suggest to have an alias for it globally, for example:

type dictionary<T> = { [key: string]: T; };
function setData(data: dictionary<any>) { ... }
function setData(data: { [key: string]: any }) { ... }
function setData(data1: dictionary<any>, data2: dictionary<any>, something: any) { ... }
function setData(data1: { [key: string]: any }, data2: { [key: string]: any }, something: any) { ... }

disabling rules with inline comments

I don't know if this is duplicate ( issue 11051 ) or not, since the following code is 100% error in type-checking but 100% correct in logic.

Type inference is powerful, but sometimes it'll block some convenient usage.
Given the following class, the generic is consider to be a state of it:

type MapCallback<Input, Output> = (value: Input) => Output;

abstract class Querier<Target, Result> {

  public mapCallback: MapCallback<any, any>;

  public abstract rawQuery(target: Target): Result[];
  public query(target: Target): Result[] {
    const results = this.rawQuery(target);
    return results.map(this.mapCallback);
  }

  public map<MappedResult>(callback: MapCallback<Result, MappedResult>): Querier<Target, MappedResult> {
    this.mapCallback = callback;
    return <Querier<Target, MappedResult>>this; // error: cannot be converted
  }

}

The map method will change its state, for example:

class SomeQuerier extends Querier<object, number> { ... }
const someResults = 
  new SomeQuerier() // Querier<object, number>
    .map((num: number) => num.toString()) // Querier<object, string>
    .query(someObject); // string[]

So I think is there a way to provide some inline comments to disable rules, just like eslint and tslint did?

public map<MappedResult>(callback: MapCallback<Result, MappedResult>): Querier<Target, MappedResult> {
    this.mapCallback = callback;
    // tsc:disable-next-line type-convert-checking
    return <Querier<Target, MappedResult>>this;
  }

Edit: I found the following part is duplicate ( issue 10727 ), just ignore it.

Relative complement operator

We have union operator ( | ) now, from myType1 to myType2:

type myType1 = string | number;
type myType2 = myType1 | boolean;
// myType2 = string | number | boolean

But we can't do the opposite way from myType2 to myType1:

type myType2 = string | number | boolean;
type myType1 = myType2 - boolean; // we cannot do this now
// expected myType1 = string | number;

If we can have the complement operator ( - ), it will be easily to use in some Partial case (e.g. options ):

interface PartialOptions {
  a?: string;
  b?: number;
  c?: {
    c1: string;
    c2: number;
  }
}

If we want to get types within c, it is impossible to do this now since c can be undefined:

type cnames = keyof PartialOptions['c'];
// cnames = never, since PartialOptions['c'] could be undefined
type ctypes = PartialOptions['c'][cnames]; // error: Type 'never' cannot be used as an index type

The only way to get types now is to copy it manually. but we have to copy it again while PartialOptions changed:

type ctypes = string | number;

If we can use complement operator:

type EntireOptions = {
  [T in keyof PartialOptions]: PartialOptions[T] - undefined;
}
// expected EntireExmaple = {
//   a: string;
//   b: number;
//   c: {
//     c1: string;
//     c2: number;
//   }
// }
type cnames = keyof EntireOptions['c'];
// expected cnames = 'c1' | 'c2'
type ctypes = EntireOptions['c'][cnames];
// expected ctypes = string | number
@ikatyang ikatyang changed the title Suggestion for optional parameter and relative complement operator Suggestion for optional parameter and relative complement operator and generics Mar 4, 2017
@ikatyang ikatyang changed the title Suggestion for optional parameter and relative complement operator and generics Suggestion for optional parameter with type 'never' Mar 5, 2017
@mhegazy
Copy link
Contributor

mhegazy commented Mar 9, 2017

never is the bottom type, it is assignable to everything. For the distinction between undefined/missing I do not think never is the right type.

You need a type that is for read is undefined and for write is a subtype of undefined. Issue #13195 tracks doing so.

@mhegazy mhegazy added the Duplicate An existing issue was already created label Mar 9, 2017
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 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
Projects
None yet
Development

No branches or pull requests

2 participants