Skip to content

Index signatures prevent automatic typing of e.g. JSON for interfaces and classes, but not types. #19963

Closed
@cmcaine

Description

@cmcaine

TypeScript Version: Version 2.7.0-dev.20171112

I would like to restrict the input to a function to objects that can be serialised, and I've got it working OK for types, but not for classes or interfaces: I'm told they don't have an index signature.

This is related to issue #1897.

The motivation is that the firefox webextension storage API will only serialise certain objects.

A small example:

Code

declare namespace browser.storage {
    // Non-firefox implementations don't accept all these types
    type StorageValue = 
        string |
        number |
        boolean |
        // ...
        StorageArray |
        StorageMap |
        StorageSet |
        StorageObject;

    // The Index signature makes casting to/from classes or interfaces a pain.
    // Custom types are OK.
    interface StorageObject {
        [key: string]: StorageValue;
    }

    // These have to be interfaces rather than types to avoid a circular
    // definition of StorageValue
    interface StorageArray extends Array<StorageValue> {}
    interface StorageMap extends Map<StorageValue,StorageValue> {}
    interface StorageSet extends Set<StorageValue> {}

    type StorageArea = {
        set: (keys: StorageObject) => Promise<void>,
	// ...
    };

    const sync: StorageArea;
}

type A = {
  prop: string[];
}

interface B {
  prop: string[];
}

class C {
  constructor(public prop: string[]) {}
}

interface D extends browser.storage.StorageObject {
  prop: string[];
}

const a: A = { prop: [] }
const b: B = { prop: [] }
const c = new C([])
const d: D = { prop: [] }

browser.storage.sync.set({ a }) // Works
browser.storage.sync.set({ b }) // Fails: no index signature
browser.storage.sync.set({ c }) // Fails: no index signature
browser.storage.sync.set({ d }) // Works

a.foo = 1 // Property 'foo' does not exist on type 'A'.
b.foo = 1 // Property 'foo' does not exist on type 'B'.
c.foo = 1 // Property 'foo' does not exist on type 'C'.
d.foo = 1 // No error (this is bad)

Errors:

example.ts(51,26): error TS2345: Argument of type '{ b: B; }' is not assignable to parameter of type 'StorageObject'.
  Property 'b' is incompatible with index signature.
    Type 'B' is not assignable to type 'StorageValue'.
      Type 'B' is not assignable to type 'StorageObject'.
        Index signature is missing in type 'B'.
example.ts(52,26): error TS2345: Argument of type '{ c: C; }' is not assignable to parameter of type 'StorageObject'.
  Property 'c' is incompatible with index signature.
    Type 'C' is not assignable to type 'StorageValue'.
      Type 'C' is not assignable to type 'StorageObject'.
        Index signature is missing in type 'C'.
example.ts(55,3): error TS2339: Property 'foo' does not exist on type 'A'.
example.ts(56,3): error TS2339: Property 'foo' does not exist on type 'B'.
example.ts(57,3): error TS2339: Property 'foo' does not exist on type 'C'.

In addition to this issue, I'd also like to express that instances of a class can be stored (you can't store objects with functions, but prototypes and constructors are ignored).

This bug is tracked on the web-ext-types repo (that I maintain) at https://github.com/kelseasy/web-ext-types/pull/38#issuecomment-343855598

Expected behavior:

Some feature exists to suit the above use case.

Actual behavior:

See example

Thanks for yo time

Metadata

Metadata

Assignees

No one assigned

    Labels

    QuestionAn issue which isn't directly actionable in code

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions