Home > Net >  Type U (generic) is not assignable to type unknown
Type U (generic) is not assignable to type unknown

Time:03-18

I have a generic Repository class and each Repository has its own Sub-Repositories. The Sub-Repositories and kept by a Record {String: Repository}. It looks like this:

export class Repository<T> {
  private readonly collection: Record<string, T> = {};
  private readonly subrepositories: Record<string, Repository<unknown>> = {};
.
.
  subrepository<U>(docName: string) {
    if (!this.subrepositories[docName]) {
      this.subrepositories[docName] = new Repository<U>();
    }
    return this.subrepositories[docName];
  }


In this line: this.subrepositories[docName] = new Repository<U>(); I get typescript error:

TS2322: Type 'Repository<U>' is not assignable to type 'Repository<unknown>'.
Types of property 'set' are incompatible.
Type '(id: string, data: U) => void' is not assignable to type '(id: string, data: unknown) => void'.
Types of parameters 'data' and 'data' are incompatible.
Type 'unknown' is not assignable to type 'U'.
'U' could be instantiated with an arbitrary type which could be unrelated to 'unknown'.

The only way I managed to overcome the issue is by casting this.subrepositories first to unknown and then to Record<string, Repository<U> like that:

  subrepository<U>(docName: string) {
    if (!this.subrepositories[docName]) {
      (this.subrepositories as unknown as Record<string, Repository<U>>)[
        docName
      ] = new Repository<U>();
    }
    return this.subrepositories[docName];
  }

But is it really a solution?

CodePudding user response:

Your type is probably invariant on the type parameter T. This happens if you use a type parameter in both a covariant position (such as a field) and a contravariant position (such a as a function parameter). You can learn more about variance here

export class Repository<T> {
  private readonly collection: Record<string, T> = {};
  private readonly subrepositories: Record<string, Repository<unknown>> = {};
  fn!: (p: T) => void; // Remove this and asssignability works
  subrepository<U>(docName: string) {
    if (!this.subrepositories[docName]) {
      this.subrepositories[docName] = new Repository<U>();
    }
    return this.subrepositories[docName];
  }
}

Playground Link

The reason this doesn't work is that it is actually not type safe. Since you could pass in a type that wasn't expected by the function.

  sampleMethod() {
      const rep = this.subrepository<{ a: number} >("foo");
      rep.fn =  p => p.a.toFixed();

      
      const repAlias = this.subrepository<{ a: string} >("foo");
      repAlias.fn({ a: "" }) //            
  • Related