microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
99.26k stars 12.31k forks source link

Distinguish between plain object types and class types #29063

Open aleclarson opened 5 years ago

aleclarson commented 5 years ago

Search Terms

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:

Related

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

aleclarson commented 5 years ago

Proposal A

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

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) // πŸ‘
aleclarson commented 5 years ago

Proposal B

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

Details

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

ctrlplusb commented 5 years ago

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

SephReed commented 4 years ago

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"

SephReed commented 4 years ago

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

aleclarson commented 4 years ago

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

AlexGalays commented 3 years ago

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"

I have exactly the same use case.

SephReed commented 3 years ago

@AlexGalays , depending on your project you can maybe do what I did:

  1. add a property called _noProxy: true to every class you expect to be used
  2. add a condition to your type which takes advantage of this

This is by no means perfect, but it can put out a few fires at least.

AlexGalays commented 3 years ago

@AlexGalays , depending on your project you can maybe do what I did:

  1. add a property called _noProxy: true to every class you expect to be used
  2. add a condition to your type which takes advantage of this

This is by no means perfect, but it can put out a few fires at least.

Yeah, unfortunately, I don't control all the class combinations, people can use their own :p

SephReed commented 3 years ago

Same with my project. They can use any class actually... so I have a long list of things which it should not proxy, and then am hoping to convince users within docs to add _noProxy: true to anything that messes them up. Once again, not great... but I would have had to give up on the project otherwise.

juanrgm commented 8 months ago

These examples are more fun.

https://www.typescriptlang.org/play?#code/MYewdgzgLgBAJGAhgWwKYC4YG8ZLZ6AJwEswBzGAXwG0BdGAXhmoCgZ3tcUMYAiKVNF5UANGw4AKAJSMAfJ0piOMAGYBXMMCjFwMaQqUdgAG0QQIMAMKnzB8ewBCIEMdSIwh9gHkARgCtULTFaAG4WFlBIWDhXcigAC0wcWLIEzDA1ZB9UQio6RmZ7ThS0mABGUSK6T2ZeQF4NwFI93loasFQAdxgAVVIoMoA2AEFCQkQATwkyqWCwoA

const $name: { name: string }[] = [
    { name: "test" },
    () => { },
    function () { },
    class Class { },
    Boolean,
    Object,
];

const $length: { length: number }[] = [
    { length: 1 },
    [],
    ["πŸ˜₯"],
    new Uint16Array(1),
];