TypeScript Version: 3.6.4 & 3.7.2 & 3.8.0-dev.20191112
Search Terms: generic constraints, TS2322, 2322
Code
export class Model<GenericSchema extends { id: string }> {
public fails(): GenericSchema {
return { id: '' }; // <-- This line produce the error, see below
}
public works(): GenericSchema {
return { id: '' } as GenericSchema;
}
}
Expected behavior:
Should compile without errors. To me this should work since the the fails() method returns an object which satisfies the constraint { id: string }.
Actual behavior:
3:5 - error TS2322: Type '{ id: string; }' is not assignable to type 'GenericSchema'.
'{ id: string; }' is assignable to the constraint of type 'GenericSchema', but 'GenericSchema' could be instantiated with a different subtype of constraint '{ id: string; }'.
Context:
For additional context, I originally filed an issue with @types/mongodb which gives the original use case
https://github.com/DefinitelyTyped/DefinitelyTyped/issues/39358
Last, here is a discussion of the exact same matter on Stack Overflow
https://stackoverflow.com/questions/58663733/typescript-class-generic-constraint
Playground Link:
https://codesandbox.io/s/mapped-type-with-generics-l4b0b
Related Issues:
I believe this is related to https://github.com/microsoft/TypeScript/issues/34567 and as such probably also https://github.com/microsoft/TypeScript/issues/33014.
The error here is correct. Model could be instantiated, for example, with GenericSchema = { id: string, foo: string }, which is assignable to { id: string }. The inverse, however, is not true.
To me this should work since the the fails() method returns an object which satisfies the constraint { id: string }.
It satisfies the constraint, yes, but it doesn't satisfy all possible types that GenericSchema could be. Type parameters are universally quantified--that's the purpose of using a generic.
Here's a concrete example of the kind of error TS is trying to protect you from,
export class Model<GenericSchema extends { id: string }> {
public fails(): GenericSchema {
return { id: '' }; // <-- This line produce the error, see below
}
public works(): GenericSchema {
return { id: '' } as GenericSchema;
}
}
const model = new Model<{id:string, x:string}>();
const obj = model.works();
console.log(obj.x); //undefined, but supposed to be string, right?
Thanks a lot for the example, that makes a lot of sense!
In my real use case I'm accessing a database so it would be OK for obj.x to be out of sync, since this would be a developer error (providing the wrong type for the data model in the DB). That said, to me it now (with your help) seems the TS compile is handling the above example correctly.
Now, if I change the example to be a bit closer to a real use case with an external db driver module, it may look something like this. And now the compiler accepts the generic as a return type, without the type assertion - which also makes sense.
// TS don't know what's in the DB (obviously)
const dataInDb: any = {id: '', stuff: ''}
// DB Driver takes a generic which defines what is
// in the DB (up to the developer to make sure the
// generic match the data model)
function readDb<Schema>():Schema {
return dataInDb
}
class Model<Schema extends { id: string }> {
// This now works without the type assertion
public read(): Schema {
return readDb<Schema>();
}
}
const model = new Model<{id:string, x:string}>();
const obj = model.read();
console.log(obj.x); //undefined
A more contrived example:
class Model<GenericSchema extends { id: string }> {
public fails(): GenericSchema {
return { id: '' } as any; // <-- This line does not produce any error anymore
}
public works(): GenericSchema {
return { id: '' } as GenericSchema;
}
}
const model = new Model<{id:string, x:string}>();
const obj = model.works();
console.log(obj.x); //undefined
In summary, my initial example was simply not correct and both examples works as expected. I'll close this issue and bring the matter back over to the @types/mongodb https://github.com/DefinitelyTyped/DefinitelyTyped/issues/39358 package.
Big thanks for your help @fatcerberus and @AnyhowStep!
Most helpful comment
https://twitter.com/SeaRyanC/status/1121995986862084096