Skip to content

Prohibit undefined in TypeScript Index Signatures for Data.Map when key is String #39

@benrbray

Description

@benrbray

Thanks for aeson-typescript, it's great!

Example

I've noticed that when using Data.Map as follows:

newtype Example = Example (Map String Int)
$(deriveTypeScript defaultOptions ''Foo)
result = tt @Example

(helper functions)

tsFormatOpts :: FormattingOptions
tsFormatOpts = defaultFormattingOptions {exportMode = ExportEach, typeAlternativesFormat = EnumWithType}
ts :: (TypeScript a) => Proxy a -> String
ts = formatTSDeclarations' tsFormatOpts . getTypeScriptDeclarations
tt :: forall a. (TypeScript a) => String
tt = ts (Proxy @a)

The generated types look like this:

export type Example = IExample;
// actual generated type
export type IExample = {[ k in string] ?: number};

It's a bit difficult to use these types, since indexing IExample will give a number | undefined value, even though the ToJSON instance for Data.Map should never include undefined values.

const foo: Example = { bar : undefined } // no error

function example(data: Example) {
  const bar: { [k : string] : number } = data;
  // Type 'IExample' is not assignable to type '{ [k: string]: number; }'.
  // 'string' index signatures are incompatible.
  //   Type 'number | undefined' is not assignable to type 'number'.
  //     Type 'undefined' is not assignable to type 'number'.
}

Note that these errors still happen with exactOptionalPropertyTypes enabled.

Desired Behavior

Instead, I would have expected the generated type to look more like this:

// desired type
export type IExample = { [k : string] : number };

const foo: Example = { bar : undefined }
// Type 'undefined' is not assignable to type 'number'.

function example(data: Example) {
  const bar: { [k : string] : number } = data;
  // Type 'IExample' is not assignable to type '{ [k: string]: number; }'.
  // 'string' index signatures are incompatible.
  //   Type 'number | undefined' is not assignable to type 'number'.
  //     Type 'undefined' is not assignable to type 'number'.
}

Questions

  • was there a particular reason for this choice of types?
  • would it be possible to change the behavior when the key is String or an alias for String?

Related

microsoft/TypeScript#46969

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions