The values of Method<T> must be Methods, which can only be "firstName", "lastName", or "email". In your generic example:
function validate<T>(mapping: Mapping<T>) {
const validationFuncs: Method<T>[] = mapping.map(m => {
return typeof m === 'string' ? { [m]: m } : m;
})
}
the type T can be anything... for example, {nope: string}. In this case, keyof T is "nope", and the declared type of validationFuncs is {nope?: Methods}[].
But if mapping is ["nope"] (a valid Mapping<{nope: string}>), then validationFuncs will be [{nope: "nope"}] at runtime. But that's not a {nope?: Methods}[], because validationFuncs[0].nope is "nope" instead of undefined or any of the three allowable values for Methods. So the compiler warns you about that. This all makes sense to me.
In your non-generic, "working" example:
const validationFuncs: Method<Fields>[] = mapping.map(m => {
return typeof m === 'string' ? { [m]: m } : m;
})
something weird is happening. Method<Fields> is equivalent to
type MethodFields = {
firstName?: Methods
lastname?: Methods
e?: Methods
}
But the type checking of { [m]: m } isn't working properly because of a TypeScript bug with computed keys which might be fixed in TypeScript 2.6; not sure.
The compiler should (but does not) realize that { [m]: m } is only guaranteed to be {firstName:"firstName"}, {lastname:"lastname"}, or {e:"e"}, the last two of which are not valid Method<Fields> elements (notice the lowercase "n" in lastname). Instead, the type checker widens the type of { [m]: m } to something like { [k: string]: keyof Fields }, which is apparently wide enough to match Method<Fields>, not that I understand how. Anyway, it type checks when it shouldn't; it's a bug or a design limitation in TypeScript.
In both cases you're not implementing your code in such a way to conform with the types you've declared. I can't tell whether your types are right but the implementation is wrong, or vice versa, or something else. I hope you have enough information now to make progress. Good luck!