Skip to content

4.4 index signature check on wrong generic? #45548

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
zmagyar opened this issue Aug 23, 2021 · 5 comments
Closed

4.4 index signature check on wrong generic? #45548

zmagyar opened this issue Aug 23, 2021 · 5 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@zmagyar
Copy link

zmagyar commented Aug 23, 2021

Bug Report

We are using key remapping with template strings to type getters/setters generated automatically for our classes as can be seen in the simplified code below. This worked nicely in 4.3 and provided typing for getA() and getB() functions,
Using 4.4 it is still working but we are getting errors when any class member is named as 'getXXX'.

export interface AssociationConfig<U extends string> {
    [propName: string]: U;
};

export type AutoGeneratedFunctions<T extends AssociationConfig<string>> = {
        [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
    } 
  
interface FooAssociations extends AssociationConfig<string> {
    a: 'aaaa',
    b: 'bbbb'
}

interface Foo extends AutoGeneratedFunctions<FooAssociations> {

}

class Foo {
    doSome(a: number): string {
        return 'this works';
    }
    getSome(): string {
        return 'this works';
    }
    getSomethigElse(a: number): string {
        return "this fails: Property getSomethigElse' of type '(a: number) => string' is not assignable to '`get${string}`' index type '() => string' ";
    }
}
Output
;
class Foo {
    doSome(a) {
        return 'this works';
    }
    getSome() {
        return 'this works';
    }
    getSomethigElse(a) {
        return "this fails: Property getSomethigElse' of type '(a: number) => string' is not assignable to '`get${string}`' index type '() => string' ";
    }
}
export {};
Compiler Options
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictPropertyInitialization": true,
    "strictBindCallApply": true,
    "noImplicitThis": true,
    "noImplicitReturns": true,
    "alwaysStrict": true,
    "esModuleInterop": true,
    "declaration": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "target": "ES2017",
    "jsx": "react",
    "module": "ESNext",
    "moduleResolution": "node"
  }
}

Playground Link: Provided

🔎 Search Terms

key remapping, template strings, 4.4

🕗 Version & Regression Information

This changed between versions 4.3 and 4.4

🙁 Actual behavior

The Foo class can not have any member starting with get which doesn't match the signature defined in AutoGeneratedFunctions

🙂 Expected behavior

The signature defined in AutoGeneratedFunctions should only be applied to getA() and getB() functions because only those are defined in FooAssociations.

@DanielRosenwasser
Copy link
Member

One of the issues here is that the key remapping also applies to the index signature in

export interface AssociationConfig<U extends string> {
    [propName: string]: U;
};

so you end up creating a template index signature in the subclass. I don't think this was a situation that we had anticipated but I believe that the behavior is "expected". Tagging @ahejlsberg to get his take on this as well though.

It seems like the root issue is that index signatures are often also used as a "constraint" on all the properties in a class; but there's no way to say "I want all my properties to conform to SomeType" without creating an index signature. Ideally we would have a mechanism like that.

@andrewbranch
Copy link
Member

I want all my properties to conform to SomeType

could be done by #7481

@DanielRosenwasser
Copy link
Member

Well only for an expression - there's no way to ensure a type satisfies another type without unused local type aliases and the like (e.g. type _Assert = CompatibleWith<SomeType, Constraint>;).

@ahejlsberg
Copy link
Member

The specific change in 4.4 is that we now generate index signatures in mapped types for keys that are template literal strings with placeholders. See #44512 for more details. In this particular example, the string index signature introduced by AssociationConfig is mapped by AutoGeneratedFunctions into an index signature for the key type `get${string}`. Previously we'd just drop that member, which was wrong. So, the new behavior is indeed intended.

It's pretty easy to get the old behavior by excluding string index signatures in AutoGeneratedFunctions:

export type AutoGeneratedFunctions<T extends AssociationConfig<string>> = {
    [K in keyof T as string extends K ? never : `get${Capitalize<string & K>}`]: () => T[K]
} 

@DanielRosenwasser DanielRosenwasser added the Working as Intended The behavior described is the intended behavior; this is not a bug label Aug 23, 2021
@typescript-bot
Copy link
Collaborator

This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

5 participants