question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

maintain certain class property characteristics in mapped types, like protected/private visibility, and instance property vs instance function

See original GitHub issue

Search Terms

mapped types with protected private members

Suggestion

Feature request:

Allow types/interfaces to contain member visibility as well as property type (“instance member function” vs “instance member property”) and perhaps other intrinsics.

Or at least keep these characteristics intact in mapped types where the mapped type is generated from class types.

Use Cases

I’ve made a function called multiple that allows me to do multiple inheritance. In plain JavaScript, it works fine, like this:

class One {one() {}}
class Two {two() {}}
class Three {three() {}}
class Four extends multiple(One, Two, Three) {
  test() {
    this.one() // OK
    this.two() // OK
    this.three() // OK
  }
}

So far, after making type definitions for multiple(), the above works fine in TypeScript. playground example.

However when making properties protected or private, those properties are lost from the mapped type returned from multiple(), and therefore inheritance of protected (or denial of accessing private) members doesn’t work:

class One {protected one() {}}
class Two {protected two() {}}
class Three {private three() {}}
class Four extends multiple(One, Two, Three) {
  test() {
    this.one() // ERROR, Property 'one' does not exist on type 'Four'. Expected no error.
    this.two() // ERROR, Property 'two' does not exist on type 'Four'. Expected no error.
    this.three() // ERROR, Property 'three' does not exist on type 'Four'. Expected an error relating to private access.
  }
}

Here’s the playground example.

Because the private properties are deleted from the mapped type (just like the protected ones are), it becomes possible to inadvertently use private properties because the type system says they don’t exist:

class One { private foo = 1 }
class Two { }
class Three extends multiple(One, Two) {
	foo = false
	test() {
		this.foo = true // Uh oh! There's no error using the private property!
	}
}

Here’s the playground example.

Another problem is methods of the classes passed into multiple() are converted from instance member function to instance member property, and this happens:

class One {one() {}}
class Two {two() {}}
class Three {three() {}}
class Four extends multiple(One, Two, Three) {
	one() {} // ERROR, Class '{ three: () => void; two: () => void; one: () => void; }' defines instance member property 'one', but extended class 'Four' defines it as instance member function.(2425)
}

Here’s the playground example.

Related

Checklist

My suggestion meets these guidelines:

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

Issue Analytics

  • State:open
  • Created 4 years ago
  • Reactions:5
  • Comments:11

github_iconTop GitHub Comments

2reactions
trusktrcommented, Aug 3, 2020

I’m re-opening this, because the problem still persists with mapped typed specifically. Any time we run something like Pick, Required, Optional on a class, it obliterates the protected and private fields.

One key issue caused by this is that it makes working with mixins and other class-programming concepts more difficult.

In the previous comment’s playground example, it works, except that if two classes have the same property but with differing types then the outcome type will be never.

Here is a playground example that shows how the never type pops into the picture when there are property collisions. Also notice, the protected characteristic of the zero property is preserved, so we get the expected type error at the end of the example when trying to access the prop outside of the class.

At runtime (in plain JS) the mutiple() implementation uses the property of the first class of the list of classes with the property collision, in the same order they are passed into multiple().

However in TypeScript, there’s no way to pluck the first property (with mapped types) without losing protected and private properties.

Here’s the same example but using the original multiple() implementation (playground). Notice that the protected property disappeared, but the public property works fine and the type was chosen to be string based on the first property in the collided properties (based on order classes were passed into multiple()).

It seems that if we wish to “pick the type of the first property in the collision”, we have to use mapped types, and in such case we can not use protected or private properties, otherwise it may lead to human errors like accidentally overriding private properties that seem to not exist.

For example, here’s a playground example that shows the accidental overriding of a private property, which may break the application.

Going back to the tuple-based approach, here’s that same example with a private collision but showing an error (playground). The error is: The intersection 'Zero & One' was reduced to 'never' because property 'zero' exists in multiple constituents and is private in some. (2509).

Here is a playground example that shows my preferred approach (for now), which is to use _ prefixes for protected members, and __ prefixes for private members along with the mapped-type approach (because it picks the type of first collided property, which reflects what actually happens at runtime). In reality, they are all still public members.

Finally, note that ES #private class fields actually avoid the private property collisions, using either approach. Here’s a playground example showing the tuple approach with a private field, and playground example showing the mapped-type approach in which case no overriding of private fields happens.

1reaction
trusktrcommented, Jul 29, 2020

@MicahZoltu just showed me how to do it with tuple-to-union-to-intersection. Amazing. Playground Example.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Documentation - Mapped Types - TypeScript
Mapped types build on the syntax for index signatures, which are used to declare the types of properties which have not been declared...
Read more >
Is there a way to use mapped types while keeping methods an ...
In the above example, A is {x(): void} and B is {x: () => void} . Is there a way to use mapped...
Read more >
Microsoft/TypeScript - Gitter
Maybe those "intrinsic characteristics" of the class properties could be maintained in mapped types, and the type system can use them if the...
Read more >
Binding Model — PyXB 1.2.6 documentation - SourceForge
Each such structure is stored as a private class field and is used by Python properties to provide access to element and attribute...
Read more >
MagicDraw CodeEngineering UserGuide.pdf - No Magic, Inc
Is mapped to the MagicDraw specified property “Type. Modifier“. Final modifier. Direction kind “in“ of UML Parameter. Example. Java Source Code public class...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found