microsoft / TypeScript

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

Implement private fields proposal #9950

Closed dead-claudia closed 4 years ago

dead-claudia commented 8 years ago

I think it would be nice to have the stage 1 private fields proposal implemented in TypeScript. It'll mostly supercede the current pseudo-private implementation, and a fully-correct implementation is only transpliable to ES6 using per-class WeakMaps. I'll note that the spec itself uses WeakMaps internally to model the private state, which may help in implementing the proposal.

Currently, the proposal only includes private properties, but methods are likely to follow. Also, the most important part of the proposal is that private properties are only available within the class itself.

glen-84 commented 8 years ago

What does the TypeScript team think about the syntax and the use of a symbol/character prefix?

I personally think that it looks awful, but apparently there are technical limitations (the usages need to identify the visibility, for reasons that I don't fully comprehend).

It would be sad to see TypeScript losing the current private/protected syntax, which is much cleaner and consistent with a number of other languages. Is this likely to happen? Are there any alternatives?

To add to this, there are talks about possibly switching the # and @ symbols around (i.e. using # for decorators), which would obviously have an affect on TypeScript as well.

kitsonk commented 8 years ago

I think it is "awful" too. I think like the :: bind operator, which didn't make it actually very far, it is worth holding off even trying to implement it until it gets a bit further down the path with T39. I highly suspect it will be a bit of a rough ride. Also, the down-emit to support it will be rather difficult I suspect, because of the need to rewrite every access site.

the usages need to identify the visibility, for reasons that I don't fully comprehend

Mainly because you need to identify at the usage site how to lookup the property, since it needs a different lookup algorithm so that descendent classes can re-use the same private names without needing to "know" about the ancestor private properties.

littledan commented 8 years ago

One advantage of the proposed syntax is that you can omit the this and just use #field directly. Further, the sigil may be swapped with decorators (https://github.com/tc39/proposal-private-fields/issues/32) and instead @ would be used, so you'd use @field, just like Ruby. Do you find this syntax ugly still?

dalexander01 commented 8 years ago

I don't think people will be happy with a non "private" keyword based solution.

littledan commented 8 years ago

I don't see a good way to do that while supporting general eval as JavaScript does--a concrete token allows some differentiation and makes it possible to support private state that's not based on a static type system as in TypeScript. We've discussed a number of syntax alternatives at https://github.com/tc39/proposal-private-fields/issues/14 , and I don't see TypeScript's current syntax as something that can work properly all the time in a fully general JS environment.

glen-84 commented 8 years ago

Mainly because you need to identify at the usage site how to lookup the property, since it needs a different lookup algorithm so that descendent classes can re-use the same private names without needing to "know" about the ancestor private properties.

I wish I knew more about how JavaScript works internally. I thought that it would work the way that it's described here and here, but apparently not. I still find it difficult to believe that that would have a significant impact on performance, but there are no numbers for comparison.

Do you find this syntax ugly still?

Essentially, yes. More so, I find it both inconsistent with the rest of the JavaScript syntax, as well as inconsistent with many other languages:

Language Syntax Notes
C# private int x;
C++ private:
    int x;
Java private int x;
PHP private $x;
Python __x "no comment"
Ruby private
    def method
        # ...
    end
It's a method, but fields are private
by default I think.
TypeScript private x;
Visual Basic Private x As Integer

All but one of the above languages use the private keyword, and it seems that Python doesn't really have "proper" private state anyway.

With the exception of Python (again), none of the languages format field declarations or field accesses differently depending on the visibility, and they all allow for new visibility modifiers if that is ever required.

I also feel that both # and @ work better for things like decorators, that are (in Kevin's words) "syntactically 'outside' of the imperative flow".

I don't see a good way to do that while supporting general eval as JavaScript does

Would it be possible to explain this issue in layman's terms? (maybe in https://github.com/tc39/proposal-private-fields/issues/14)

It would be nice to have a list of these issues with examples in one place, so that there is something to refer to.

kitsonk commented 8 years ago

I still find it difficult to believe that that would have a significant impact on performance, but there are no numbers for comparison.

It would, if you want it to be really private versus just a convention like it is in TypeScript. Whenever you look up a property in JavaScript, the process is essentially like this:

  1. Look at instance for own property.
  2. If no own property, look at prototype for own property.
  3. Ascend prototype chain until no property found.
  4. If found deal with property descriptor (writable, get, set, configurable)
  5. If not found and read, return undefined or write, set value as own property if not sealed or frozen.

Now with privates with no pattern in their name, you wouldn't have own properties, you would have something like own private properties which wouldn't ascend a prototype chain. You would then have to look for them there as well for every operation that could possible access the private properties, because you can't be sure which one is being referred to. Looking it up in the normal way and then only if it is not found looking in privates could be very dangerous, because what if you found something in the prototype chain, but there is also a private version?

The biggest challenge is that JavaScript classes are still a bit of a misnomer. They are essentially syntactic sugar for prototypical inheritance constructor functions. There is one thought that occured to me which I will try to add to tc39/proposal-private-fields#14.

Would it be possible to explain this issue in layman's terms?

If it requires re-writing the call site, then you would never be able to support things like:

class Foo {
    private foobar: string;
    baz() {
        const p = 'foo' + 'bar';
        console.log(this[p]);
    }
}

Or any usage of eval like structures. You could choose not to support things like index access for privates or no eval support, but then "why bother". Pretty much all of this has been pointed out in one way or another in the proposal there but people still are "but I like private" 😁.

glen-84 commented 8 years ago

you wouldn't have own properties, you would have something like own private properties

If you had a single set of properties with a visibility item in the descriptor, I guess the issue is that, if the property did not exist on the current object, you wouldn't know whether or not to ascend the prototype chain.

If it requires re-writing the call site, then you would never be able to support things like ...

AFAIK, that won't be supported anyway (ref).

I still don't really follow the eval case. I thought that the property checks would happen at runtime, after property names had been calculated.

but people still are "but I like private"

I'm trying not to be one of those people, and to understand the issues, but the proposed syntax just makes me uncomfortable. =)

glen-84 commented 8 years ago

Okay, I think I understand the eval/rewrite case now. Usage sites within the class would be rewritten to indicate the visibility based on the declared properties, and that wouldn't be possible unless they are simple lookups.

glen-84 commented 8 years ago

If you had a single set of properties with a visibility item in the descriptor, I guess the issue is that, if the property did not exist on the current object, you wouldn't know whether or not to ascend the prototype chain.

Hmm. But if it doesn't exist in the current class, then isn't it by definition non-private? If so, it would have to move up the prototype chain anyway (as with regular public property lookups). There would be no additional work for private access.

(I'm possibly missing something obvious)

kitsonk commented 8 years ago

But if it doesn't exist in the current class, then isn't it by definition non-private? If so, it would have to move up the prototype chain anyway (as with regular public property lookups).

No, you don't want to ascend. Private labels aren't inherited and subclasses can re-use privates without worrying about masking/name collisions.

littledan commented 8 years ago

@glen-84 Both of those posts that you mentioned indicate problems with un-sigil'd private state. Do you have ideas for solutions to those problems? I think complicating the lookup chain would be risky compatibility-wise, make JavaScript harder to implement with good performance (possibly making existing programs slower), be basically impossible to square with Proxies, and generally, significantly complicate the mental model of the language (which already has a relatively complex object system).

In the cross-language comparison, you mention Ruby. I think Ruby is a good example of private state with a sigil--@. You can call getter and setter methods without a sigil.

glen-84 commented 8 years ago

No, you don't want to ascend. Private labels aren't inherited and subclasses can re-use privates without worrying about masking/name collisions.

I meant move up if the property wasn't on the current class, to look for a public or protected property.

glen-84 commented 8 years ago

Both of those posts that you mentioned indicate problems with un-sigil'd private state. Do you have ideas for solutions to those problems?

It's very difficult for me to do that, as someone without an understanding of the internal workings of JavaScript.

You'd have to somehow encode a "private key" into each lexical environment.

I have no idea what this means. What is it for? Is it impossible to do?

You'd have to change the semantics of each and every property access (because any property access might result in a private field). Engines are highly optimized around the current property lookup semantics.

So highly optimized that a single switch on visibility would significantly affect performance?

Would for-in enumerate over these properties?

I guess it would depend on the context. Within the same class, yes. However, this is not a reason for not using private, it's an implementation detail, and other languages have probably already answered such questions.

Can someone shadow a private property with a normal property lower on the prototype chain? What about the reverse?

Probably yes (visibility is increased) to the first question, and no to the second. Again, this has all been done before, hasn't it?

How do you prevent leaking the names of private fields to clients that shouldn't know that information? This is probably a fatal information leak.

Visibility is just a tool for encapsulation and defining a public interface. 99% of developers probably don't care about "information leaks". That said, I did suggest two separate features here. Perhaps this proposal could be called something different, like "hidden state" or "secure state", and allow for something like private/protected state to be implemented differently.

All of this runtime stuff is going to be terrible for performance

Use "hidden/secure state" if you want perf. :D In all seriousness, what type of performance loss are we talking about? Maybe someone can create a prototype. =)

Also, we couldn't use such a solution to self-host the built-ins

Wouldn't self-hosting built-ins (if I even understand what that means) be really bad for performance? If not ... use "hidden/secure state" and not higher-level private/protected visibility.

I think complicating the lookup chain would be risky compatibility-wise

I'm not the first/only person to think that this is how it could/may work. You access a property, it looks to a descriptor for visibility, and responds accordingly. It doesn't seem complicated if you have no idea how things really work in JS. I really need to read a crash course on JS internals/property lookup algorithms. =P

I think Ruby is a good example of private state with a sigil--@

Ruby isn't even a C-family language, and there are no public fields it seems, but only getters and setters (accessors). The current private state proposal has multiple declarations sitting side-by-side, with the same general purpose (declaring properties), but visibly different and inconsistent syntaxes.

With all that said, I'm way out of my depth here, and I'm most-likely adding more noise than value, so I'll try to keep quiet and let the experts come to a consensus.

littledan commented 8 years ago

I'll discuss this wish with TC39 and we'll see if we can come up with any other ideas about how to avoid a prefix. I don't have any, personally. Note that, if TypeScript does decide to implement private fields, they are still at Stage 1 in TC39 and therefore subject to change.

shelby3 commented 7 years ago

What does the TypeScript team think about the syntax and the use of a symbol/character prefix?

I personally think that it looks awful...

Note I've made a suggestion to get rid of the aweful # syntax.

littledan commented 7 years ago

@shelby3 Unfortunately, I don't see that as a realistic option. tl;dr, we have to worry about how everything will interact with eval; not including a sigil just makes everything "too dynamic" to work properly.

shelby3 commented 7 years ago

@littledan I have followed up there now and made my strongest argument against using a sigil, and my argument to maximize compatibility with TypeScript. I do understand now though why we must declare the privacy for untyped arguments of methods.

about-code commented 7 years ago

@isiahmeadows

It'll mostly supercede the current pseudo-private implementation [...] Also, the most important part of the proposal is that private properties are only available within the class itself.

I hope it won't supersede pseudo-private implementation. I think availability only within a class is actually not such a good thing (given that by availability you mean accessibility). I admit there may be special situations where it makes sense to have strictly private and inaccessible properties, e.g. for security purposes. I also admit, that it is obviously confusing to people familiar with other languages, that private is actually not that private in JavaScript. But apart from security reasons, in my opinion, most developers should use private most of the time only to define a contract but not to control accessibility.

E.g. from an architects perspective I think a very good thing about the TS private-keyword and its pseudo-private nature is

Accessibility of private properties at runtime very much contributes to testability because I am able to inject private state into a class under test by just setting its private fields (in TypeScript I recommend using bracket notation instead of any casts due to better refactoring support), e.g:

let instance: ClassUnderTest = new ClassUnderTest();
instance["_privateField"] = "My injected state";

No need for complicated test code just to set up a class with a particular (private) state. Another advantage of pseudo-private properties is that they are essential to monkey patching.

glen-84 commented 7 years ago

@about-code See https://github.com/tc39/proposal-private-fields/issues/33

igabesz commented 7 years ago

I don't think that the TypeScript community would happily change all of their private variable lines to private #variable.

From the TypeScript perspective most of us are happy with our lovely, sweet illusions of a good language (intellisense, build-time errors, types, various sugars). These illusions give us better development experience, we write better code faster. This is the main reason why we use TS besides ESNext transpilation -- but for the latter there is Babel and that's better (or at least it was better when I last checked).

I'm not talking about those few who actually want anything more from private variables than a syntactic sugar in their source files. I think those guys need something stronger, maybe closer to the native code.

But for the rest us: we don't really need JS private. We need the simple, easy private convenient variables just like as we used it in C++, Java, C#, etc.

Please vote for a solution that won't be a pain for us. Maybe soft private? Because I doubt that we want the sigil private #variable. Or maybe TS private and ES private would be different concepts? Ugly.

dead-claudia commented 7 years ago

@igabesz

I don't think that the TypeScript community would happily change all of their private variable lines to private #variable

Actually, with that proposal, private would be unnecessary. Instead, the # sigil replaces it entirely without conflict.

But for the rest us: we don't really need JS private. We need the simple, easy private convenient variables just like as we used it in C++, Java, C#, etc.

TypeScript's private is soft private, like that of most OO languages. Even Java's private variables are still technically soft-private, because they are still accessible with an escape hatch. JS private is equivalent to using a closure, except that you can still access private state of non-this instances.

Please vote for a solution that won't be a pain for us. Maybe soft private? Because I doubt that we want the sigil private #variable. Or maybe TS private and ES private would be different concepts? Ugly.

They would be different concepts, but in general, escape hatches are rarely useful in practice. The primary exception is with object inspection (e.g. developer tools, Node.js util.inspect), but most are host-defined and already use privileged native APIs now, so there is no pressing need to add it to the spec.

Additionally, once ES has standardized private state, TS will adopt it and likely deprecate its own soft-private mechanism, to be removed in the next major version. It has always been a strict superset of ES, and it has added (async functions), changed (classes), and/or deprecated (/// <amd-dependency />) features as necessary as ES itself evolves, to maintain itself as a strict superset and not duplicate the current spec.

glen-84 commented 7 years ago

Additionally, once ES has standardized private state, TS will adopt it and likely deprecate its own soft-private mechanism, to be removed in the next major version.

😭

mhegazy commented 7 years ago

Additionally, once ES has standardized private state, TS will adopt it and likely deprecate its own soft-private mechanism, to be removed in the next major version.

This is not the TS team position. we have no plans of deprecating any thing any time soon. If and when the private state proposal reaches the correct state, TS will implement it. I do not think this has any implication on privates as TS implement them today.

dead-claudia commented 7 years ago

@mhegazy Okay. I stand corrected. (It was an educated guess, BTW.)

styfle commented 7 years ago

The Class Fields Proposal is at Stage 2 now so I'm glad the TypeScript team thinking through this and ready to follow EcmaScript standards 💯

If and when the private state proposal reaches the correct state, TS will implement it.

@mhegazy Is Stage 3 the correct time to implement?

EDIT: I may have found my answer 😄

kitsonk commented 7 years ago

@styfle see the discussion from design meeting notes (#16415) that discusses the current considerations around implementation.

mhegazy commented 7 years ago

Is Stage 3 the correct time to implement?

yes. We are starting to investigate the different designs possible for this feature.

calebegg commented 6 years ago

The Class Fields Proposal is now at Stage 3. (As of a month ago)

kitsonk commented 6 years ago

The Class Fields Proposal is now at Stage 3. (As of a month ago)

While the class fields reached Stage 3, the Private Methods and Accessors followed on from that, and was at Stage 2 until yesterday when it was moved to Stage 3. Now everything needed to address private members of classes is at Stage 3.

madeleineostoja commented 6 years ago

Any update on the TS team's position/progress on adopting the #[field] syntax now that it's been at stage 3 for a while? Personally I really dislike it, and much prefer TS's existing private keyword, but at the same time want to be as close to standards as possible.

Kingwl commented 6 years ago

Personally I really dislike it, and much prefer TS's existing private keyword

me too, it's so ugly

but @mhegazy i'd like to (try) do this, could i ?

weswigham commented 6 years ago

The parse shouldn't be bad (almost a new modifier that's just a token, almost), but the typecheck will be a pain (since we'll have two different kinds of privates that'll need to be compared somehow), and the downlevel emit is atrocious (because downleveling real runtime privacy is a big transform - @rbuckton has ideas on it, and maybe an implementation (I don't know for sure; he's got a lot of those in his fork)). We technically can't stop you from trying, but I wouldn't recommend it, if you can find something else you'd rather try, and I couldn't guarantee that we'd be ready to accept it once you were done. Plus, while private fields and methods have advanced, it's still slightly incomplete until private statics (and possibly decorators) advances, too (given how they all interact, it may be better to add them all at once).

Suffice to say, there's a lot here.

Kingwl commented 6 years ago

@weswigham Ok, I give up 😂

mheiber commented 6 years ago

Here's a heads-up that I (with @Neuroboy23 and others) will be experimenting with an implementation of private fields. We will post a link here as soon as there are materials for review/discussion.

Update: Our work in progress is here.

pleerock commented 6 years ago

yes. We are starting to investigate the different designs possible for this feature.

I don't like # syntax either, but I understand the technical reasons behind this syntax. @mhegazy What I would really like to do by a TypeScript team after implementing a new proposal to think what TypeScript is going to do with obsolete implementations like namespace, private which can be used but generally should not since there is a better es-standard way of doing same or almost the same.

I'll also want to add my few cents about protected. If (now hopefully) we are going to remove/deprecate private keyword someday I think its okay to do the same with protected keyword. I know most of people can disagree, but yeah extra visibility scopes can sometimes be handy, but overall value of it isn't high imho.

glen-84 commented 6 years ago

@pleerock,

This is what I'd expect to see from TypeScript:

class Example {
    private a = 1;
    #b = 2;
}

Emit (targeting ESNext):

class Example {
    a = 1;
    #b = 2;
}

Emit (targeting ESNext, with new compiler option to emit fields marked as private as ES private fields):

class Example {
    #a = 1;
    #b = 2;
}

That should be backwards compatible, while also allowing you to use ES private fields with a clean syntax.

pleerock commented 6 years ago

@glen-84 of course it would be, I was talking about long-term plan on obsolete keywords and syntaxes

RyanCavanaugh commented 6 years ago

We do not have any plans to deprecate any syntax or keywords.

weswigham commented 6 years ago

I also don't think we'll have an option of any sort to transpile private into # - they have different expected behavior; # is hard runtime privacy, while private is just design time privacy.

pleerock commented 6 years ago

@weswigham I think so as well. @RyanCavanaugh yeah that was expected answer. But still I would apply at least some strategy for a new users to avoid using namespace/private keywords because imho they got obsolete with latest ecmascript proposals

RyanCavanaugh commented 6 years ago

People generally figure this out based on reading documentation, which we of course update. module foo { is still in the language but is basically never seen in the wild anymore except in very old codebases that predate namespace foo {.

pleerock commented 6 years ago

@RyanCavanaugh good to hear that. But still I believe that new people coming from c# lets say will definitely try to use namespaces (not all, but some of them). I guess the same might be with users coming from other languages - they will start to use private instead of # and this tends to be more massive than example with namespaces

glen-84 commented 6 years ago

I also don't think we'll have an option of any sort to transpile private into # - they have different expected behavior; # is hard runtime privacy, while private is just design time privacy.

D'oh. I was hoping that we could optionally use private for hard privacy as well, to avoid the ... unfortunate syntax.

Anyway, soft-private is probably enough for my (current) use cases.

pleerock commented 6 years ago

I think it would be great to include this syntax support in upcoming 3 version of TypeScript since this feature is huge and version 3 looks a good occasion to make people to start using new # syntax.

EDIT: damn forgot that version 3 is already released.

dead-claudia commented 6 years ago

Just FYI, there is some recent churn about private fields/slots/whatever in the TC39 proposal's repo, so it's not even certain the proposal in its current form will make it. Here's a couple relevant issues there:

I'm not TC39, but my personal suggestion to everyone is to first wait for the syntax to solidify again (possibly wait for stage 4), and then implement it.

RyanCavanaugh commented 6 years ago

FWIW I would not interpret the discussion in tc39/proposal-class-fields#100 to be indicative of anything in particular. # isn't pretty at first blush, but no preferable syntactic form has been suggested and the only syntax that people seem to want is manifestly not workable. There needs to be a prefix sigil of some kind and all attempts to allow this.someProp to be private are doomed to fail.

I realize that sounds rather dismissive, but frankly that is my intent - none of the complainers in those threads have meaningfully engaged with the core technical problems addressed in extant threads or the FAQ. It's depressing as heck to read through threads on that repo and see long correct explanations of why a sigil is needed get downvoted into the ground.

If the endgame is that no one uses private fields unless they really need hard runtime privacy, that's probably a good outcome for the web in terms of performance and debuggability.

dead-claudia commented 6 years ago

I agree it's probably not an ideal issue to mention, but the other, tc39/proposal-class-fields#106, is probably much more indicative - there's serious discussion on using weak maps instead among other things, because proxy interop plainly sucks.


Isiah Meadows contact@isiahmeadows.com www.isiahmeadows.com

On Mon, Jul 30, 2018 at 3:38 PM, Ryan Cavanaugh notifications@github.com wrote:

FWIW I would not interpret the discussion in tc39/proposal-class-fields#100 https://github.com/tc39/proposal-class-fields/issues/100 to be indicative of anything in particular. # isn't pretty at first blush, but no preferable syntactic form has been suggested and the only syntax that people seem to want is manifestly not workable. There needs to be a prefix sigil of some kind and all attempts to allow this.someProp to be private are doomed to fail.

I realize that sounds rather dismissive, but frankly that is my intent - none of the complainers in those threads have meaningfully engaged with the core technical problems addressed in extant threads or the FAQ. It's depressing as heck to read through threads on that repo and see long correct explanations of why a sigil is needed get downvoted into the ground.

If the endgame is that no one uses private fields unless they really need hard runtime privacy, that's probably a good outcome for the web in terms of performance and debuggability.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/Microsoft/TypeScript/issues/9950#issuecomment-408984395, or mute the thread https://github.com/notifications/unsubscribe-auth/AERrBGCphyQUu5lJQW7lgsqALLL3e0_Wks5uL2DLgaJpZM4JVDwV .

littledan commented 6 years ago

There is some discussion of using WeakMaps, but I don't see how that would improve interaction with Proxy. I'd invite any TC39 member who has another idea about how private class features should work to present it to the committee for feedback; currently, this proposal is at Stage 3 and theoretically represents the consensus position of the committee. Many people made compromises to reach a shared goal of enabling this feature, even as other designs are possible.

I am working to organize a discussion among frameworks about how to use Proxy to observe object mutations, and how that should interact with private fields. Let me know if you would like to join the conversation.

MichaelTheriot commented 5 years ago

@RyanCavanaugh

FWIW I would not interpret the discussion in tc39/proposal-class-fields#100 to be indicative of anything in particular. # isn't pretty at first blush, but no preferable syntactic form has been suggested and the only syntax that people seem to want is manifestly not workable. There needs to be a prefix sigil of some kind and all attempts to allow this.someProp to be private are doomed to fail.

Late on this, but there actually is a proposal suggesting this->var syntax.

Granted, this proposal is most likely dead ("the committee was not enthused" is the only reason I have been able to find). But alternate syntax has indeed been proposed, and I hope that all interested parties are familiar and aware of these.