Skip to content

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

Closed
@srijs

Description

@srijs

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?

Activity

DanielRosenwasser

DanielRosenwasser commented on Jan 1, 2016

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

srijs commented on Jan 1, 2016

@srijs
Author

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

DanielRosenwasser commented on Jan 1, 2016

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

srijs commented on Jan 3, 2016

@srijs
Author

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

DanielRosenwasser commented on Jan 3, 2016

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

added
QuestionAn issue which isn't directly actionable in code
on Jan 7, 2016
locked and limited conversation to collaborators on Jun 19, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    QuestionAn issue which isn't directly actionable in code

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @DanielRosenwasser@srijs@mhegazy

        Issue actions

          Weird type-widening issue when using methods instead of function properties. · Issue #6309 · microsoft/TypeScript