Open Nathan-Fenner opened 5 years ago
The use case sounds like a duplicate of #24085, though it seems you've thought through more of the consequences. Let's close this in favor of #24085.
Sounds like you want an "exclusive-or" type operator - similar to #14094.
I don't think that's it since string
and number
are already exclusive so a string xor number
type wouldn't be distinguishable from string | number
. It's more like they want two overloads:
function smallest<T extends string>(x: T[]): T;
function smallest<T extends number>(x: T[]): T;
But not string | number
because smallest([1, "2"])
is likely to be an error.
It is close to a combination of #24085 and #25879 but in a relatively simple way that ensures soundness is preserved.
It only affects generic instantiation and narrowing inside generic functions. No new types or ways of types are being created; an xor operator doesn't do anything to achieve this.
T extends oneof(A, B, C)
means that at least one ofT extends A
,T extends B
,T extends C
holds.
That is what a union constraint does. Do you not mean exactly one of?
That is what a union constraint does.
No because if T = A | B | C
then none of T extends A
, T extends B
, T extends C
holds. (Were you reading extends
backwards?)
True! No I wasn't, but I had parsed it in my head like (T extends A) | (T extends B) | (T extends C)
.
What about a XOR type operator? This would be useful in other scenarios as well. As |
is a valid bitwise operator in JS for OR, it would fit using ^
as XOR operator in types.
function smallest<T extends string ^ number>(x: T[]): T {
if (x.length == 0) {
throw new Error('empty');
}
return x.slice(0).sort()[0];
}
@michaeljota See previous comments for why that doesn't work. There is no type today that could be put inside the extends
to get the desired behavior. Therefore, new type operations cannot solve this problem, since they just allow you (potentially conveniently) write new types. The type number ^ string
is exactly the same as string | number
, since there is no overlap between string
and number
. It does nothing to solve this problem.
This has to be solved by a different kind of constraint rather than a different type in the extends
clause.
You want one of a giving list of types, right? I really don't know much about types, sorry. Still, OR operator can be satisfied with n types of the interception, and a XOR operator should only allow one of the types in the interception.
I understand that if you create a interception, string | number
, you can use only the functions of both types, and you should narrow down to one of those to use those functions. Also, if you declare a generic type as T extends string | number
, then T
would accept string
, number
or string | number
. Then a XOR operator used in a generic type as T extends string ^ number
should only accept strictly one of string
or number
, excluding the interception itself string | number
.
That's not what you would like this to do?
The problem with that approach is that the ^
is incoherent anywhere outside of an "extends" clause, so it becomes (pointless) syntactic sugar.
For example, what should
var x: string ^ number = 4;
do? Are you allowed to reassign it to "hello"
? Or, in more-complicated examples, like:
var x: Array<string ^ number> = [2, 5, "hello"];
why should/shouldn't this be allowed?
There's no coherent way that this can be done. The only way to give it meaning is to only allow it in extends
clauses, where it has the meaning of
type Foo<T extends A ^ B> = {}
// means the same thing as
type Foo<T extends_oneof(A, B)> = {}
It still represents a new type of constraint, not a type operator (because it doesn't produce coherent types).
For example, what should
var x: string ^ number = 4;
do? Are you allowed to reassign it to "hello"?
I think it should. In this case, I guess this would behave the same as string | number
.
Or, in more-complicated examples, like:
var x: Array<string ^ number> = [2, 5, "hello"];
why should/shouldn't this be allowed?
Here it would be useful, as you either want an array of strings, or an array of numbers, but not both. I think, this would be something like:
var x: Array<string ^ number> = [2, 5, 'hello']
~~~~~~~~~~~~~~~ // Error: Argument of type '(string | number)[]' is not assignable to parameter of type 'string[] | number[]'.
// So (string ^ number)[], would be string[] | number[]
This would make #30769 less of a bitter pill to swallow...
And would solve #13995, right?
@jcalz It "solves" it in the sense that (with other compiler work) the following code would compile like you'd expect:
declare function takeA(val: 'A'): void;
export function bounceAndTakeIfA<AB extends_oneof('A', 'B')>(value: AB): AB {
if (value === 'A') {
// we now know statically that AB extends 'A'
takeA(value);
return value;
}
else {
// we now know statically that AB does not extend 'A', so it must extend 'B'
return value;
}
}
It should be noted that besides the actual implementation of extends_oneof
as a new form of constraint, the way that constraints are calculated/propagated for generic types needs to change. Currently, TS never adds/changes/refines generic type variable constraints (because as the conversation above shows, you can never really be sure about the value of the generic type, only of particular values of that type).
This feature makes it possible to soundly refine the constraints for type parameters, but actually doing that refinement would be a bit more work.
Here is anohter example:
I have a numeric library. Now I wan't to generalize it to get number or bigint (but not both).
function add<Num extends bigint | number>(a: Num, b: Num) {
var d1: Num = a + b;
return a + b
}
var d2 = add(1n, 2n);
But I don't have a way to do this.
I suggest the following syntax: T extends T1 || T2
. Readed T exteds type T1 or extends type T2. When I see T extends T1|T2
y read T extends type type T1|T2 i.e. a variable that can holds sometimes T1 and sometimes T2.
function smallest<T extends string||number>(x: T[]): T {
if (x.length == 0) {
throw new Error('empty');
}
return x.slice(0).sort()[0];
}
Extended example from #40851, which I hope could be solved with oneof
:
function useObjectWithSingleNamedKey<Key extends string>(
keyName: Key,
value: string,
fn: (obj: {[key in Key]: string}) => void)
{
fn({ [keyName]: value }) // <-- Fails with `extends string`, but hopefully wouldn't with `oneof`
fn({ [keyName]: value } as { [key in Key]: string }) // <-- Unwanted workaround
}
// Seemingly works with `extends string`:
useObjectWithSingleNamedKey('foobar', 'test', obj => {
console.log(obj.foobar) // <-- Works, and should
console.log(obj.notHere) // <-- Does not work, and should not
});
// But this "works" too, and shows why `extends string` is actually not enough:
useObjectWithSingleNamedKey<'foobar' | 'notHere'>('foobar', 'test', obj => {
console.log(obj.foobar) // <-- Works, and should
console.log(obj.notHere) // <-- Works, but should not
});
Another variant is "exclusive or" ^
:
function smallest<T extends string ^ number>(x: T[]): T { ... }
function sum<T extends string ^ number ^ bigint>(a: T, b: T) { return a + b; }
TypeScript already have disjunction via |
and conjunctions via &
so ^
seems to me look logical here. As generic's constrain this could behave exactly the same as oneof
but here for example:
function sum(a: string ^ number ^ bigint, b: string ^ number ^ bigint) {}
will behave the same as:
function sum(a: string | number | bigint, b: string | number | bigint) {}
except arrays ofc. WDYT?
@MaxGraey have you read previous comments in this thread?
What follows is my opinion. I'm saying this here so I don't have to say "I think" and "in my opinion" in every sentence:
The problem with any suggestion that takes T extends A | B
and does something to A | B
(e.g., T extends A ^ B
, T extends A || B
, T extends A š¤” B
) is that it messes with what TypeScript types are supposed to be: the (possibly infinite) collection of JavaScript values that are assignable to the type.
For example, the type boolean
can be represented by the set { x
where typeof x === "boolean"
}, a.k.a., {true
, false
}, while the type string
can be represented by the set { x
where typeof x === "string"
}. The type unknown
is represented by the the collection { x
where true
} of all JavaScript values , and the type never
is represented by the empty set { x
where false
} of no JavaScript values. Unions and intersections of types correspond to unions and intersections of their corresponding sets.
As soon as you say string ^ boolean
to mean something other than string | boolean
in any context, you're saying "you can't think of string ^ boolean
as a type anymore", at least not in the sense of a set of acceptable values. Since there are no JavaScript values which are both string
and boolean
, the type string ^ boolean
as a set of values is exactly the same as string | boolean
. In order to mean something different, you'd have to say that types are actually collections of all their possible subtypes, only some of which can be cashed out to sets of values. Which is a huge amount of extra cognitive load for what you get out of it.
This could be mitigated by a rule that ^
can only appear in a type if it is in the extends
clause of a generic constraint, similar to how infer
can only modify a type if it is in the extends
clause of a conditional type. But this still puts more burden on TS users than I'd want, since they would undoubtedly try to use A ^ B
in disallowed contexts.
If we are trying to represent "(T extends A)
or (T extends B)
", it is better to think of this as a different sort of constraint and not a different sort of type. That is, change the extends
part of T extends A | B
, not the A | B
part. This leads us to T extends_oneof(A, B)
or T in (A, B)
or T š (A, B)
, etc.
@MaxGraey have you read previous comments in this thread?
Oh no, I missed this! Sorry.
Regarding rest part I agree that context dependent type via "exclusive or" is not perfect solution, but using T extends_oneof A, B
, T in A, B
, T is A, B
via comma separation is bad idea due to it produce ambiguous:
function foo<T in A, B>(a: T, b: T) {} // T is A or B, one parametric type `T`
function foo<T in A, B>(a: T, b: B) {} // T is A, two parametric types `T` and `B`
Perhaps this could be:
function foo<T in A | B>(a: T, b: B) {}
But now we it produce union type which we are just trying to avoid)
(Edited above to change A,B
to (A,B)
) yeah, you need some delimiter... sure T in (A, B)
or maybe T of [A, B]
, or the original proposal extends_oneof(A, B)
T extends A š¤” B
[...]T š (A, B)
LOL. Now if we just had keys on the keyboard for those...
Another variant just make this works as expected š
function sum<T extends number | string, U = T extends number ? number : string>(a: T, b: U) {
return a + b;
}
Another variant just make this works as expected joy
function sum<T extends number | string, U = T extends number ? number : string>(a: T, b: U) { return a + b; }
Nope:
let x: number | string = 5;
let y: string = "hello";
let z = sum(x, y);
function sum<T extends number | string, U = T extends number ? number : string>(
a: T,
b: U
): U extends number ? number : string {
return a + b;
}
let x: number | string = 5;
let y: string = "hello";
let z = sum(x, y); // will infer as string
But this incorrect anyway
But this incorrect anyway
That's the point. It should produce an error, but it doesn't.
Here is a basic User
class:
class User< U > {
private _user: Partial< U > = {};
public set< K extends keyof U >(
key: K,
val: U[ K ],
) : void {
this._user[ key ] = val;
}
}
And a basic Employee
interface:
interface Employee {
name: string;
city: string;
role: number;
}
Now, consider the following implementation:
const admin = new User< Employee >();
admin.set( "name", 10 );
The above script yields the following error which is expected.
Argument of type 'number' is not assignable to parameter of type 'string'.
Now take a look at the following implementation:
const admin = new User< Employee >();
admin.set< "name" | "role" >( "name", 10 );
The above script does not yield any error and it is also expected, as long as 10
is assignable to name
or role
.
To make it more clear, let's take a look at the following script:
const admin = new User< Employee >();
admin.set< "name" | "role" >( "name", true );
The above script yields the following error which is also expected.
Argument of type 'boolean' is not assignable to parameter of type 'string | number'.
Now, what I want to achieve is, 10
should not be allowed to be stored as a value of name
at any mean.
For this, I need to make some changes to my code something similar to the followings:
public set< K extends keyof U >(
key: K,
val: U[ valueof key ],
) : void {
this._user[ key ] = val;
}
But as long as there is nothing like valueof key
, this cannot be achieved dynamically.
For testing, if you replace U[ K ]
by U[ "name" ]
, you will see 10
is no longer allowed anymore at any form.
However, is there any way to achieve my goal?
Many programmers solved similar problems by applying T[ keyof T ]
which I think is not applicable to this case.
Thanks in advance.
@AnmSaiful your problem is not really relevant to this issue (there is a sense in which it is similar - but we do not need to know that a value is a singleton to correctly handle your case) - it has to be with the (lack of) contravariance of assignment, and TypeScript's (deliberately) unsound handling of this case. See my comment for the correct type in your code.
@Nathan-Fenner I also think so, but after seeing @MartinJohns's comment on #41233 I thought perhaps implementation of this feature will also implement the feature I requested.
hegel has this. it's called $StrictUnion
function sum1<T: $StrictUnion<bigint | number | string>>(a: T, b: T): T {
return a + b
}
function sum2<T: bigint | number | string>(a: T, b: T): T {// error
return a + b
}
function sum3(a, b) {// infered as <T: $StrictUnion<bigint | number | string>>(T, T) => T
return a + b
}
hegel has this. it's called $StrictUnion
Perhaps $DiscrimanatedUnion
or $DisjointUnion
could be a better name
btw this may relate to nominal types. With nominal type it could be represent as:
function sum1<T extends new bigint | new number | new string>>(a: T, b: T): T {
return a + b;
}
or with variadic types (or something like this):
type DisjointUnion<F, T extends unknown[]> = Union<new ...T>;
function sum1<T extends DisjointUnion<bigint, number, string>>(a: T, b: T): T {
return a + b;
}
Another variant (proposal):
function sum1<T extends bigint | number | string, U infer T>(a: T, b: U): U {
return a + b;
}
in this constarein context infer
means as "sync" T
and U
to one exclusive type
@MaxGraey that example does not work
@thecotne it shouldn't work. It's just proposal) marked this as proposal in original message
@MaxGraey sorry for some reason i read "Another variant" as if it's a workaround ...
I see. Sorry for this misleading
However this already works!
function sum1<T extends bigint | number | string, U extends T>(a: T, b: U): U {
return a + b;
}
sum1('abc' as string, 123); // expected error
sum1(123, 'abc'); // expected error
sum1('abc' as string, 'bcd'); // ok!
sum1(123 as number, 456); // ok!
sum1(123, 123); // ok!
The only inconvenience is associated with casting to a more general type ('abc' as string)
However this already works!
It does not.
// No error.
sum1<string | number, number>('abc', 123);
So that's why better expose new mechanics instead U extends T
. I propose make this something like U infer T
Here's another example for a use-case with a type map that expresses covariance between two fields of an object according to a predefined map.
type MyTypeMap = {
MyString: string;
MyNumber: number;
};
type MyTypedValue<T extends keyof MyTypeMap> = {
typeName: T;
value: MyTypeMap[T];
};
const good: MyTypedValue<'MyString'> = { typeName: 'MyString', value: 'string' }; // can't write 1, would be a type-error:
const bad: MyTypedValue<'MyString' | 'MyNumber'> = { typeName: 'MyString', value: 1 }; // Mixing string & number
The idea here is to express a certain type mapping and enforce it in an object that carries values of the RHS of the type mapping while also carrying the LHS as a reified witness so other parts of the program know can get a richer type information.
This also can't be expressed as a series of type-guards instead of using the type map.
A workaround is to not export MyTypedValue
as generic, but instead specify all generic types, in a union and export that:
export MyRealTypedValue = MyTypedValue<'MyString'> | MyTypedValue<'MyNumber'>
Seems I potentially made a duplicate requesting similar feature on https://github.com/microsoft/TypeScript/issues/46236# . My use case is as following:
type KindA = {kind:'a'};
type KindB = {kind:'b'};
type KindC = {kind:'c'};
type AllKinds = KindA | KindB | KindC;
function create<T ??? AllKinds>(kind:T['kind']):T {
switch(kind) {
case "a": return {kind:'a'};
case "b": return {kind:'b'};
case "c": return {kind:'c'};
}
}
create("a"); // KindA
Where:
I wouldnt mind using some other keyword then extends
or extends oneof
for example is
would make good sense:
function create<T is AllKinds>(kind:T['kind']):T
Almost created a duplicate and then found this issue, will leave my example (which represents a more complicated real-life use case) here:
enum Entity {
A = "a",
B = "b",
C = "c"
}
type EntityPayload<E extends Entity> = {
[Entity.A]: { a: string };
[Entity.B]: { b: number };
[Entity.C]: { c: boolean };
}[E];
function payloadToString<E extends_oneof(Entity)>(
entity: E,
payload: EntityPayload<E>
): string { // <-- simpler case where return type is the same
switch (entity) {
case Entity.A: // <-- here E should be inferred as exactly Entity.A
return payload.a; // <-- we could be sure here that `payload` is exactly `{ a: string }`
case Entity.B:
return `number ${payload.b * 5}`;
case Entity.C:
return payload.c ? "true" : "false";
default:
return never(entity); // <-- can check for exhaustiveness properly
}
}
declare function never(x: never): never;
function transformPayload<E extends_oneof(Entity)>(
entity: E,
payload: EntityPayload<E>
): EntityPayload<E> { // <-- result type should also be inferred properly
switch (entity) {
case Entity.A:
return { a: payload.a + "xyz" }; // <-- here we can both use `payload.a` and match return result against `EntityPayload<Entity.A>`
case Entity.B:
return { b: payload.b * 5 };
case Entity.C:
return { c: !payload.c };
default:
return never(entity);
}
}
@Nathan-Fenner
It seems like the your proposed functionality can already be achieved by straightforwardly following the definition of the problem.
function smallestString(a:string[]):string{return "";}
function smallestNumber(a:number[]):number{return 0;}
type SmallestString = typeof smallestString
type SmallestNumber = typeof smallestNumber
type Smallest = SmallestString|SmallestNumber
function smallest<T extends Smallest>(...args:Parameters<T>):ReturnType<T>{
if (Math.random()<0.5) return '' as ReturnType<T>;
else return 0 as ReturnType<T>;
}
smallest([1,'a']); // error
// Argument of type '[(string | number)[]]' is not assignable to parameter of type '[a: string[]] | [a: number[]]'.
// Type '[(string | number)[]]' is not assignable to type '[a: number[]]'.
// Type '(string | number)[]' is not assignable to type 'number[]'.
// Type 'string | number' is not assignable to type 'number'.
// Type 'string' is not assignable to type 'number'.ts(2345)
smallest([1,2]); // OK
smallest(['1','2']); // OK
Of course the actual implementation of smallest
has to be correct. Unlike the demo dummy implementation above, it has to stay in the correct lane.
On the typescript playground it seems to work for all available versions 3.3.3333 - latest 4.5.4.
The discussion following your original example goes off in myriad directions, and this solution might not apply in all those cases.
NOTE: In case the original target union has many elements, the distributive conditional can assist --
type Any = "A" | "B" | "C" | "D" | "E"
type Fn<T> = (arg:T[])=>T
type FnAny_<T> = T extends any ? Fn<T> : never;
type FnAny = FnAny_<Any>
function fnAny<T extends FnAny>(...a:Parameters<T>):ReturnType<T>{
if (Math.random()<0.5)
return undefined as unknown as ReturnType<T>
else
return undefined as unknown as ReturnType<T>
}
fnAny(["A","A"]) // ok
fnAny(["A","E"]) // type check ERROR
@craigphicks
It does not works.
See at: play ground
You can detect (o check) parameters in the call, but you cannot use the correct types inside the function.
If you look at line 10, the type of the variables element1
and element2
are number|string
not one of each:
function smallestString(a:string[]):string{return "";}
function smallestNumber(a:number[]):number{return 0;}
type SmallestString = typeof smallestString
type SmallestNumber = typeof smallestNumber
type Smallest = SmallestString|SmallestNumber
function smallest<T extends Smallest>(...args:Parameters<T>):ReturnType<T>{
var [array] = args;
var [element1, element2] = array; // line 10, element1 is string|number
return element1 < element2 ? element1 : element2;
}
@emilioplatzer - You have a different definition of "working".
Let me explain my intended meaning of correctness :
The type at the interface exposed to the user the type seen is
(parameter) args: [a: string[]] | [a: number[]]
as show by GUI. That is not an array of mixed type number|string
, but an exclusive-or of any array of number or a an array of strings. That is exactly what the OP was requesting.
Why, when the type at the interface is correct, does the internal implementation require (multiple) coercion? I think we can put that down to priorities in Typescript development - the ease of use and correctness of an interface presented to the external world is a higher priority than crossing all t's so that an internal implementation requires no coercion while remaining perfectly correct.
Pointing out the minor inconveniences remaining on the internal side does not the negate the correctness of the type being presented to external side.
The reason I left out the internal implementation in my first post is exactly to highlight and assert the correctness of the externally present typing.
However, for the record here is a complete implementation of smallest
, complete with coercion(s) - not so hard to write.
function smallestString(a:string[]):string{return "";}
function smallestNumber(a:number[]):number{return 0;}
type SmallestString = typeof smallestString
type SmallestNumber = typeof smallestNumber
type Smallest = SmallestString|SmallestNumber
function smallest<T extends Smallest>(...args:Parameters<T>):ReturnType<T>{
if (!args.length) throw "";
if (typeof args[0]==='number') {
return Math.min( ...(args as any as number[])) as ReturnType<T>;
} else {
let min = args[0];
for (let a of args.slice(1))
if (a<min) min = a;
return min as ReturnType<T>;
}
}
smallest([1,'a',1); // error
// Argument of type '[(string | number)[]]' is not assignable to parameter of type '[a: string[]] | [a: number[]]'.
// Type '[(string | number)[]]' is not assignable to type '[a: number[]]'.
// Type '(string | number)[]' is not assignable to type 'number[]'.
// Type 'string | number' is not assignable to type 'number'.
// Type 'string' is not assignable to type 'number'.ts(2345)
smallest([1,2]); // OK
smallest(['1','2']); // OK
const a1=[1,'1'] as const;
const a2=[1,'1'];
const a3 = f();
function f():(number|string)[]{
const a:(number|string)[]=[];
return a;
}
smallest(a1); // error
smallest(a2); // error
smallest(a3); // error
@craigphicks, You are right, It works. I apologize.
Your post works as a complete workarownd for "extends oneof" generic constraints that allows for narrowing type parameters in the interface of a function. That's very good for all.
But we still need a way to have generic constraints that allows for narrowing type parameters inside of the implementation of a function.
For example I write a generic function for check digits (like last digit of ISBN). Some number are very big, in that cases I must use BigInts, but when not Number is better because is faster.
It's a shame typescript can't do type checking in this case.
function checkdigit<Num extends bigint|number>(number:any, cast:(numbreString:string|number)=>Num, multipliers:Num[], divider:Num, shift?:Num, turn?:boolean):Num|null{
var digitos=number.toString().split('');
var i=0;
var sumador:Num = cast(0);
while(digitos.length){
var digito = cast(digitos.pop()||0);
var multiplicador = multipliers[i];
// problem here:
var producto:Num = digito * multiplicador;
// problem here:
sumador = sumador + producto;
i++;
}
if(shift){
// problem here:
sumador = sumador + shift;
}
// problem here:
var remainder:Num = sumador % divider;
if(!remainder) return cast(0);
if(divider-remainder>9) return null;
// problem here:
return turn ? divider-remainder : remainder;
}
var isbnBook1 = "007140638" // 7
var isbnBook2 = "046505065" // 4
console.log(checkdigit(isbnBook1, Number, [9,8,7,6,5,4,3,2,1], 11), "= 7?");
console.log(checkdigit(isbnBook2, BigInt, [9n,8n,7n,6n,5n,4n,3n,2n,1n], 11n), "= 4?");
In this special case, and because I need that the number can be a string (for the trainling zeros), I use another workaround for the interface. I pass the "cast function". Your solution is better, is more geneirc.
But we still have the problem inside functions.
@emilioplatzer
I have refactored your code:
function checkdigit(...args:Parameters<CheckdigitNumber>):ReturnType<CheckdigitNumber>;
function checkdigit(...args:Parameters<CheckdigitBigint>):ReturnType<CheckdigitBigint>;
function checkdigit<F extends CheckdigitBigint>
OR
function checkdigit<F extends CheckdigitNumber>
You have to try both to make sure it passes compile.
3..2. so that arguments use rest notation (...args:Parameters<F>)
3..3. so that the return type is :ReturnType<F>
3.3. so that the parameter names are extracted at the top of the body
const [number, cast, multipliers, divider, shift, turn] = args as
3.4. the rest of the body remains exactly as in your original code.
Two achievements:
as ReturnType<F>
, but you have to substitute in each Unfortunately the part 3.1. (substitute in EACH type one at a time) is a huge flaw. We want the compiler to do that automatically. Maybe that is a proposal?
The reason for having to append as ReturnType<F>
might be related bug issue #22617. The return type problem looks similar to #40111, which was merged into #22617. I will inquire about that.
type Checkdigit<Num extends bigint|number> = (
number:any,
cast:(numbreString:string|number)=>Num,
multipliers:Num[],
divider:Num,
shift?:Num,
turn?:boolean
)=>Num|null
type CheckdigitNumber = Checkdigit<number>
type CheckdigitBigint = Checkdigit<bigint>
function checkdigit(...args:Parameters<CheckdigitNumber>):ReturnType<CheckdigitNumber>;
function checkdigit(...args:Parameters<CheckdigitBigint>):ReturnType<CheckdigitBigint>;
// We have to substitute manually each of
// F extends CheckdigitBigint
// F extends CheckdigitNumber
// to test compile. If both succeed, it works.
function checkdigit<F extends CheckdigitBigint>(...args:Parameters<F>):ReturnType<F>{
const [number, cast, multipliers, divider, shift, turn] = args as Parameters<F>;
var digitos=number.toString().split('');
var i=0;
var sumador = cast(0);
while(digitos.length){
var digito = cast(digitos.pop()||0);
var multiplicador = multipliers[i];
var producto = digito * multiplicador;
sumador = sumador + producto;
i++;
}
if(shift){
sumador = sumador + shift;
}
var remainder = sumador % divider;
if(!remainder) return cast(0) as ReturnType<F>; // return type coercion required (bug?)
if(divider-remainder>9) return null as ReturnType<F>; // return type coercion required (bug?)
return (turn ? divider-remainder : remainder) as ReturnType<F> // return type coercion required (bug?)
}
Search Terms
Suggestion
Add a new kind of generic type bound, similar to
T extends C
but of the formT extends oneof(A, B, C)
.(Please bikeshed the semantics, not the syntax. I know this version is not great to write, but it is backwards compatible.)
Similar to
T extends C
, when the type parameter is determined (either explicitly or through inference), the compiler would check that the constraint holds.T extends oneof(A, B, C)
means that at least one ofT extends A
,T extends B
,T extends C
holds. So, for example, in a functionJust like today, these would be legal:
But (unlike using
extends
) the following would be illegal:Use Cases / Examples
What this would open up is the ability to narrow generic parameters by putting type guards on values inside functions:
This can't be safely done using
extends
, since looking at one item (even if there's only one item) can't tell you anything aboutT
; only about that object's dynamic type.Unresolved: Syntax
The actual syntax isn't really important to me; I just would like to be able to get narrowing of generic types in a principled way.
(EDIT:) Note: despite the initial appearance,
oneof(...)
is not a type operator. The abstract syntax parse would be more likeT extends_oneof(A, B, C)
; theoneof
and theextends
are not separate.Checklist
My suggestion meets these guidelines:
(any solution will reserve new syntax, so it's not a breaking change, and it only affects flow / type narrowing so no runtime component is needed)