Home > Blockchain >  Typescript error when using generic T extends Type on container class but not with direct reference
Typescript error when using generic T extends Type on container class but not with direct reference

Time:06-18

in the following code, if I use T extends HealthyConfig and create a Config class instance using the generic T I get an error of Type 'boolean' does not satisfy the constraint 'T[keyof T]', but when directly referencing HealthyConfig no error is displayed. Surely because T extends HealthyConfig the types should be known as far as at least those defined in HealthyConfig so why doesn't it work with using T?

type Key = string | number;
type Val = Key | boolean | Obj;
type Obj = { [x: Key]: Val } | Array<Val>;

interface Config<T extends Obj> {
    get<Tret extends T[keyof T]>(key: keyof T, defaultVal: Tret): Tret;
}

type EnabledConfig = {
    enabled?: boolean;
}

class Enabled<T extends EnabledConfig> implements Config<T> {
    public readonly c: T;
    constructor(c?: T) {
        this.c = c || {} as T;
    }
    
    get<Tret extends T[keyof T]>(key: keyof T, defaultVal: Tret): Tret {
        const result: Tret = this.c[key] as Tret;
        return (result === undefined) ? defaultVal : result;
    }
}

type Status = 'yes' | 'no';

type HealthyConfig = EnabledConfig & {
    healthy?: Status;
}

class Healthy {
    public readonly config: Config<HealthyConfig>
    constructor(config: Config<HealthyConfig>) {
        this.config = config;
    }
    get enabled(): boolean {
        return this.config.get<boolean>('enabled', true);
    }
}

const en = new Enabled<HealthyConfig>({enabled: false});
const isEnabled: boolean = en.get<boolean>('enabled', true); // works
const isHealthy: Status = en.get<Status>('healthy', 'no'); // works

const h = new Healthy(new Enabled<HealthyConfig>({enabled: true, healthy: 'no'}));
const enabled: boolean = h.config.get<boolean>('enabled', true); // works
const healthy: Status = h.config.get<Status>('healthy', 'yes'); // works

// This generic "T extends HealthyConfig" causes a problem 
// with the "this.config.get" showing an error on the "<boolean>"
// of "Type 'boolean' does not satisfy the constraint 'T[keyof T]'"
class UnHealthy<T extends HealthyConfig> {
    public readonly config: Config<T>
    constructor(config: Config<T>) {
        this.config = config;
    }
    get enabled(): boolean {
        return this.config.get<boolean>('enabled', true);
    }
}

CodePudding user response:

I've made a little refactor of your code:

type Key = string | number;
type Val = Key | boolean | Obj;
type Obj = { [x: Key]: Val } | Array<Val>;

interface Config<T extends Obj> {
    get<K extends keyof T, V extends T[K]>(key: K, defaultVal: V): V;
}

type EnabledConfig = {
    enabled?: boolean;
}

class Enabled<T extends EnabledConfig> implements Config<T> {
    public readonly c: T;
    constructor(c?: T) {
        this.c = c || {} as T;
    }
    
    get<K extends keyof T, V extends T[K]>(key: K, defaultVal: V): V {
        const result: V = this.c[key] as V;
        return (result === undefined) ? defaultVal : result;
    }
}

type Status = 'yes' | 'no';

type HealthyConfig = EnabledConfig & {
    healthy?: Status;
}

class Healthy {
    public readonly config: Config<HealthyConfig>
    constructor(config: Config<HealthyConfig>) {
        this.config = config;
    }
    get enabled() {
        return this.config.get('enabled', true);
    }
}

const en = new Enabled<HealthyConfig>({enabled: false});
const isEnabled: boolean = en.get('enabled', true); // works
const isHealthy: Status = en.get('healthy', 'no'); // works

const h = new Healthy(new Enabled<HealthyConfig>({enabled: true, healthy: 'no'}));
const enabled: boolean = h.config.get('enabled', true); // works
const healthy: Status = h.config.get('healthy', 'yes'); // works

// This generic "T extends HealthyConfig" causes a problem 
// with the "this.config.get" showing an error on the "<boolean>"
// of "Type 'boolean' does not satisfy the constraint 'T[keyof T]'"
class UnHealthy<T extends Config<HealthyConfig>> {
    public readonly config: T
    constructor(config: T) {
        this.config = config;
    }
    get enabled() {
        return this.config.get('enabled', true);
    }
}

Is this what you need?

CodePudding user response:

Why do you need UnHealthy to be generic? Provide concrete config, HealthyConfig in your case, compiler will pick up T[keyof T], and you won't able to use type distinct from boolean | Status | undefined as the defaultVal arg.

class UnHealthy {
    public readonly config: Config<HealthyConfig>
    constructor(config: Config<HealthyConfig>) {
        this.config = config;
    }
    get enabled() {
        console.log(this.config)
        return this.config.get('enabled', false)
    }
}
  • Related