Repository navigation
Description
TypeScript Version: 3.4.2
Search Terms:
Generic variable vs generic function
Generic composition
Generic inference parameter in function
Code
Let's consider this code
/**
* Extract object props by a specific value
*/
type PickByValue<T, ValueType> = Pick<T, {
[Key in keyof T]: T[Key] extends ValueType ? Key : never
}[keyof T]>;
/**
* Map keys by value-compatibility. Non-recursive.
*/
type MatchKeys<T, U> = {
[P in keyof T]: keyof PickByValue<U, T[P]>;
};
/**
* Like keyof, but for value
*/
type ValueOf<A extends Object> = Exclude<A[keyof A], undefined>;
/**
* The src props we have
*/
export interface SrcPropsInterface {
srcAttributeString: string;
srcAttributeNumber: number;
}
/**
* The dest props we want to apply
*/
export interface DestPropsInterface {
destAttributesString: string;
anotherAttributesString: string;
destAttributesNumber: number;
anotherAttributesNumber: number;
}In a variable approach, the following code works perfectly with all completion:
/**
* Here it's perfectly constraint
*/
type Combi = Partial<MatchKeys<SrcPropsInterface, DestPropsInterface>>;
const comb: Combi = {
srcAttributeNumber: "anotherAttributesNumber",
srcAttributeString: 'destAttributesString',
// toto: 'test' // Error as expected
};With a generic function approach, we get some weird things:
declare const obj: DestPropsInterface;
// Try generic way + inference
/**
* Return a object that exposed the missing attributes that still need to be fill somewhere
*/
function map<
DestProps,
T extends Partial<MatchKeys<SrcPropsInterface, DestProps>>
>(data: DestProps, map: T) {
// Consider the Proxy code here
const obj = {} as Pick<DestProps, Exclude<keyof DestProps, ValueOf<T>>>;
return obj;
}Case 1: no completion anymore
const target = map(obj, {
});
But as soon as I write blindly a known attribute from SrcPropsInterface, the type checking work:
const target = map(obj, {
srcAttributeNumber: 'anotherAttributesNumber',
srcAttributeString: 'badValue' // error as expected
});Case 2: any-like
We can put any kind of value and there is no complaint (and naturally no completion):
const target = map(obj, {
srcAttributeNumber: 'anotherAttributesNumber',
foo: 'bar',
bar: 42
});Case 3: no output anymore
As soon as the map object is correctly fill, the target object get correctly all remaining attribute:
const target = map(obj, {
srcAttributeNumber: 'anotherAttributesNumber',
});
target.anotherAttributesString; // Ok
target.destAttributesNumber; // Ok
target.destAttributesString; // Ok
target.anotherAttributesNumber; // Error as expected: we already remove it
But if we provide a wrong map because the type checker doesn't provide the correct feedback, target is converted as never:
const target = map(obj, {
srcAttributeNumber: 'anotherAttributesNumber',
foo: 'bar'
});Expected behavior:
Case 1: get correct type checker in the map parameters like for the variable approach
Case 2: I suppose it's just a side-effect of Case 1, but I expect to have TypeScript wiping me by attempting to introduce unknown parameters.
Case 3: I suppose it's just a side-effect of Case 1 and 2.
Actual behavior:
Check Case 1, Case 2 and Case 3: no type checking on the generic map parameter whereas in a variable approach, it works.
Playground Link:
Playground @ 2019-04-08
Related Issues:
#28938



Activity
MasGaNo commentedon Apr 8, 2019
Btw, everything work perfectly when I set explicitly all generic parameters:
MasGaNo commentedon Apr 8, 2019
Actually, with this approach, I get another strange thing:
I still don't have any completion, but as soon as I write some letters, I remove the hint and ask again for the completion, I get some suggestion:

RyanCavanaugh commentedon Apr 9, 2019
There's a lot going on here I don't understand which part(s) you consider to be the "bug" here - can you simplify this down into a basic example of a small amount of setup + what code you'd expect to error/not error?
MasGaNo commentedon Apr 9, 2019
Ok, I will try to simplify and to isolate which part of the code creates the bug.
MasGaNo commentedon Apr 9, 2019
Just simplified the code, and I think I just figured out the problem, a problem from my side 😓
So there is no bug directly... I will try to explain with this piece of code:
Here if I just write something like, I have as expected some errors:
But as soon as I put a "known" attribute, everything is okay, as it fill the contract of the type as it
extendsthePartialtype:And changing the definition with this approach:
is not helpful at all, as at this moment, P works with inference.
Actually, I wanted to have this effect, but without creating another sub-function and/or passing explicitly all Generic Parameters, but correct me if I'm wrong, seems not to be possible, don't it?:
Buuuuuut, there is still one bug IMHO with the first implementation:
Why don't I have any suggestion?:


Except if I write at least, one letter:
?
RyanCavanaugh commentedon Apr 9, 2019
You don't get suggestions here because
{}is a validP/Partial<T>, so the call is considered 100% valid. Probably you want to define this asmap<T>(data: T, map: Partial<T>)if possibleMasGaNo commentedon Apr 9, 2019
Yes, I can understand that
{}is a validP/Partial<T>, like when we say:But here, I still having all potential known values:

But for development purpose and suggestion, it's very surprising to not having suggestion on all potential values.
It's like with this approach:
Here, we get the full suggestion list:


But after providing the
aattribute, suggestion disappear:It's a little bit weird as experience IMHO, and that's make me confused about my first post, as I wasn't focus on the main problem.
What do you think?
RyanCavanaugh commentedon Apr 9, 2019
It's vexing. The language service is asking the checker what the type of the surrounding object literal is in order to show completions. The valid and correct answer is
{ a: string }, so the completion list is valid and correctly empty becauseahas already been provided.Basically we'd need an entirely different kind of operation here where the language service would ask the checker, What property values would be valid here?, which almost requires a sort of counterfactual reasoning that we're really not capable of. For the specific case of
U extends Partial<T>this is human-understandable, but the type system can't reasonably work backwards through that relationship to understand that the set of property keys forUfalls back toT.MasGaNo commentedon Apr 10, 2019
Sorry, it wasn't my intention ^^"
I don't really know how exactly the language service works under the hood.
I'm trying a different approach that provide us all what we need. So it's absolutely not a priority in comparison of what you already have in the roadmap 🙂
Especially when I see the evolution of TypeScript since I started to use it in 2014, it's absolutely fantastic, and I start all my project with it 😀
Keep continue like that, and we can close this issue if you want 👍
johnrom commentedon May 14, 2021
I think I found another instance of this issue. Check out this TypeScript Playground
"why are you doing this backwards?", please accept my apologies but this is the way.
this way allows a previously typed setFieldValue function to autosuggest names matching values to devs as they type.