Skip to content

Inconsistency in types after type inference #24369

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

Closed
DeTeam opened this issue May 24, 2018 · 7 comments
Closed

Inconsistency in types after type inference #24369

DeTeam opened this issue May 24, 2018 · 7 comments
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@DeTeam
Copy link

DeTeam commented May 24, 2018

I'm currently working on providing a nice API in a library where users will be providing implementations of a particular interface. Interface's methods are meant to be executed async in a particular order. Result of calling one method will be transformed in an known way and passed to the next method.

In order to not force my users typing signatures again and again for every part of the process in type parameters, I wanted to benefit from type inference and not to have specify types at all, while still having proper type checking.

Example below illustrates the intention.

While trying things out — faced some inconsistencies in typescript (or VS Code?) which IMHO worth reporting.

TypeScript Version: 2.9.0-dev.201xxxxx
Search Terms: "inference"

Code

type F<X> = [X];
type G<Y> = [Y];
interface IProcess<X, Y> {
  start(): X;
  process<XX = X>(opt: F<XX>): Y;
  finish(opt: G<Y>): void;
}
function foo<X, Y>(desc: IProcess<X, Y>): void {
  const x = desc.start();
  const y = desc.process([x]);
  desc.finish([y]);
}
foo({
  start() {
    return {
      name: "Joe"
    };
  },
  process([x]) {
    return x.name;
  },
  finish([x]) {
    console.log(x);
  }
});

Expected behavior:

In process function x.name would be of a type string

Actual behavior:

  • Property 'name' does not exist on type 'XX'.
  • Even though: var x: XX = { name: string }

types

Playground Link: playground link

Related Issues: didn't find any

@DanielRosenwasser
Copy link
Member

Your interface says that the start method on an IProcess is generic, which really says that it works regardless of the type of XX. I'm really not sure why process needs to be generic, but I suspect that you're trying to solve the failure of inference in this situation:

interface IProcess<X, Y> {
  start(): X;
  process(opt: [X]): Y;
  finish(opt: [Y]): void;
}

function foo<X, Y>(desc: IProcess<X, Y>): void {
  const x = desc.start();
  const y = desc.process([x]);
  desc.finish([y]);
}

foo({
  start() {
    return {
      name: "Joe"
    };
  },
  process([x]) {
    return x.name;
  },
  finish([x]) {
    console.log(x);
  }
});

Here, foo fails to find inferences for X and Y. I think there might be a limitation in our inference here, but it's pretty undesirable.

@DanielRosenwasser DanielRosenwasser added the Bug A bug in TypeScript label May 24, 2018
@DanielRosenwasser DanielRosenwasser added this to the TypeScript 3.0 milestone May 24, 2018
@mhegazy
Copy link
Contributor

mhegazy commented May 24, 2018

We do not treat method signatures as contextually sensitive.. i am not quite sure if this was a deliberate choice or a bug..

so this works as expected:

foo({
    start: () => {
        return {
            name: "Joe"
        };
    },
    process: ([x]) => {
        return x.name;
    },
    finish: ([x]) => {
        console.log(x);
    }
});

The error on name as noted by @DanielRosenwasser is correct, since process is generic function, and it can be called with anything that is not related to X.. I would make not make it generic really. but if you must, then add a constraint:

 process<XX extends X = X>(opt: F<XX>): Y;

@weswigham
Copy link
Member

weswigham commented May 24, 2018

The error on name as noted by @DanielRosenwasser is correct, since process is generic function, and it can be called with anything that is not related to X.. I would make not make it generic really. but if you must, then add a constraint:

Nonsense! Inference for XX should fail, since the param [x]is unannotated, meaning it should fall back to its default, X, which we do have an inference for (from the first method), which has a name member (theoretically making the method implementation valid).

@DeTeam
Copy link
Author

DeTeam commented May 25, 2018

Thanks for your comments.

@DanielRosenwasser

I'm really not sure why process needs to be generic

I suspect that you're trying to solve the failure of inference in this situation:

Thinking what would be the best way to rephrase my use-case from the ticket description.. I want users of my lib not to have to specify types while getting benefits of type inference OR confirm here that such expectations of inference in typescript should not be there :)

I'm expecting more advance inference based on: little knowledge of how inference works in TS, previous experience working with haskell.

@mhegazy

We do not treat method signatures as contextually sensitive.. i am not quite sure if this was a deliberate choice or a bug..

This is exactly what I was looking for in the docs but didn't manage to find 🤔

@weswigham

Inference for XX should fail

This would solve the inconsistency issue, 👍

@MartinJohns
Copy link
Contributor

This is exactly what I was looking for in the docs but didn't manage to find

Another case where having a specification for TypeScript would be helpful. 🤔

@DanielRosenwasser
Copy link
Member

DanielRosenwasser commented May 25, 2018

We do not treat method signatures as contextually sensitive

I'm pretty sure we do:

        function isContextSensitiveFunctionOrObjectLiteralMethod(func: Node): func is FunctionExpression | ArrowFunction | MethodDeclaration {
            return (isInJavaScriptFile(func) && isFunctionDeclaration(func) || isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) &&
                isContextSensitiveFunctionLikeDeclaration(func);
        }

Nonsense! Inference for XX should fail, since the param [x]is unannotated, meaning it should fall back to its default, X, which we do have an inference for (from the first method), which has a name member (theoretically making the method implementation valid).

I'm wondering if this is an instance where in which "pulling" on the type of XX in the body of process, the instantiation is prematurely fixed to {}.

@mhegazy mhegazy modified the milestones: TypeScript 3.0, TypeScript 3.1 Jul 2, 2018
@RyanCavanaugh RyanCavanaugh added Design Limitation Constraints of the existing architecture prevent this from being fixed and removed Bug A bug in TypeScript labels Mar 13, 2019
@RyanCavanaugh RyanCavanaugh removed this from the TypeScript 3.4.0 milestone Mar 13, 2019
@RyanCavanaugh
Copy link
Member

Tagging this as design limitation unless @weswigham or @ahejlsberg has ideas about a targeted fix. In general we are not great at inference that requires knowing which order things needs to happen in.

Or we wait for #30134

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

6 participants