TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

Cannot circumvent weak type detection in class

Open DanielSWolf opened this issue 5 years ago • 6 comments

TypeScript Version: 3.8.3

I understand the idea behind weak type detection. But there are cases where I want an interface with all-optional members, and I want to implement it without any of these members. I'm looking for an idiomatic way of telling TypeScript that this is intentional.

Search Terms: weak type detection

Code

interface Model {
  empty?: boolean;
  title?: string;
  [key: string]: unknown; // only to prevent weak type detection
}

class MyModel implements Model {
  id = 42;
}

Expected behavior:

The indexer in the Model interface prevents weak type detection as documented in the handbook.

Note: The syntax suggested in the handbook is [propName: string]: {}. This effectively forces all properties to be of object type, which doesn't make sense to me. So I went with unknown instead, as suggested here.

Actual behavior:

TypeScript gives me this error:

Class 'MyModel' incorrectly implements interface 'Model'. Index signature is missing in type 'MyModel'. (2420)

Playground Link: Link

Related Issues:

DanielSWolf avatar Mar 24 '20 11:03 DanielSWolf

You can use // @ts-ignore. Why are you intending to do this?

RyanCavanaugh avatar Mar 24 '20 15:03 RyanCavanaugh

We use an architecture where any object can be used as a model. But there are a number of properties that, if present on a model, have specific meaning. Some models implement all of these properties, some implement a subset, some implement none.

I understand that I can use ts-ignore. But from where I stand, TypeScript's weak type detection is just a heuristic that may or may not point to an actual error. So I'm looking for an idiomatic way of telling TypeScript that I know what I'm doing.

DanielSWolf avatar Mar 24 '20 20:03 DanielSWolf

I had the same use case and found that any seems to work as the property type.

interface Model {
  empty?: boolean;
  title?: string;
  [key: string]: any; // only to prevent weak type detection
}

StevenGBrown avatar Feb 27 '22 05:02 StevenGBrown

@StevenGBrown This seems to work indeed. At the same time, however, it can make code highly unsafe. Consider the following:

interface Model {
  empty?: boolean;
  title?: string;
  [key: string]: any; // only to prevent weak type detection
}

class MyModel implements Model {
  id = 42;
}

const model: Model = new MyModel();
const test: number = model.noSuchProperty;

Giving the Model interface an indexer with any type effectively allows you to read and write non-existing Model properties without any type checks. That's why I chose unknown (which sadly didn't work).

DanielSWolf avatar Feb 28 '22 06:02 DanielSWolf

Ah yes that's unfortunate.

StevenGBrown avatar Feb 28 '22 07:02 StevenGBrown

Same issue here. We've also a model interface for a dialog, which can have only optional properties / methods.

it works fine and unsafe if we use any

export interface IDialogModel {
  onOk?(): void;
  onCancel?(): void;

  [key: string]: any;
}

// ok
class DialogModel implements IDialogModel {
  onCancel(): void {
    /* ignore */
  }
  onOk(): void {
    /* ignore */
  }
}

but if we use unknown we get same error message as in issue description

export interface IBsDialogModel {
  onOk?(): void;
  onCancel?(): void;

  [key: string]: unknown;
}

// error: Index signature for type 'string' is missing in type 'DialogModel'. ts(2420)
class DialogModel implements IDialogModel {
  onCancel(): void {
    /* ignore */
  }
  onOk(): void {
    /* ignore */
  }
}

viceice avatar Nov 29 '22 10:11 viceice