Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement private fields proposal #9950

Closed
dead-claudia opened this issue Jul 26, 2016 · 141 comments
Closed

Implement private fields proposal #9950

dead-claudia opened this issue Jul 26, 2016 · 141 comments
Assignees
Labels
Committed The team has roadmapped this issue Fixed A PR has been merged for this issue Suggestion An idea for TypeScript

Comments

@dead-claudia
Copy link

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.

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript In Discussion Not yet reached consensus labels Jul 26, 2016
@glen-84
Copy link

glen-84 commented Aug 14, 2016

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
Copy link
Contributor

kitsonk commented Aug 15, 2016

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
Copy link

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 (tc39/proposal-private-fields#32) and instead @ would be used, so you'd use @field, just like Ruby. Do you find this syntax ugly still?

@dalexander01
Copy link

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

@littledan
Copy link

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 tc39/proposal-private-fields#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
Copy link

glen-84 commented Aug 20, 2016

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 tc39/proposal-private-fields#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
Copy link
Contributor

kitsonk commented Aug 21, 2016

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
Copy link

glen-84 commented Aug 21, 2016

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
Copy link

glen-84 commented Aug 21, 2016

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
Copy link

glen-84 commented Aug 21, 2016

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
Copy link
Contributor

kitsonk commented Aug 21, 2016

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
Copy link

@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
Copy link

glen-84 commented Aug 21, 2016

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
Copy link

glen-84 commented Aug 21, 2016

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
Copy link

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
Copy link

shelby3 commented Sep 12, 2016

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
Copy link

@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
Copy link

shelby3 commented Sep 13, 2016

@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
Copy link
Contributor

about-code commented Dec 15, 2016

@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

  • that it allows to express the contract of a type's interface and how it is meant to be used by clients
  • that the contract is checked at compile time by the TS compiler
  • that I can still consciously "violate" the contract at runtime, particularly for whitebox unit tests.

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
Copy link

glen-84 commented Dec 15, 2016

@about-code See tc39/proposal-private-fields#33

@igabesz
Copy link

igabesz commented May 11, 2017

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
Copy link
Author

dead-claudia commented May 11, 2017

@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
Copy link

glen-84 commented May 11, 2017

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
Copy link
Contributor

mhegazy commented May 11, 2017

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
Copy link
Author

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

@styfle
Copy link
Contributor

styfle commented Jun 17, 2017

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
Copy link
Contributor

kitsonk commented Jun 17, 2017

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

@phaux
Copy link

phaux commented Mar 30, 2020

but i must admit i'm really bummed we don't have shorthand syntax (see proposal)

That shorthand would be incompatible with pipeline proposal which also might use # and it's already discussed to become stage-2 proposal. It really doesn't make sense to implement proposals this early and then make breaking changes. At such early stage you don't even know which proposal will win.

Even if it turns out to be compatible, I can think of at least a few more use cases for a prefix # operator which are more useful than being a shorthand for typing 5 extra characters.

@ljharb
Copy link
Contributor

ljharb commented Mar 30, 2020

… especially when a good portion of the use case for private fields isn't on this at all (eg, static isMe(obj) { try { obj.#x; return true; } catch { return false; } }, to name one example).

@chase-moskal
Copy link

@phaux — that's really good information about shorthand vs pipeline, thanks!

@jhpratt

TypeScript has been firm on not implementing anything before stage 3, even under a flag.

fair enough, but then it must be pointed out, that private methods are in fact stage 3, but there is no typescript support yet — and look, i'm very fond of the shorthand proposal, but that's just aesthetic — i'm actually now much more bummed about the lack of private methods and accessors right now

i assume (pray) that the bloomberg team are continuing god's work here on typescript, as they did for babel a year ago? if so, then we really just need to be patient and sit tight :)

it does seem to me like typescript used to be much more competitive with babel and browsers for ecmascript features, and now it's actually too conservative — we're in a situation where both babel and chrome had shipped private fields (unflagged) for a year before bloomberg came along to save us

i'm uneasy by the creeping divergence between the typescript and babel featureset — my babel friends have been writing beautifully slick code for the last year, whereas i'm still here paying a typescript penalty — they're laughing at me!

i loved that typescript used to offer experimental flags that allowed me to decide what i'm willing to risk refactoring in the future to buy me some great features — is it now typescript's position, that it's too dangerous to let developers decide those risks, and that typescript is now my nanny?

more and more, it looks like i can choose awesome features, or static types — but i can't now have the best of both worlds, so there is a growing cognitive dissonance within me

i'm really curious about the dynamics here — if bloomberg didn't step in like a knight in shining armor, how long would it have been before typescript took up the challenge themselves? or is this a typescript experiment: "if we just avoid these new features.. how long before the community will just.. do it for us?", hah, that's a pretty cynical even for me to wonder! or maybe this is a really cool benevolent collaboration exploring new avenues for funding and support for open source, and it's just been a little slower than expected? or perhaps typescript's philosophy just a lot more conservative than it used to be? where are we on this spectrum?

for implementing stage 3 features, is it more that typescript is lacking bandwidth, or priority?

sorry ranting on, and hey, i'm really not trying to be rude or level serious accusations here, just tossing thoughts around from my outsider perspective. i'm just a user and i'm genuinely curious about how folks here more in-the-know might react to these pictures i've painted — interesting discussion, i appreciate you all!

👋 chase

@0kku
Copy link

0kku commented Mar 31, 2020

i loved that typescript used to offer experimental flags that allowed me to decide what i'm willing to risk refactoring in the future to buy me some great features — is it now typescript's position, that it's too dangerous to let developers decide those risks, and that typescript is now my nanny?

It's not about nannying you. It's about not wanting to spend time implementing features that will inevitably become a maintenance burden when the semantics change in the proposal. They absolutely don't want to spend time figuring out how to reconcile over the changed semantics when they are in conflict with TS's implementation. They have better things to do. There's still a mountain of type-related problems to solve, so it makes sense to spend their limited resources on that, and not try to do TC39's job on top of solving the type system problems.

As for private methods and accessors: Those are not implemented in V8 either. Class methods and instance fields are different, and they can't just... flip a switch to support those too. It takes actual time to implement them and they have their hands full with work already as it is and they're a small team. Furthermore, the rule isn't just that the proposal needs to be at stage 3: they also explicitly say that they want to have high confidence in the proposal's semantics to be final. Though rare, the semantics can change at Stage 3. The feature is not implemented in V8 for no good reason. Don't get me wrong though; I too would love to see that proposal implemented, and can't wait to use it. However, implementing proposals from earlier stages is a waste of time and the TS team's time is better spent elsewhere. There are countless Stage 2, 1 and 0 proposals that I would love to use today, but I understand why I can't have them yet. Patience my friend.

@ljharb
Copy link
Contributor

ljharb commented Mar 31, 2020

You're also quite able to use babel, exclusively, to transpile your typescript, if you prefer the options babel makes available to you.

@chase-moskal
Copy link

@0kku — so you're saying it's just a bandwidth/capacity problem, rather than a philosophical one — the backlog of bugs is now higher priority than the implementation of stage 3 features, however back in typescript's earlier history, perhaps it had fewer bugs, and so it was easier to allocate capacity to experimental implementations? i can buy this theory, and our response should be to wait, or contribute

@ljharb — oh now that is very interesting — is it actually possible to run typescript and babel together in such a way? to achieve the best of both worlds?

but i just can't see how the vscode ts language service could still work without imploding on the unfamiliar syntax (like private methods for example)? lacking intellisense bites too big a chunk out of typescript's value proposition — if intellisense has to be disabled entirely, the strategy is a non-starter

how could this be fixed in the longer term? i suppose if we distinguished specific typescript errors for each experimental feature's syntax, and also, if a typescript project could disable those specific errors at the tsconfig level — then we'd have a shot at opening the door to leverage both babel features and typescript features at the same time — man that would be really cool, and knock out the entire class of gripes.. cheers!

👋 chase

@mbrowne
Copy link

mbrowne commented Mar 31, 2020

@ljharb

You're also quite able to use babel, exclusively, to transpile your typescript

I suppose you could set up a type-checking process where you transpile just the private methods using Babel (but leave in the types on everything else) and do typechecking on that code with tsc. (The build script, for after typechecking passes, would just use Babel alone.) But you'd have to ignore lots of syntax errors in your IDE - I think that would be a deal-breaker for most people.

@chase-moskal Although what @0kku said is correct in general, I haven't seen any reason not to implement private methods in TS at this point. I think it's more that that proposal reached stage 3 later than class fields, and the implementers are still working on it.

@RyanCavanaugh
Copy link
Member

If you're curious why we're not keen to jump on experimental features, please see the enormous complexity clusterfoxtrot that is --useDefineForClassFields. We implemented class property intializers very, very early under the assumption that no possible TC39 proposal with different semantics could even plausibly exist, and we were quite wrong.

Decorators are trending toward being in the same boat, and you're in for a surprise if you think we can drop support entirely for the old semantics. Multiple large companies have built large well-adopted frameworks around it; it's entirely unrealistic that we could say "Well the flag name started with experimental so you should have known that this feature supported for the last 5 years might just up and disappear one day".

For both of those features we're going to be stuck maintaining two subtly different codepaths here for the rest of our lives, and it sucks, but that's the trade-off -- we're very committed to keeping build-to-build TypeScript updates possible without breaking the runtime behavior of your code; this tradeoff is invisible when it's in your favor (e.g. your program keeps working) but you can see the downside in terms of eager feature adoption more readily.

From our perspective, you cannot disclaim away version-to-version runtime compatibility, the same way you cannot waive away responsibility for negligence.

We also held off on early adoption of optional chaining despite MASSIVE community pushback - if I had a nickel for every time someone said "Well just put it behind a flag", I'd be typing this from a boat. But it was the right thing to do, because we would have had to guess what (null)?.prop produced, and we absolutely would have guessed null (not undefined), and we'd be living with another ongoing complexity burden to figure that one out. There is a strong counterfactual upside here for the fact that no one is stuck sitting on a codebase of 3 million lines filled with ?. usage where they depend on that producing null sometimes, with no way to even figure out how to transition forward to the different undefined behavior.

Bloomberg implemented private fields because they were very excited to have the feature and were eager to contribute to TypeScript. If they hadn't been in this situation, we would have implemented the feature ourselves, just like everything else.

In general I would say that the timeline of JavaScript is very, very long, and that realistically you can write good and ergonomic JavaScript in 2020 without using 2024's syntax, just like you could write good and ergonomic JS in 2016 without using 2020's syntax. No one is blocked by the inability to use syntax that doesn't even have finally-decided semantics yet; it's just imaginary programming at that point.

@mbrowne
Copy link

mbrowne commented Mar 31, 2020

Not only that, but it's not even 100% certain that the current class fields proposal and all its details will make it to stage 4 in its current form (especially now that there is a TC39 member company agitating to scrap the proposal completely and replace it with a different one). I would guess 95% certainty, but in general even implementing a stage 3 proposal is not completely without risk. However, the likelihood of further changes after stage 3 is very low, so I think the current TS policy is a good one.

@trusktr
Copy link
Contributor

trusktr commented Mar 31, 2020

now that there is a TC39 member company agitating to scrap the [class fields] proposal completely and replace it with a different one

Interesting, mind point a link to that?


Here's an idea: perhaps the TS team could spend some time to improve the plugin system, to give plugin authors a clear API that hooks into both compile-time and language-server-protocol intellisense-time. This would make it possible for 3rd parties to implement any experimental features they want including new syntax, and still have working intellisense inside IDEs and text editors.

Last I checked, ttypescript is the only way to configure TypeScript transformers non-programmatically in tsconfig.json, and it doesn't hook into intellisense of any IDEs, so the ability for 3rd parties to make meaningful features that integrate well with existing tools like VS Code aren't there yet. We wouldn't want to give up our nice VS Code experience by switching to ttypescript and needing to rely on terminal output while VS Code is unable to understand syntax and throw errors.

Then TypeScript could focus on the stable features, and let 3rd parties make or use risky experimental features (as easy as it is in the Babel ecosystem).

@mbrowne
Copy link

mbrowne commented Mar 31, 2020

Interesting, mind point a link to that?

#30829 (comment)

@RyanCavanaugh
Copy link
Member

There's a very limited set of syntactic things you can do with a Babel plugin; basically the parser has to already support it. The do expression "plugin", for example, still requires logic in the core of Babel itself: https://github.com/babel/babel/blob/master/packages/babel-parser/src/parser/expression.js#L991 It's really not any different from a commandline switch.

@0kku
Copy link

0kku commented Mar 31, 2020

Is there an issue that can be followed for private class methods and accessors?

@Bessonov
Copy link

Bessonov commented Mar 31, 2020

@RyanCavanaugh

For both of those features we're going to be stuck maintaining two subtly different codepaths here for the rest of our lives

I don't think that it's should go this way and I can imagine that it hurts the whole community. If decorators reach stage 3 I think it's reasonable to provide some time to upgrade to the new version of decorators. May be support both for a time, may be make a hard switch and drop legacy decorators in TypeScript 4+. I don't know the difference between old and new decorators, because I don't use them (well, experimental feature). But I think that affected projects should work on the proposal, if the proposal doesn't meet their needs. It's better for everyone. TypeScript is the wrong place to stir up the war about legacy and experimental features.

@arthur5005

This comment has been minimized.

@mbrowne
Copy link

mbrowne commented Apr 2, 2020

@RyanCavanaugh

There's a very limited set of syntactic things you can do with a Babel plugin; basically the parser has to already support it.

True. But in the interest of comparing with TypeScript, in Babel you can fork just the parser and set up your babel.config.js to use the custom parser and any custom syntax plugins you create. Yes it's a lot of work (especially learning how to add new syntax for the first time), but I'm not aware of any equivalent option for TS, and of course in Babel it's much easier to write transformation plugins based on existing syntax. I'm not sure what the current state of customization options is in TypeScript, but when I last looked into it a couple years ago it looked like there was no option to extend it other than forking the entire thing.

@Shinigami92
Copy link

@robot56
Copy link

robot56 commented Aug 4, 2020

The hash for privates IMO is ugly and confusing. It’s hard enforced because there’s no way to implement them otherwise. There’s no build step like there is in TS. The effect is it just clogs up code with symbols for the very little gain of a “hard private” with people thinking it’s the ‘correct’ way to write JS as many people are taught that things should just be private by default.
There should have been more proposal before pushing it into the standard, especially when there’s been obvious disagreement on the matter.

@ljharb
Copy link
Contributor

ljharb commented Aug 4, 2020

@robot56 things should just be private by default. They're taught that all things should be accessible via reflection, which is not, in fact, a good thing for anyone.

@robot56
Copy link

robot56 commented Aug 4, 2020

@ljharb Yes, they should. However when the way to declare a private becomes throwing hash symbols in front of variables, the “correct” way to make a class in JS means teaching people to just putting hashes in front of member variables. Not just when declaring them, but also when referencing them. It’s especially confusing if you’re coming from other languages. My first thought when seeing something like this.#x = this.#something(); and declaration in a class was that the hash was apart of the variable itself. I would have never guessed it was a modifier. The underscore for public, that seems backwards too. This isn’t really relevant to TS, but just an annoying rant I guess.

@ljharb
Copy link
Contributor

ljharb commented Aug 4, 2020

Yes, learning new things when one is cemented in familiarity might be an adjustment. I have faith that the JS community will continue learning!

(the hash is part of the variable itself, the name of this.#x is not "x, but private", but in fact, #x, a unique value in that lexical scope)

@TeoTN
Copy link

TeoTN commented Sep 16, 2020

Yes, learning new things when one is cemented in familiarity might be an adjustment. I have faith that the JS community will continue learning!

The way you put it, sounds like if that was learning something adding to our programming wisdom, whereas this is just another quirk of JS that one has to memorize :D

@Neo-Ciber94
Copy link

Neo-Ciber94 commented Jan 17, 2022

Would be nice to have a flag to make the compiler emit #private fields

// tsconfig.json
{
    emitPrivateClassMembers: true
}

Which simply converts this

// myclass.ts
class MyClass {
    private value = 0;
    getValue() {  return this.value; }
    setValue(x: number) { this.value = x; }
}

To this

// dist/myclass.js
class MyClass {
    constructor() {
        this.#value = 0;
    }
    getValue() {  return this.#value; }
    setValue(x) { this.#value = x; }
}

@angelhdzmultimedia
Copy link

angelhdzmultimedia commented Apr 18, 2023

Would be nice to have a flag to make the compiler emit #private fields

// tsconfig.json
{
    emitPrivateClassMembers: true
}

Which simply converts this

// myclass.ts
class MyClass {
    private value = 0;
    getValue() {  return this.value; }
    setValue(x: number) { this.value = x; }
}

To this

// dist/myclass.js
class MyClass {
    constructor() {
        this.#value = 0;
    }
    getValue() {  return this.#value; }
    setValue(x) { this.#value = x; }
}

Any update on this? I'm so used to private _fieldOrMethod (AS3, C#, Dart) that I have been ignoring the #fieldOrMethod syntax.
Being able to set an option private fields/methods to # in tsconfig.json would be really nice.

Thank you for all the nice job being done.

@RyanCavanaugh
Copy link
Member

This was discussed and explicitly rejected at #31670. I don't think anything has moved in the interim that would cause us to revisit that decision.

@angelhdzmultimedia
Copy link

This was discussed and explicitly rejected at #31670. I don't think anything has moved in the interim that would cause us to revisit that decision.

It would have been nicer if you kindly pointed me to a solution, that I had to find myself after reading some of the replies to that thread, instead of the rude and implied "we decided NOT, so deal with it" (yes, I read your other responses).

My concern was that when transpiled, my private members would be left exposed, but the solution is, that TS transpiles the private members to WeakMap, so, not as private as with the #, but at least the members will have some kind of privacy in JSland.

Didn't know about that WeakMap trick, so your link at least provided me with a solution, and, for that I'm thankful. 🤯🔥

Have a nice rest of the day, wish you health.

@ljharb
Copy link
Contributor

ljharb commented Apr 19, 2023

@angelhdzmultimedia a closed-over WeakMap is precisely as private as native private fields; that was part of the proposal design.

@angelhdzmultimedia
Copy link

@angelhdzmultimedia a closed-over WeakMap is precisely as private as native private fields; that was part of the proposal design.

Awesome discovery! Thank you! Now I am relieved.

@RyanCavanaugh
Copy link
Member

Just to clarify, I'm here (on GitHub) to give provide information about TypeScript feature prioritization, not JavaScript runtime behavior. If you're looking for information about how to write code with certain behavior in JS, Discord, StackOverflow, etc, are all available and encouraged, and indeed you'll see me helping out there from time to time. But I don't have time to offer 1:1 JS support on years-old closed issues in all cases and expectations should be set accordingly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Committed The team has roadmapped this issue Fixed A PR has been merged for this issue Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests