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

--strictFunctionTypes claims incompatible types with generics #20702

Closed
geovanisouza92 opened this issue Dec 14, 2017 · 4 comments
Closed

--strictFunctionTypes claims incompatible types with generics #20702

geovanisouza92 opened this issue Dec 14, 2017 · 4 comments
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@geovanisouza92
Copy link

geovanisouza92 commented Dec 14, 2017

TypeScript Version: 2.6.1

Code

type Direction = keyof IDirectionOptions;

interface IDirectionOptions {
  "horizontal": { foo: number; };
  "vertical": { bar: string; };
  "diagonal": { baz: boolean; };
}

// This structure works

type FnOk<D extends Direction> = () => ResultOk<D>;

type ResultOk<D extends Direction> =
  { kind: "invalid", err: Error }
  | { kind: "valid", data: IDirectionOptions[D] }
  ;

function horizontalOk(): ResultOk<"horizontal"> {
  return { kind: "valid", data: { foo: 123 } };
};

function verticalOk(): ResultOk<"vertical"> {
  if (Math.random() > 0.5) {
    return { kind: "valid", data: { bar: "foo" } };
  }
  return { kind: "invalid", err: new Error("LOL") };
}

function selectOk<D extends Direction>(direction: D): FnOk<D> {
  switch (direction) {
    case "horizontal": return horizontalOk;
    case "vertical": {
      const v = verticalOk();
      if (v.kind === "valid") {
        v.data.bar;
      }
      return verticalOk;
    }
    case "diagonal": return () => ({ kind: "invalid", err: new Error("lol") });
  }
}

// This structure doesn't

type FnErr<D extends Direction> = () => ResultErr<D>;

type ResultErr<D extends Direction> =
  { kind: "invalid", err: Error }
  | { kind: "valid", data: IData<D> }
  ;

interface IData<D extends Direction> {
  value: IDirectionOptions[D];
}

function horizontalErr(): ResultErr<"horizontal"> {
  return { kind: "valid", data: { value: { foo: 123 } } };
};

function verticalErr(): ResultErr<"vertical"> {
  if (Math.random() > 0.5) {
    return { kind: "valid", data: { value: { bar: "foo" } } };
  }
  return { kind: "invalid", err: new Error("LOL") };
}

function selectErr<D extends Direction>(direction: D): FnErr<D> {
  switch (direction) {
    case "horizontal": return horizontalErr; // Error here
    case "vertical": {
      const v = verticalErr();
      if (v.kind === "valid") {
        v.data.value.bar;
      }
      return verticalErr; // Error here
    }
    case "diagonal": return () => ({ kind: "invalid", err: new Error("lol") });
  }
}

Expected behavior:

So, When I use an generic interface, where the parameter is a keyof mapped type, inside a discriminated union (I know, I'm kinda using all advanced features in just one code; I swear it's real code), with --strictFunctionTypes enabled, it should not report any error, both using FnErr<D> or FnOk<D>.

Actual behavior:

When --strictFunctionTypes is enabled, it reports only FnErr<D> as structurally invalid.


Is this an expected behavior? If so, why?

Thanks.

@ghost
Copy link

ghost commented Dec 14, 2017

Note that const x: FnErr<"horizontal"> = horizontalErr; does work. The problem is that TypeScript doesn't realize that if direction === "horizontal", then so does D.
(Also, the parameter should be direction: D, not direction: Direction.)
A simple example:

function f<T extends "a" | "b">(s: T): T {
    switch (s) {
        case "a": return "a";
        case "b": return "b";
        default: throw new Error();
    }
}

This is probably not an easy problem to solve...

@geovanisouza92
Copy link
Author

@andy-ms yeah, const still works as noted.

About the parameter direction: Direction, I noted like that because, in my case, direction is inside another object, like interface X { direction: Direction; /... } and the parameter is x: X. I redacted just to make the example simple but similar to my code.

Your last example is much more simple and clear, thanks!

@geovanisouza92 geovanisouza92 changed the title --strictFunctionTypes claims incompatible types only when using a generic interface --strictFunctionTypes claims incompatible types with generics Dec 14, 2017
@mhegazy mhegazy added the Design Limitation Constraints of the existing architecture prevent this from being fixed label Jul 12, 2018
@mhegazy
Copy link
Contributor

mhegazy commented Jul 12, 2018

As @andy-ms noted, this is a design limitation, since the compiler can not assert the relationship of the input to the output.. a functionally similar example that would work is:

function f<T extends "a" | "b">(s: T): T {
    switch (s) {
        case "a": return s;
        case "b": return s;
        default: throw new Error();
    }
}

As for the OP, a related discussion can be found in #23132

@typescript-bot
Copy link
Collaborator

Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

3 participants