-
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
Custom Type Operator #13500
Comments
Type operators are used to generate types, but that is not the only place where understanding them is needed. for instance when doing an assignablity checks between two type operators the system needs to understand what they mean, for example to check if The type system also needs to know what types mean when it is doing inferences. Inferences are really the inverse of creating the type. again if creating the type involves running code, the system can not infer to them. Doing this really needs extensbility to the compiler itself, allowing adding code in all places where knowledge about types is needed, e.g. assignablity checking, inference, as well as resolution. |
I'm just trying to figure out how it cannot work. Could you give me an example using the proposed custom type operator? For instance with type Partial<T> => {
type Type;
for (K in T) {
Type[K?] = T[K];
}
return Type;
}
interface State {
a: number;
b: number;
}
function setState(state: Partial<State>) {
} What's the difference, compare too mapped types? Why does mapped types works but not the other? type Partial<T> = {
[P in keyof T]?: T[P];
};
interface State {
a: number;
b: number;
}
function setState(state: Partial<State>) {
} Isn't it nearly the same code being run on the compiler, in both example? Even though one of the code needs to be interpreted, the same logic is being run? |
I had a similar wish - for example I would like to have a partial type where some specific properties are still required:
I had other cases as well, for example, add two properties for each in the source ( I previously needed complex code generators for what can now be solved with |
type P<T extends WithKey> = {
// all properties are optional
[P in keyof T]?: T[P];
} & {
// but keep "key" property as mandatory.
key: string;
}; |
can we solve type DeepPartial<T> = {
[P in keyof T]?: DeepPartial<T[P]>;
}; this appears to work from light testing, but I don't have a practical use case to really make sure it makes sense. |
@patsissons Your solution worked for my real use case. I think it would make a good addition to the core lib type definitions alongside |
@patsissons I arrived at your solution but it is only now that I see some issues. Consider the following: interface IName {
name: string;
}
interface IType extends IName {
typeName: IName;
color: string;
}
interface IRoom extends IName {
roomType: IType;
}
interface IUser extends IName {
room: IRoom;
}
type DeepPartial<T> = {[P in keyof T]?: DeepPartial<T[P]>; };
const p0: Partial<IUser> = { room: 0 }; // TS complains, room should be IRoom | undefined
const p1: DeepPartial<IUser> = { room: 0 }; // ? Shouldn't room be of type DeepPartial<IRoom> | undefined
const p2: DeepPartial<IUser> = { room: {} }; // OK, we have a DeepPartial<IRoom>
const p3: DeepPartial<IUser> = { room: { a: 0 } }; // TS complains, a doesn't exists in DeepPartial<IRoom> | undefined
const p4: DeepPartial<IUser> = { room: { roomType: 0 } };
const p5: DeepPartial<IUser> = { room: { roomType: { } } };
const p6: DeepPartial<IUser> = { room: { roomType: { a: 0 } } };
const p7: DeepPartial<IUser> = { room: { roomType: { typeName: 0 } } };
const p8: DeepPartial<IUser> = { room: { roomType: { typeName: {} } } };
const p9: DeepPartial<IUser> = { room: { roomType: { typeName: { a: 0 } } } };
const p10: DeepPartial<IUser> = { room: { roomType: { typeName: { name: 0 } } } }; I have written some examples which you can run in the typescript playground. Some examples are good but some others are questionable. For instance, do you happen to know why the definition for |
@patsissons: sounds like @tinganho: if you'd be okay with a more functional version of your code (i.e. no mutating, incl. * type DbArgument<F extends Fields> => {
type Type = {};
// ...
return ChangedType;
} type DbArgument<
F extends Fields,
Types extends {},
// ^ note you can make declarations here, which may include processing!
// ...
> = ChangedType; Other functionality you used:
Once #6606 lands, these are all good (details). Iteration (where not covered by mapped types):
|
Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed. |
Overview
There are some holes in the TS type system that's really hard to cover. Since, special casing syntaxes for a specific use case is not very efficient, nonetheless a system with a lot of special cases is not a good system. Here are some current proposals, of special cases with special syntaxes in the TS issue tracker:
deepPartial
to deep optionalize a type Partial Types (Optionalized Properties for Existing Types) #4889.rest
to get the rest part of a type Rest type #13470.But, I have also had a very strong desire to strongly type an ORM(Object Relational Mapping) and strongly type data models, where I don't need to repeat the code a lot. Below, is a real world case where, I use the popular ORM, sequelize, to define a comment model:
As you can see, it is quite tedious, to have to write just one model. I need to define a type for the argument side,
AComment
, and one for the instance side of model,IComment
, and at the end I need to send in the values to the factory function to produce a SQL table for that model. What I don't understand is, we have all the types inferred from the factory function's argument. Why can't we get the type from there to produceAComment
andIComment
?Proposal
I propose a solution, I call custom type operator. So in layman terms, it takes a couple of type arguments and produces a new type:
In below, we added two custom type operator
DbArgument<F>
andDbInstance<F>
, so that we can skip to writeIComment
andAComment
in the previous example.With the value argument we produced a type for the instance side
IComment
and one for the argument sideAComment
. And all the user have to write is the argument values to the factory functiondefine
:You can regard it as the equivalent to a function in the value space. But instead of the value space it operates in the type space.
Syntax
Definition
A Custom Type Operator's body need to return a type and can never reference a value.
Example
Grammar
Type Relations
A == B
returnstrue
ifB
is invariant toA
elsefalse
.A < B
returnstrue
ifB
is a sub type ofA
elsefalse
.A <= B
returnstrue
ifB
is a sub type ofA
or invariant elsefalse
.A > B
returnstrue
ifB
is a super type ofA
elsefalse
.A >= B
returnstrue
ifB
is a super type ofA
or invariant elsefalse
.A != B
returnstrue
ifB
is not invariant toA
elsefalse
.case A
(switch (T)) the same asT == A
.T
isfalsy
if it contains the types''
,0
,false
,null
,undefined
.T
istruthy
when it is notfalsy
.Assignment Operators
A &= B
is equal toA = A & B
;A |= B
is equal toA = A | B
;A['k']: string
is equal toA = A & { k: string }
;A['k'?]: string
is equal toA = A & { k?: string }
;A[readonly 'k']: string
is equal toA = A & { readonly k: string }
;If Statement
Example
Grammar
Switch Statement
A case statement in a switch statement, evaluates in the same way as the binary type operator
==
Example
Grammar
For-In Statement
Loop through each property in a type.
Example
Grammar
For-of Statement
Loop through an array / tuple.
Example
Grammar
Examples
DeepPartial
Rest
cc @sandersn
The text was updated successfully, but these errors were encountered: