-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
allow narrowing from any #9999
Comments
Can you specify a use case? The whole point of This code passes: function foo(x:any) {
if (typeof x === 'string') {
needs_string(x);
}
}
function needs_string(x:string) {} and so does this: function foo(x:{}) {
if (typeof x === 'string') {
needs_string(x);
}
}
function needs_string(x:string) {} |
@jesseschalken here are some use cases: A. Narrowing the error variable in a B. Retaining type safety when we explicitly use type guards on an function validate(x: any) {
x.whatever(); // no error, but that's OK since we've opted out of checking due to `any`
if (Array.isArray(x)) {
// we've EXPLICITLY opted-in to type checking now with a compile-time AND runtime array check
x.Pop(); // oops typo, but no error!?
x.whatever(); // no error!?
x(); // no error!?
}
} C. Refactoring using tools like VSCode Suppose in the following example we use the VSCode 'rename' tool to rename // An example taken directly from #5930 as a reason why any should NOT be narrowed
// By contract, this function accepts only a Dog or a House
function fn(x: any) {
// For whatever reason, user is checking against a base class rather
// than the most-specific class. Happens a lot with e.g. HTMLElement
if (x instanceof Animal) {
x.woof(); // Disallowed if x: Animal
} else {
// handle House case
}
} EDIT: Adding a fourth use case that came up later in #9999 (comment): D. Consuming definitions from |
I think @RyanCavanaugh summed up the issues around narrowing
However, TypeScript currently only caters to the one set of users. 👍 for a flag to cater to both sets of users, since both groups have valid needs. |
Serious question @Aleksey-Bykov -- how are there even any |
A. Eeek. That's unfortunate. There should definitely be a compiler option to make caught errors B & C. I see. The problem then is that TypeScript enables you to mix dynamic and static typing, but you can't have the same variable be dynamically typed in one place and statically typed in another place. Dynamic vs static is a property of the variable itself and you can't transition in/out of dynamic mode without creating a new variable or using I don't care personally because I try to stay as far away from |
Unorganized thoughts, some contradictory
|
It's possibly just me, but I find
Probably a stupid suggestion but is it worth considering adding a type keyword that's exactly like |
You can do type unknown = {} right now if you want. |
True, as long as you don't mind adding boilerplate for than in every file you need it. Also |
but you are right, not that many left |
@yortus It depends how your project is set up, but if you define it at the top level, outside a module or namespace, it will be available across the entire project without needing to be imported, just like the things in lib.d.ts are. Fair point about compiler messages/intellisense though. |
@RyanCavanaugh |
@jesseschalken I assume you are pointing out this is how things currently are, not how they necessarily should be. From #5930 it's appears the team had no problem with the same variable transitioning from untyped to typed in the explicitly protected region of a type guard. After all, their first instinct was to implement narrowing from It only got backed out when a subset of users, whose code is written according to the old proverb "just as all Having said that, their reasoning that "I used |
I would just like to repeat what I said in #8677, which is that you need to do something to make try-catching I am writing an TypeScript program that uses exceptions, and right now I have had to multiple times include the following eight(!) line idiomatic construct just to catch an exception:
That's very awkward and contains a lot of repetition (and therefore potential for typos). Compare with the clear, compact analogous code in C#:
You should do something to make this less awkward in TypeScript; allowing This said, two things:
|
@mcclure If it's any help you can reduce some of that boilerplate with something like this: function narrowError<T>(e:{}, c:new(...x:any[]) => T):T {
if (e instanceof c) {
return e;
} else {
throw e;
}
}
class SomeCustomError {
constructor(public someField:string) {}
}
try {
// ...
} catch (e_) {
let e = narrowError(e_, SomeCustomError);
console.log(e.someField);
} |
@RyanCavanaugh but then how come this works?
|
@Aleksey-Bykov because |
but it doesn't matter since |
The parameter type of a type guard isn't used in the narrowing logic |
😮 🔫 |
it just blows out the rest of my mind, why the type of the parameter which is being narrowed isn't used? from what do we narrow then? why would we care to specify it at all? |
It's used for the function body. I use it for generics |
I don't understand what the alternative would be. Many type guards can effectively determine any input value, and need to do some weird stuff in their implementation, so |
Honestly, I treat Besides, other problems the any type causes should be resolved on their own. we shouldn't change the behavior of P.S. @Aleksey-Bykov an Array with extra params checked against |
well... i thought that there is no alternative at all to begin with, hence this topic - allow narrowing from any, which makes me feel silly because narrowing from any does work already, just probably not to anything and not from anything, and what those from and to are is a question yet to be explored turns out i don't know what type guards really are, i thought that typguards are predicates that operate on a value of a type they are declared for still trying to wrap my head around the idea that there are types (only one type?) that get ignored when used for parameters of type guards at least (anything else?) |
and of course this one doesn't work:
how can i trust what i see after i've seen that:
which might or might not work depending what |
@Aleksey-Bykov anything inferred/declared as |
ok, i think i got it, it's just trickier than i though it was:
uff da, that's been a ride wiki needs a topic on this, thanks |
I thought they were the same thing? |
The distinction is super subtle but they are not identical. One observable difference is let x: Object = { hasOwnProperty : 4 }; // error
let y: {} = { hasOwnProperty : 4 }; // ok |
let y: {} = { hasOwnProperty : 4 }; // ok
y.hasOwnProperty('foo'); // also ok? huh? Sorry for getting off topic, but shouldn't |
We can't have the notion of an invalid object literal. That's basically nonsense. This all happens because of apparent members. These are properties that appear during property access and in the source of an assignability check, but not in other places. They're there to mimic JS's prototype chain and boxing behavior. So when you write Apparent members are also why you can invoke methods like |
I see. So basically everything has the methods from |
👍 for the behavior described at #9999 (comment) Summary:
@yortus do you want to update your fork and own the PR for this? It should be a quick change compared to what you have -- just remove the switch logic and compare the target using |
@RyanCavanaugh PR submitted: |
There are numerous questions as to why
any
cannot be narrowed from. Despite being natural and intuitive this feature was rejected due to being a serious breaking change to poorly written yet massive code bases.Please consider putting it behind a flag provided the evident need for it.
TLDR
any
is not what you think it is (poor naming at play)type unknown = {} | undefined | null | void
instead*.d.ts
by handsUPDATE
@yortus made a branch per #9999 (comment) where narrowing from
any
works!try it:
npm install typescript-narrowany
The text was updated successfully, but these errors were encountered: