Repository navigation
Type assertion considers empty object compatible with any interface #25539
Description
TypeScript Version: 3.0.0-dev.20180707
2.9.2
Search Terms: empty object compatible with any interface
type assertion empty object
I originally thought that as acted as a hard cast operator, ignoring types. But then someone told me that it can be used to check types. And, sure enough, the expression ({x:24} as IPerson) will result in TS2352. However, ({} as IPerson) will yield IPerson even if IPerson has required properties. This behavior seems inconsistent with itself and I could not find anything obvious in the FAQ about it.
Code
interface IPerson {
name:string;
}
function myFunc(person:IPerson) {
console.log(`name is ${person.name.length} characters long`);
}
const person = {} as IPerson;
myFunc(person);Expected behavior:
$ ./node_modules/.bin/tsc
index.ts:9:16 - error TS2352: Type '{}' cannot be converted to type 'IPerson'.
Property 'name' is missing in type '{}'.
9 const person = {} as IPerson;
~~~~~~~~~~~~~
Actual behavior:
$ ./node_modules/.bin/tsc
$ node ./build/dist/index.js
/tmp/.private/ohnobinki/typescript3-play/build/dist/index.js:3
console.log(`name is ${person.name.length} characters long`);
^
TypeError: Cannot read property 'length' of undefined
at myFunc (/tmp/.private/ohnobinki/typescript3-play/build/dist/index.js:3:40)
at Object.<anonymous> (/tmp/.private/ohnobinki/typescript3-play/build/dist/index.js:6:1)
at Module._compile (module.js:652:30)
at Object.Module._extensions..js (module.js:663:10)
at Module.load (module.js:565:32)
at tryModuleLoad (module.js:505:12)
at Function.Module._load (module.js:497:3)
at Function.Module.runMain (module.js:693:10)
at startup (bootstrap_node.js:188:16)
at bootstrap_node.js:609:3
Related Issues:
I found FAQ entry “Why are all types assignable to empty interfaces?”, but that situation has an empty interface and an object with anything in it rather than the other way around.
Activity
mattmccutchen commentedon Jul 10, 2018
ascan be used to cast to a supertype or a subtype, but you get TS2352 if the target type is neither a supertype nor a subtype of the original type, because that often (but not always) indicates a mistake. See the language specification (which is seriously out of date in many places, but this behavior has been around long enough to be covered).Want to turn this issue into a feature request for a better error message? That would fit well with the work on better error messages in TypeScript 3.0.
Improve the error message when asserting to a type that is neither a
RyanCavanaugh commentedon Jul 10, 2018
The OP is working as intended; see issues like #7531 and #9312.
Regarding phrasing, in TypeScript 1.8 we issued the error
But then had to change it to the more generic phrasing because we switched to a different rule for when an assertion is allowed. The proposed text in the PR isn't right either, as the actual relationship is "relatability"; this is a more loose rule than assignability as it allows this:
even though
xandyare not subtypes of or assignable to/from each other.I think there's likely a better message than the current
but I don't know what it is. We need to communicate to the developer that the two types appear to be completely unrelated, but don't have much space to communicate what our definition of "related" is.
mattmccutchen commentedon Jul 10, 2018
Thanks RyanCavanaugh for looking at this!
I don't think we need to communicate the whole definition. Even giving the user an idea of what is going on so we don't get more issues filed like this one will be an improvement on the status quo. How about just what you said?
RyanCavanaugh commentedon Jul 10, 2018
Just writing the options down next to each other:
It's not immediately apparent why someone confused by one wouldn't be confused by the other? Trying to see it through OP's eyes,
{}is just as much "completely unrelated" toIPersonas it is "not convertable to".We don't want to churn the error messages too much (it causes search keyword rot) so I'd like any new message to be identifiably better than the status quo.
mattmccutchen commentedon Jul 10, 2018
AIUI, OP's incorrect belief was that type assertions allowed upcasts only, and the "Because z" reinforces this belief (!) by reporting how the assertion fails to be an upcast rather than how it fails to be a downcast. To me, the "completely unrelated" message clearly indicates that the rule is something other than upcasts only, while "not convertable" just says the assertion doesn't follow the rule, whatever it is. If I saw that
{x:24} as IPersonis an error and{} as IPersonis not, I'd assume "completely unrelated" must have some definition consistent with those observations and not file an issue. So I think this change addresses at least OP's misunderstanding, though I won't claim it addresses every conceivable misunderstanding. It's unfortunate that the "Because z" explains only one direction of the failure, but that's less trivial to fix; I could try to fix it if you give me an idea how.I don't want to start a long debate that consumes time that could be better spent on other things, so I'm fine if you want to close the issue and PR and not pursue this now.
RyanCavanaugh commentedon Jul 10, 2018
I think you're right about the bidirectionality. "completely unrelated" is sort of informal; maybe it should be something like
Daniel Rosenwasser (@DanielRosenwasser) thoughts?
binki commentedon Jul 10, 2018
Ah, I see I misunderstood the functionality of
asbecause I tried to guess what its functionality was given the error messages I could get it to produce in a set of experiments which did not include pure widening. I found the operator quite confusing because I could sort of see it being used to add static type checking by narrowing types sometimes and bypassing static type checking widening them other times (mainly when casting toanywhich I thought was a special case). I wish there were one-way operators so that I could remove this ambiguity from my code and thataswas never defined the way it is, but it’s way too late to change that.I guess that to get the functionality I want, there is no built-in expression style operator. I am required to declare a variable or call a function to get what I want which actually ends up being similar to the situation in C#:
If the error message is made less suggestive, that would be helpful. I think if it said something that made it obvious that the compiler tried both directions that would help. For example, “{ blah; x } can neither be narrowed or widened to IPerson because neither type is assignable to the other”. The “Types 'x' and 'y' are not related to each other” from Ryan Cavanaugh (@RyanCavanaugh) is also clear.
mattmccutchen commentedon Jul 10, 2018
Nathan Phillip Brink (@binki) FWIW, I think you're using the terms "narrowing" and "widening" backwards to how they are used in TypeScript (downcast and upcast respectively).
I'll wait for further guidance from Ryan Cavanaugh (@RyanCavanaugh) or Daniel Rosenwasser (@DanielRosenwasser) before updating the PR.
binki commentedon Jul 10, 2018
Matt McCutchen (@mattmccutchen) Thanks for the correction. It looks like I was thinking that “more strictly specified” meant wider for objects because there are more keys when it’s actually narrower because it is more specified and fewer other can be assigned to it. Hopefully I keep that straight in the future.
Improve the error message when asserting to a type that is neither a
Improve the error message when asserting to a type that is not
Improve the error message when asserting to a type that is not
typescript-bot commentedon Jul 25, 2018
Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.
Improve the error message when asserting to a type that is not