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

Empty type {} gets inferred to without a warning #2511

Closed
zpdDG4gta8XKpMCd opened this issue Mar 26, 2015 · 20 comments
Closed

Empty type {} gets inferred to without a warning #2511

zpdDG4gta8XKpMCd opened this issue Mar 26, 2015 · 20 comments
Assignees
Labels
Spec Issues related to the TypeScript language specification

Comments

@zpdDG4gta8XKpMCd
Copy link

function isOkOf<a>(anything: { isOk: a }) : a {
    return anything.isOk;
}
function notOver<a>(is: (value: a) => boolean) : (value: a) => boolean {
    return function not(value: a) : boolean {
        return !is(value);
    };
}
interface A {
    isOk: boolean;
}
var values : A[] = [{ isOk: true }];

values.map(notOver(isOkOf)); // <-- expected: compilation error, actual: `a` inferred to {} without a warning
@DanielRosenwasser
Copy link
Member

When you say you expect an error, you mean because of the failure to find candidates for type arguments, not for any other reason, right?

If so, this sounds like a duplicate of #360.

@zpdDG4gta8XKpMCd
Copy link
Author

Yes. Somehow I thought #360 was shipped with 1.4, wasn't it? I saw it my self giving errors in a LKG a while ago.

@DanielRosenwasser
Copy link
Member

No, that relates to if we are unable to find a common supertype as in the following:

function f<T>(x: T, y: T) {
    // ...
}

f(10, "hello");

@zpdDG4gta8XKpMCd
Copy link
Author

Sorry for the unrelated question, is the milestone for shipping #360 set?

@JsonFreeman
Copy link
Contributor

Your code looks correct, but maybe you are talking about the case where values is not an A[]? In that case, isOkOf expects something that has an isOk property, but you will not have one if values is {}[] for example.

@zpdDG4gta8XKpMCd
Copy link
Author

Well, I think the code is unsound and vulnerable to refactoring. Consider

var values /* : A[] */ = [{ isDeliciousPizza: true }];

values.map(notOver(isOkOf)); // <-- still no warnings

@JsonFreeman
Copy link
Contributor

I would also add that the compiler does two odd things with your original repro:

  • The a from notOver is inferred to {} because it's fixed before it has any candidates, and we don't report an error on this.
  • By the time we have to compare isOkOf to is, isOkOf has been instantiated to any. So even though we never find out that the a of isOkOf is a boolean, we don't give an error about this.

@JsonFreeman
Copy link
Contributor

Yes, the vulnerability you note is my point exactly. Because isOkOf is generic, we end of fixing the a of notOver, and then we lose the type argument inference we would have gotten. It would be awesome if we could still infer from the structure of anything, even though we don't yet know what the a of isOkOf is yet.

@JsonFreeman
Copy link
Contributor

You can test that out by making isOkOf not generic. I believe you should now get an error.

@zpdDG4gta8XKpMCd
Copy link
Author

You can test that out by making isOkOf not generic. I believe you should now get an error.

Well, the whole point it to have isOkOf generic for dryer / more reusable code base.

It would be awesome if we could still infer from the structure of anything, even though we don't yet know what the a of isOkOf is yet.

Wouldn't it? May I dare to ask if there is any chance for TS to ever get Hindley Milner sort of type system?

@danquirk danquirk added the Bug A bug in TypeScript label Mar 26, 2015
@JsonFreeman
Copy link
Contributor

Perhaps you are asking for us to infer the A for the a in notOver, based on the return type of notOver. That would allow us to plumb it through to the parameter of is. This would be a significant change to our type argument inference algorithm. But this may potentially be the right thing to do in the absence of inference candidates.

@JsonFreeman
Copy link
Contributor

I mean as a fallback.

@mhegazy mhegazy added the Spec Issues related to the TypeScript language specification label Mar 27, 2015
@mhegazy mhegazy added this to the TypeScript 1.5 milestone Mar 27, 2015
@mhegazy mhegazy modified the milestones: TypeScript 1.5, TypeScript 1.5.1 Apr 23, 2015
@mhegazy mhegazy removed the Bug A bug in TypeScript label May 27, 2015
@JsonFreeman
Copy link
Contributor

I can think of 4 options here:

  1. Use contextual signature instantiation during assignability. This would give an error in your original example, and in the delicious pizza example. It also makes the type system much stricter with respect to generics.
  2. Give an error when {} is inferred because there are no inference candidates. We have discussed this option before, and it never really gained much traction, but we could revisit. Again, it would give an error in both your examples.
  3. When the return type of the signature is a function type, we can treat the signature as a curried function and also infer to its return type's parameters. The inferences would come from the contextual type of the call. This would be done recursively until we hit a return type that is not a function type. This would add some complexity in overload resolution, but it's also kind of nice. It would not really make the type system stricter like option 1, just smarter when this pattern arises. It would not error in your original example, but would error in the delicious pizza example, which is the most correct behavior. I think it's my favorite option because it's the most targeted fix.
  4. Do nothing.

@JsonFreeman
Copy link
Contributor

Actually 3 is more complex than I thought. Previously a contextual type could have no effect on a call expression. With this proposal it would. The tricky part is that we'd have to add another condition for fixing type parameters as a result of contextually typing a call expression.

@JsonFreeman
Copy link
Contributor

The other complex thing about it is that it messes with the arity of the signature and the number of arguments being passed, and I can't figure out how that would interact with spread and rest.

@DanielRosenwasser
Copy link
Member

There's a branch that has suggestion 2 called downWithDreadedCurlyCurly. Haven't really touched it in a while though.

@zpdDG4gta8XKpMCd
Copy link
Author

I like the best options 1 then 2.
3 - is too specific for the case
4 - doesn't change anything

@mhegazy mhegazy modified the milestones: TypeScript 1.5.2, TypeScript 1.6 Jun 17, 2015
@mhegazy mhegazy removed this from the TypeScript 1.5.2 milestone Jun 17, 2015
@ahejlsberg
Copy link
Member

I'm closing this one since it looks to be covered in #3423.

@zpdDG4gta8XKpMCd
Copy link
Author

@ahejlsberg, I am afraid this request is a related, but separate issue
If so, I would reopen it, consider:

interface A<Z> { z: Z }
declare function fn<X extends A<Z>, Z>(value: X): void;
declare var a: A<number>;
fn(a); // <-- a problem: fn<A<number>, {}>

I would like the "inferred" empty type was flagged by the compiler.

@microsoft microsoft locked and limited conversation to collaborators Jun 18, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Spec Issues related to the TypeScript language specification
Projects
None yet
Development

No branches or pull requests

6 participants