-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Consider property access a form of type guards #1260
Comments
👍 for this. For the existing issues/questions:
I assume that solely in these typeguard contexts, you're allowing a property access for properties that exist on any of the constituent types. If so, I'd think it makes sense to extend this sort of logic to the language service. After all, I assume we'll still give errors in the appropriate contexts. For instance, interface A { bar: { bizzle: any } };
interface B { }
var foo: A | B;
if (foo.bar is fine, but as soon as you have something like var foo: A | B;
if (foo.bar.bizzle the LS will complain that
Shouldn't be terrible if user-defined type tags supposedly won't affect perf either, but it'll be good to keep an eye on it. |
Any update on this? if i can weigh in:
In addition to what Daniel said, what would be cool is when Also, in the case of var foo: A | B;
if (foo.bar.bizzle if bar doesn't exist in the union type, maybe |
Since this is still open, it seems that the original example here is no longer valid?
The code above now errors at x.length and seems this is the case per https://github.com/Microsoft/TypeScript-Handbook/blob/master/pages/Advanced%20Types.md Can anyone confirm? |
The example was invalid at the time this was written as well. The idea was that at certain locations where you want to check a member on the type, you shouldn't need to cast, and that if certain types in a union lacked that member, they'd be "narrowed out". |
+1 I hate noise +1'ing. Some repos seem to have a separate thumbs up icon you can click on under the original issue post and it will tally. Absent that, I'm indicating this is of value. |
@rcollette There's an emoji smiley in the upper right hand corner of all comments - use that to add a 👍 (or other reaction), as you just described. |
Ah. The you can click on it to add to it once its there. I've never been first I guess. (gee that reminds me of the days when you'd see "first" as the only response to a post). |
I would love to see this implemented. User-defined type guard functions which are currently required in lieu of this proposal, litter the code with functions which would be unnecessary in vanilla javascript. These functions exist only to support the type system, yet they remain when compiled down to javascript. |
Another benefit of implementing property access type guards is that user-defined type guards are often unsafe. For example: interface Foo {
a: string;
}
interface Bar {
b: string;
}
function isFoo (o : Foo | Bar): o is Foo {
return (<Foo> o).a !== undefined;
} This works as intended. But if for some reason, later on Bar is expanded to: interface Bar {
b: string;
a: string;
} Now we have a bug that is uncaught by the type checker because the type guard essentially bypasses it with the type assertion. It would seem that type assertions are almost always present in user-defined type guards. |
Since this is tagged "Awaiting more feedback", I'll just link to #36194 as a potential use case. |
Just curious: Is this simply a yet-to-be-implemented feature request or has it not been implemented due to some kind of technical cost or differing opinion? |
"Awaiting More Feedback" generally means that this makes sense, and would presumably be OK to add to the language, but the quantity and quality of feedback heard so far has not indicated that this is what we should spend churn, complexity, and risk on. This particular change request is very problematic from a soundness perspective, especially compared to the much clearer intent of using |
Feedback for you. Prettier is a formatter that works with ASTs that come from different parsers. In the case of JS-based languages, these AST formats are all based on the same ESTree base format, but also they have differences. It'd be great to be able to use a union type for this, and it looks like it might work (simplified code for two parsers only): import * as Babel from "@babel/types";
import { TSESTree } from "@typescript-eslint/types";
type Node = Babel.Node | TSESTree.Node; but in practice the resulting E.g., this already quite complex condition node.type === "MethodDefinition" &&
node.value &&
node.value.type === "FunctionExpression" &&
getFunctionParameters(node.value).length === 0 &&
!node.value.returnType && needs to be rewritten this way to make TS happy: node.type === "MethodDefinition" &&
node.value &&
node.value.type === "FunctionExpression" &&
getFunctionParameters(node.value).length === 0 &&
(!("returnType" in node.value) || !node.value.returnType) The added |
@RyanCavanaugh Can you link me to an explainer that includes this and other information about how the TS team uses labels and other meta-features for issues in this repo? I'd like to be able to link other people who haven't been informed yet, and I imagine it will keep other similar replies DRYer. |
Just wanted to check in and see if there were any updates on this - this would be a fantastic feature! Let me know if there is anything I can do to help. |
The example from prettier in this comment is very representative of real-life use-cases. One of the solutions I'm considering is to virtually add all properties to all members of my unions and set some of them to Example: interface A {
a: string;
};
interface B {
b: string;
};
type U = A | B;
const val: U = getValueFromSomewhere();
if ("a" in val) {
// Ok, val is of type A
} I will do something equivalent to the following: interface A {
a: string;
// Fake properties to make TS happy
b?: undefined;
};
interface B {
b: string;
// Fake properties to make TS happy
a?: undefined;
};
type U = A | B;
const val: U = getValueFromSomewhere();
if (val.a) {
// Ok, val is of type A
} In my opinion, it's a tradeoff. I know this will reduce the type-safety but since it's easier to use and reason with, it will lead to more people moving from JS to TS without having to change their code too much and in a too annoying way which actually increases type-safety. Edit: Wrapper helping with the UX issueI've created a type to help solve the problem. Here is the things you need: // Creates a union of all keys of all objects in the Terface union
type AllKeys<Terface> = Terface extends any ? (keyof Terface & (string | number | symbol)) : never
// Creates a new interface adding the missing keys to Terface
type Wrap<Terface, Keys extends string | number | symbol> = (Terface & {
[K in Exclude<Keys, keyof Terface>]?: undefined;
})
// Distributes the union and automatically add the missing keys
type BetterUXWrapper<Terface, Keys extends AllKeys<Terface> = AllKeys<Terface>> = Terface extends any ? Wrap<Terface, Keys> : never; Here is an example usage: interface A {
a: string;
};
interface B {
b: string;
};
type URaw = A | B;
type U = BetterUXWrapper<URaw >;
declare const val: U;
if (val.a) {
// Ok, val is of type A
} |
The |
This is a proposal for using property access as another form of type guards (see #900) to narrow union types. While we're investigating expanding the power of type guards (#1007) this feature would support the natural style that JavaScript programmers have in their code today.
Using property access to narrow union types
We do not expand the situations in which types are narrowed, but we do expand the known type guard patterns to include basic property access. In these narrowing contexts it would be an error to access a property that does not exist in at least one of the constituent types of a union type (as it is today). However, it would now be valid to access a property that exists in at least one, but not all, constituent types. Any such property access will then narrow the type of the operand to only those constituent types in the union which do contain the accessed property. In any other context property access is unchanged.
Invalid property access
Issues/Questions
I have a sample implementation and tests in a branch here: https://github.com/Microsoft/TypeScript/tree/typeGuardsViaPropertyAccess. There're a couple bugs remaining but examples like the above all work and no existing behavior was changed.
The text was updated successfully, but these errors were encountered: