Omitting prototype from keyof
#55471
export declare class SomeClass {
static readonly FOO = "FOO";
static readonly BAR = "BAR";
// prototype: SomeClass;
}
export type ValueTypesOfClass = typeof SomeClass[keyof typeof SomeClass];
// This assignment should NOT be allowed, but it currently is in TypeScript.
const icon: ValueTypesOfClass = "hiiii";
- What's wrong here?
- Grabbing all of the types of values on
typeof SomeClass - but that expands out to (typeof SomeClass)["prototype"] | "FOO" | "BAR".
- But
(typeof SomeClass)["prototype"] is just SomeClass, which is an empty instance type.
- So it's really just
{} | "FOO" | "BAR", and everything non-nullish is assignable to {}...
- Which means that
"hiiii" is assignable to ValueTypesOfClass
- Pattern is very dubious - could've written an enum here.
- Why get rid of it? It may break existing code.
- Conclusion
- Must check out Mongoose break. Believe it's an asymmetry between how we handle
keyof and how we omit properties in mapped types. Want to see how fixing that up changes things.
- But also, we want to be cautious. Very niche. Skeptial that it adds value, whereas there's always a risk of breaking.
Making const Contexts/Type Parameters Easier for Arrays
#51931
#55229
declare function f1<const T extends unknown[]>(arr: T): T;
declare function f2<const T extends readonly unknown[]>(arr: T): T;
const a = f1([1, 2, 3]);
// ^? const a: unknown[]
const b = f2([1, 2, 3]);
// ^? const b: readonly [1, 2, 3]
-
For f1 we infer unknown[] because it omitted readonly.
- Why?
const pushes the type of the array towards readonly [1, 2, 3] - but that's not assignable to unknown[].
Array has methods for mutation, whereas ReadonlyArray does not.
-
Very annoying footgun - today, it makes no sense to write const type parameters with mutable array constraints in TypeScript.
-
Newest change pushes const-y element types without violating the expectations of the contextual type. In this case, it doesn't push the readonly-ness of the tuple as a whole.
-
Note the change isn't simply restricted to const type parameters - it also works with as const when an outer contextual type has a mutable array type - which means that a and b here are equivalent:
declare function f1<const T extends unknown[]>(arr: T): T;
const a = f1([1, 2, 3]);
// ^ [1, 2, 3]
// ^
// These work the same now!
// v
declare function f2<T extends unknown[]>(arr: T): T;
const b = f2([1, 2, 3] as const);
// ^ [1, 2, 3]
-
Does this step on satisfies?
- No, in fact we think it composes better. You can do
as const satisfies unknown[].
-
It does break certain codebases which currently have a union of mutable/immutable tuple types. We think they can typically adapt the code fairly easily.
-
The most embarrassing part of the blog post introducing const type parameters was pointing out that you should never write const type parameters with mutable array constraints - but that they were allowed to be written.
-
Overall it feels right. Want to push on this direction.
Dynamically Named/Instantiable Tuple Labels
#44939
#55452
type SomeString = "world";
type Tuple = [`hello${SomeString}`: number]
// type Tuple = [helloworld: number]
- Thought we understood this, took a closer look.
- Why is it such a crucial need to relabel the parameters?
- Implementation feels strange - the thing that instantiates and follows with the type, but disappears as soon as have a union.
- Huh...
type SomeString = " world";
type Tuple = [`hello${SomeString}`: number]
// type Tuple = [hello world: number]
-
Oh no...
type SomeString = ": number";
type Tuple = [`hello${SomeString}`: number]
// type Tuple = [hello: number: number]
-
OH NO.
type SomeString = ": number, yadda";
type Tuple = [`hello${SomeString}`: number]
// type Tuple = [hello: number, yadda: number]
-
We know, we know this is a prototype. 😄 You'd need some escaping to handle those.
-
That's not the only reason we're iffy on this.
-
Can almost imagine wanting to support labels from const [count, setCount] = useState(0);
- But that doesn't work. You'd need
const [count, setCount] = useState<"count">(0);
- Suggesting a type parameter for something entirely design-time feels a bit ridiculous.
-
Doesn't feel consistent with names in other positions - expression vs. types.
-
Don't like how you lose the name if you have a union - doesn't feel like it's consistent with the rest of the type system.
-
Feels like the code you'd need to write for this would be so gross it's not worth the feature.
-
@weswigham had alternative approach to the use-case in the original issue that doesn't even need dynamic names for tuples.
-
When it comes to design-time validation and help, open-ended string completions are something we should probably solve first.
-
Conclusion:
- Number one thing is to better understand scenarios - we don't feel ready to commit to this until we see more motivating use-cases, code that this PR allows you to write that is compelling.
- Skeptical of approach for implementation. Stuff to fix if we wanted the feature:
- Syntax - make it consistent with where types are used over expressions in other positions. @weswigham to weigh in on this one.
- Disappearing in union case - feels very odd for the rest of the type system. Not clear on what could change there.
- [[Editor's Note: I don't know if that's a feature issue, but very surprising.]]
Omitting
prototypefromkeyof#55471
typeof SomeClass- but that expands out to(typeof SomeClass)["prototype"] | "FOO" | "BAR".(typeof SomeClass)["prototype"]is justSomeClass, which is an empty instance type.{} | "FOO" | "BAR", and everything non-nullish is assignable to{}..."hiiii"is assignable toValueTypesOfClasskeyofand how we omit properties in mapped types. Want to see how fixing that up changes things.Making
constContexts/Type Parameters Easier for Arrays#51931
#55229
For
f1we inferunknown[]because it omittedreadonly.constpushes the type of the array towardsreadonly [1, 2, 3]- but that's not assignable tounknown[].Arrayhas methods for mutation, whereasReadonlyArraydoes not.Very annoying footgun - today, it makes no sense to write
consttype parameters with mutable array constraints in TypeScript.Newest change pushes
const-y element types without violating the expectations of the contextual type. In this case, it doesn't push thereadonly-ness of the tuple as a whole.Note the change isn't simply restricted to
consttype parameters - it also works withas constwhen an outer contextual type has a mutable array type - which means thataandbhere are equivalent:Does this step on
satisfies?as const satisfies unknown[].It does break certain codebases which currently have a union of mutable/immutable tuple types. We think they can typically adapt the code fairly easily.
The most embarrassing part of the blog post introducing
consttype parameters was pointing out that you should never writeconsttype parameters with mutable array constraints - but that they were allowed to be written.Overall it feels right. Want to push on this direction.
Dynamically Named/Instantiable Tuple Labels
#44939
#55452
Oh no...
OH NO.
We know, we know this is a prototype. 😄 You'd need some escaping to handle those.
That's not the only reason we're iffy on this.
Can almost imagine wanting to support labels from
const [count, setCount] = useState(0);const [count, setCount] = useState<"count">(0);Doesn't feel consistent with names in other positions - expression vs. types.
Don't like how you lose the name if you have a union - doesn't feel like it's consistent with the rest of the type system.
Feels like the code you'd need to write for this would be so gross it's not worth the feature.
@weswigham had alternative approach to the use-case in the original issue that doesn't even need dynamic names for tuples.
When it comes to design-time validation and help, open-ended string completions are something we should probably solve first.
Conclusion: