-
Notifications
You must be signed in to change notification settings - Fork 13k
Description
Omitting prototype
from keyof
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 justSomeClass
, 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 toValueTypesOfClass
- Grabbing all of the types of values on
- 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.
- Must check out Mongoose break. Believe it's an asymmetry between how we handle
Making const
Contexts/Type Parameters Easier for Arrays
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 inferunknown[]
because it omittedreadonly
.- Why?
const
pushes the type of the array towardsreadonly [1, 2, 3]
- but that's not assignable tounknown[]
.Array
has methods for mutation, whereasReadonlyArray
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 thereadonly
-ness of the tuple as a whole. -
Note the change isn't simply restricted to
const
type parameters - it also works withas const
when an outer contextual type has a mutable array type - which means thata
andb
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[]
.
- No, in fact we think it composes better. You can do
-
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 writeconst
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
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?
- Seems dubious.
- 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.
- But that doesn't work. You'd need
-
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.]]