Closed
Description
TypeScript Version: 3.6.0-dev.20190608
Search Terms:
- not assignable generic parameter with default value
- generic class assign constructor function specialization
Code
export class Entity {
// members are not important
}
/**
* A generic interface.
*
* NOTE:
* The first generic parameter is required,
* the second one is optional.
*/
export interface EntityCrudRepository<
T extends Entity,
Relations extends object = {}
> {
findFirst(): Promise<T & Relations>;
}
/**
* A generic class implementing the generic interface.
*/
export class DefaultCrudRepository<
T extends Entity,
Relations extends object = {}
> implements EntityCrudRepository<T, Relations> {
constructor(
private entityClass: typeof Entity & {prototype: T},
private dataSource: object,
) {}
async findFirst(): Promise<T & Relations> {
// dummy implementation, not important
return {} as (T & Relations);
}
}
/**
* A type representing any generic class that's implementing
* our generic interface and providing a two-arg constructor.
* E.g. `DefaultCrudRepository`, but also any other class
* matching the required API contracts.
*/
export type CrudRepositoryCtor = new <
T extends Entity,
Relations extends object
>(
entityClass: typeof Entity & {prototype: T},
dataSource: object,
) => EntityCrudRepository<T, Relations>;
/**
* A test-suite builder wants to accept any `EntityCrudRepository`
* implementation, so that we can define the same set of tests
* for different repository classes.
*/
export function crudRepositoryTestSuite(
dataSourceOptions: object,
repositoryClass: CrudRepositoryCtor,
) {
// write tests calling `repositoryClass` ctor to obtain
// an instance of the repository for the given Entity and DataSource
// (entities are defined by individual test cases)
}
/**
* Usage in tests.
*/
//describe('DefaultCrudRepository with memory connector', () => {
crudRepositoryTestSuite(
{connector: 'memory'},
DefaultCrudRepository,
);
//});
Expected behavior:
The program compiles with no errors.
Actual behavior:
$ tsc --lib es2017 tsbug.ts
tsbug.ts:46:5 - error TS2345: Argument of type 'typeof DefaultCrudRepository' is not assignable to parameter of type 'CrudRepositoryCtor'.
Type 'DefaultCrudRepository<Entity, {}>' is not assignable to type 'EntityCrudRepository<T, Relations>'.
Types of property 'findFirst' are incompatible.
Type '() => Promise<Entity>' is not assignable to type '() => Promise<T & Relations>'.
Type 'Promise<Entity>' is not assignable to type 'Promise<T & Relations>'.
Type 'Entity' is not assignable to type 'T & Relations'.
Type 'Entity' is not assignable to type 'T'.
'Entity' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Entity'.
46 DefaultCrudRepository,
~~~~~~~~~~~~~~~~~~~~~
Found 1 error.
This part seems most relevant to me:
Type 'DefaultCrudRepository<Entity, {}>' is not assignable to type 'EntityCrudRepository<T, Relations>'
IIUC, the compiler is not able to treat DefaultCrudRepository
as a generic constructor, instead if specializes the constructor function too early using "sensible" default values for generic arguments.
Playground Link:
Unfortunately, I am not able to reproduce the problem in the Playground.
Related Issues:
- constraint check failure if a type that satisfies the constraint is used in the generic parameter default #31159 I tested older TypeScript versions to check if my issue is perhaps a regression introduced by Instantiate constraint with default upon comparison #31240, but with no luck. Versions tested:
3.4.5
,3.0.3
.