Skip to content

Weird type-widening issue when using methods instead of function properties. #6309

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
srijs opened this issue Jan 1, 2016 · 5 comments
Closed
Labels
Question An issue which isn't directly actionable in code

Comments

@srijs
Copy link

srijs commented Jan 1, 2016

Hi there.

I'm using the tsc compiler, version 1.8.0-dev.20151231, and I'm running into a weird issue where widening of structural types works when I'm defining a class with a function property, and fails when I'm replacing that function property by a "proper" method.

But see for yourself: This is the version that compiles, and this is the version that doesn't.

What is the difference here? Is this a bug in the type inference engine?

@DanielRosenwasser
Copy link
Member

This doesn't have to do with widening.

The problem is that you typed run as (T) => X which is _actually_ (T: any) => X. Parameter names are required in function type literals. You can catch this issue by using the --noImplicitAny compiler option.

You'll see the version that errors the same way here.

@srijs
Copy link
Author

srijs commented Jan 1, 2016

Thanks, I enabled --noImplicitAny for building my project now.

Out of interest, is there a way I can make this program typecheck without any? Why is TypeScript not able to widen {foo: number} contra-variantly to {foo: number, bar: number} to make it assignable?

For example, I can add explicit type annotations to make it work, like in this version, but could tsc not infer that itself based on the return type of the fooAndBarEq function?

@DanielRosenwasser
Copy link
Member

Part of it is the way we select inference points, and the flow of direction between types. Supporting many different inference sites can be complex.

One way to get what you want is to make fooIsEq and barIsEq generic. That way you can say ahead of time what type you're working with.

function fooIsEq<T extends { foo: number }>(x: number) {
  return new Predicate<T, boolean>(v => v.foo === x);
}

function barIsEq<T extends { bar: number }>(x: number) {
  return new Predicate<T, boolean>(v => v.bar === x);
}

function fooAndBarEq(x: number) {
  return fooIsEq<{foo: number; bar: number }>(42)
            .chain(() => barIsEq(42));
}

Another approach is to say that chain can build types up with an intersection type:

class Predicate<T, X> {
  constructor(private _run: (x: T) => X) {}

  public run: (x: T) => X = (v: T) => {
    return this._run(v);
  }

  public chain<U, Y>(f: (x: X) => Predicate<U, Y>) {
    return new Predicate((v: U & T) => f(this.run(v)).run(v));
  }
}

function fooIsEq(x: number) {
  return new Predicate<{foo: number}, boolean>(v => v.foo === x);
}

function barIsEq(x: number) {
  return new Predicate<{bar: number}, boolean>(v => v.bar === x);
}

function fooAndBarEq(x: number) {
  return fooIsEq(42).chain(() => barIsEq(42));
}

This will allow you to compose predicate functions easily, but you're more prone to accidentally passing in an incorrectly typed function.

By the way, does Predicate really need two type parameters? Usually they just return booleans.

@srijs
Copy link
Author

srijs commented Jan 3, 2016

Thanks a lot. The & intersection type is pretty cool. Tbh, I was looking for a dual to | previously, but didn't find anything. Is there documentation in the current TypeScript spec for it?

I'd be especially interested in how co & contravariance are solved and how it interacts with the bivariance of function arguments. Is that why you were saying "[I was] more prone to accidentally passing in an incorrectly typed function." with this approach?

@DanielRosenwasser
Copy link
Member

We're in the process of documenting things a little more, and the spec will have it soon as well.

What I meant is that you can chain with basically any single-argument function. I guess in practice it won't be a problem, since you'll get an error when you actually try to run with some value that isn't compatible.

@mhegazy mhegazy added the Question An issue which isn't directly actionable in code label Jan 7, 2016
@mhegazy mhegazy closed this as completed Jan 7, 2016
@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
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

3 participants