Typescript: Distinguish between plain object types and class types

Created on 17 Dec 2018  路  6Comments  路  Source: microsoft/TypeScript

Search Terms

  • plain object class type

Suggestion

Some way to distinguish an object type that only has Object in its prototype chain.

Use Cases

In this library, we use mapped types to convert immutable objects/arrays into their mutable representation. We _never_ do this for class instances, so I need a way to skip the mapped type when an object type is actually a class type (in order to preserve readonly properties on class instances).

Examples

I have no suggestions for such syntax.

Checklist

My suggestion meets these guidelines:

  • [ ] This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • [x] This wouldn't change the runtime behavior of existing JavaScript code
  • [x] This could be implemented without emitting different JS based on the types of the expressions
  • [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • [x] This feature would agree with the rest of TypeScript's Design Goals.

Related

https://stackoverflow.com/questions/53819550/distinguish-between-plain-objects-and-class-types#53819550

Needs Proposal Suggestion

Most helpful comment

@DanielRosenwasser @ahejlsberg Can this be brought up in a design meeting soon? 馃

All 6 comments

Proposal A

Treat class Foo {} differently than class Foo extends Object {} like so:

  • When extends Object is omitted from the class declaration, make Foo extends Object evaluate to false. Otherwise, behave normally.

Example

class Foo {
  private _: any
}
class Bar extends Object {
  private _: any
}

declare const test: (arg: Object) => void
test(new Foo) // 馃挜
test(new Bar) // 馃憤

declare const test2: (arg: object) => void
test2(new Foo) // 馃憤
test2(new Bar) // 馃憤

Proposal B

Add a new Exact<T> type that forbids narrow class types.

Details

  • When T is a literal type, the resolved type is T
  • When T is Object, any object literal is allowed (but Object sub-classes are not)
  • When T is Array, any array literal is allowed (but Array sub-classes are not)
  • When T is Function, any function literal is allowed (but Function sub-classes are not)
  • When T is any other class, only instances of T are allowed (no sub-classes or super-classes)

Example

class Foo extends Object {
  constructor(public a: number) {}
}

declare const testObject: (arg: Exact<Object>) => void
testObject(new Foo(1)) // 馃挜
testObject({ a: 1 }) // 馃憤
testObject(Object.create(null)) // 馃憤

declare const testArray: (arg: Exact<Array>) => void
class MyArray extends Array {}
testArray(new MyArray()) // 馃挜
testArray([]) // 馃憤

declare const testFunc: (arg: Exact<Function>) => void
class MyFunc extends Function {}
testFunc(new MyFunc()) // 馃挜
testFunc(() => 0) // 馃憤

declare const testFoo: (arg: Exact<Foo>) => void
class Cart extends Foo {}
testFoo(new Foo(1)) // 馃憤
testFoo(new Cart(2)) // 馃挜

__Related:__ #12936

@aleclarson - I'm facing the exact same issue in one of my libs. Please let me know if you figure any hacky workarounds. 鉂わ笍

I have a proxy which should not ever be used for classes. I need a way to say: "This function can take objects, but not ones which are classes"

Trying to solve this problem again, ended up back here.

@DanielRosenwasser @ahejlsberg Can this be brought up in a design meeting soon? 馃

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jbondc picture jbondc  路  3Comments

Zlatkovsky picture Zlatkovsky  路  3Comments

bgrieder picture bgrieder  路  3Comments

blendsdk picture blendsdk  路  3Comments

MartynasZilinskas picture MartynasZilinskas  路  3Comments