-
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 type narrowing to be specified as always-on or always-off #57725
Comments
This seems like a use case for |
@fatcerberus I agree. But, likewise, there is a need for |
This would solve Angular signals type narrowing too angular/angular#49161 |
Having this would make Angular with Signals way simpler to use, and the Angular team won't have to "hack the template language" (angular/angular#55456) in a way to make it work on Angular templates only, as the issue will still remain in normal typescript code. Hoping we can have some kind of solution for this one π Also, as I've commented above, there's a lot of discussion already regarding this topic in this issue angular/angular#49161 |
This will make reactive programming in Angular smoother. I support. |
Someone had to say it. This help angular signals assertion in templates. |
Yes please! β€οΈ It would allow me to remove this paragraph from the TanStack Query Angular documentation and greatly improve developer experience. And remove clunky workarounds that only partly work.
https://tanstack.com/query/latest/docs/framework/angular/typescript#type-narrowing |
If both getters and methods returning a boolean can narrow (using a type predicate) it would make sense to me that other methods calls should be able to do this as well. |
I love how @matthew-dean introduces the concept of a method being considered pure by demonstrating why that getters are not π . The use of impure getters has always been a contentious topic. From their inception, the consensus was clear: "Keep them simple." Over time, this evolved into a convention where getters are expected to remain pure. Programming with nullability in mind marks a significant paradigm shift, especially when comparing modern code to practices from a decade ago. Today, ensuring code is safeguarded against null reference errors has become a standard in TypeScript. As a result, the convention that getters are pure while functions are not has been widely adopted and integrated into development practices. If Angular is confident that it knows that its So why are we still using Angular already incorporates purity concepts with pipes, supported by documentation to emphasize their importance. However, do developers consistently grasp and implement these concepts correctly? If Angular aims to introduce "pure signals," the annotation could be handled at the framework level, whether in Angular, Vue, or similar ecosystems, leveraging their existing pre-compilation processes for HTML templates. I am going to be unpopular here, in fact, one could argue for an entirely different approach: marking getters as unstable while reserving stability exclusively for real fields/variables. This would prevent the proliferation of additional annotations and maintain the integrity of the type system. Otherwise, allowing developers to override purity checks feels analogous to loosening type constraints with TypeScript, as a superset of JavaScript, it has been great in safeguarding us from making common mistakes. However, placing even more power in the hands of developers introduces inherent risks. Shouldn't be more though-provoking and propose an |
One could argue that this is driven not by purity, but by API design. Having invocable signals, makes them akin to any other function. |
I'm very happy that someone wrote an issue about this. I never knew how to phrase what I was looking for properly or I would have created an issue 6 years ago. Solid community is very motivated to have this addressed. Signals in general have been pushed towards function toGetter(fn) {
return {
get value () {
return fn();
}
}
}
const doubleCount = toGetter(() => count.value * 2) Of course you can make a And regardless of our chosen Signal syntax wrapping a reactive access in a function will transfer reactivity: const getCount = () => countSignal.value
getCount(); // can be tracked reactively regardless of your syntax This means every Signals library will get back here almost immediately regardless of their choice. Which means forcing a getter is not really a solution. I've made it no secret I've struggled making TS work for SolidJS over the years and have in many ways needed to limit my expectations of what is possible due to it. But solving this would be by far the most impactful change that could be made for Signals. This is a very important problem to solve as it permeates through the foundations of these solutions impacting composition and control flow. In my opinion this is currently one of the few remaining gatekeepers for Signals success and I and the community are very motivated to see a solution here.
It doesn't necessarily have to do with impurity. But it has everything to do with composition. I do understand why one might be hesitant here. Signals are unique in that they suggest that during synchronous execution multiple reads of the same function return the same value. We shouldn't writing to signals during the pure part of execution but not all implementations do this. So I can see where one would be hesitant to give this power. But the alternative is very prohibitive. There is no way to model this behavior. Yet we can create it with JavaScript. I don't want to force every developer to mark things as pure or idepotent. But having years of looking at the alternative in a system where you can assume every signal is, this is much more desirable to bake into base types as my expectation is every Signal in Solid would be this way. The developer just marks it as a Signal or Accessor and it gets this property. Today Developers will jump through hoops with unnecessary wrapper functions, they will use unnecessary optional chaining EDIT: I suppose though in reality Signals do get written to under certain (impure) scopes (events/side effects) so while this holds in most places unless Signals kept in the past (like React State) or threw on read after write what is being asked for isn't completely pure in all contexts. It is a like sort of like how My hope is that we can find something useful here in a general sense and then if I need to fudge a bit in my types to make it work in a practical sense I'm good with that. |
The problem with completely disabling type narrowing, even per-file, is that it's very extremely important for the type system to understand a great deal of real-world code. Disabling type narrowing would mean code like the following would produce type errors: function logMaybe(maybe: string | undefined) {
if (!maybe) return;
console.log(maybe.toUpperCase());
// With type narrowing: no error
// Without type narrowing: 'Object is potentially undefined.'
} Turning off narrowing also wouldn't fix the TypeScript issue for signals. Signals still would struggle with narrowing computed values: declare const value: () => string | undefined;
if (value() !== undefined) {
value();
// Type: string | undefined
console.log(value().toUpperCase());
// ~~~~~~~ Object is possibly 'undefined'.
} ACK that Signals are clearly painful in TypeScript right now, but I really don't think turning off type narrowing would be the solution you're looking for. Taking a step back: for signals specifically, the root issue is really that the type system doesn't understand how the signal functions work. The types don't know that repeated parameter-less calls to a function all return the same value -- and so that value should receive the same type narrowing. In general, it's better to work towards making the type system understand a use case, not disable type system features. I filed #60948 as a starting proposal for one idea that might be able to solve this for most signals implementations.
|
@JoshuaKGoldberg I think this proposal better suites signals and general use cases. As I understand it what's being proposed is not a way to disable narrowing per file, but more so a way to mark certain functions "pure" and some getters/prop access as "impure". I think this would work fine if fns/methods by default were considered impure, and any calls (or access) to something impure should reset all pure fn/prop (potentially local vars/lets too) narrowing. All need to be reset, because there's no telling what state an impure fn mutates. |
π Search Terms
type narrowing of functions, asserts on getters
β Viability Checklist
β Suggestion
Right now, properties of objects can always be narrowed, whereas functions can never be narrowed. As a developer, I would like to specify when functions can be narrowed, and when object properties can't be narrowed.
π Motivating Example
Consider this example:
In the TypeScript playground, you can see that even though these code paths are identical in terms of output and function calls, one is narrowed and one is not. TypeScript always assumes that properties are always stable in their values between calls, and assumes that functions are always in-stable in their calls.
You can see this in the TypeScript error which does not error for the property, even though it should.
π» Use Cases
I'm more concerned with the stable function case than the instable property object case. I'm building a Knockout-like observable library that lays on top of Vue. It works fine / perfectly in the Vue ecosystem, however it's TypeScript that doesn't behave here.
foo.value
is always type-narrowed, whereasfoo()
is never type-narrowed, even though I can guarantee its stability between calls. Because this behavior in TypeScript is automatic (as far as I know?), there's no way to specify which function calls are stable (and can be narrowed) and which ones are not.You can type narrow by assigning the returned value of the function to another variable. However, in Vue, this approach is limited when binding to templates, where
v-if
will not type-narrow a functional getter.A very clumsy workaround is using a Vue
computed()
, which then also type-narrows.The text was updated successfully, but these errors were encountered: