Skip to content
This repository has been archived by the owner on Jan 25, 2022. It is now read-only.

Expected interop with private field access? #28

Closed
loganfsmyth opened this issue Aug 29, 2017 · 23 comments · Fixed by tc39/proposal-class-fields#301
Closed

Expected interop with private field access? #28

loganfsmyth opened this issue Aug 29, 2017 · 23 comments · Fixed by tc39/proposal-class-fields#301

Comments

@loganfsmyth
Copy link

What is the expected interop with private-field property access? https://tc39.github.io/proposal-class-fields/#sec-updated-syntax

Should

foo?.#prop

be considered valid syntax?

@ljharb
Copy link
Member

ljharb commented Aug 29, 2017

I would hope that it would still throw if the slot wasn't present, and only reacted to null/undefined

@loganfsmyth
Copy link
Author

Could you clarify what you mean by only reacted to null/undefined? My assumption was essentially that it'd be an identical transformation as the normal property access, e.g.

var val = base?.#prop;
// to
var val = base == null ? void 0 : base.#prop;

I think that is what you're describing too?

@ljharb
Copy link
Member

ljharb commented Aug 29, 2017

ah, while that's not what I meant, i guess that'd make more sense.

@claudepache
Copy link
Collaborator

Yes, class fields as well as private methods will naturally be considered with the obvious semantics. I expect to add a note in the explainer (but it is premature to add that in the spec text proper at this point).

@claudepache
Copy link
Collaborator

@ljharb

I would hope that it would still throw if the slot wasn't present, and only reacted to null/undefined

A possible misconception (or misleading mental model) is to consider ?. as if it was some sort of error-silenting operator. By design, the operator does a tight and well defined test (quite the opposite of an error-silenting operator) on its LHS, and does not change the semantics of its RHS (whether it throws or not).

@littledan
Copy link
Member

This has been asked a number of times before. I think the syntax of x?.#y composes pretty naturally. However, it's hard for me to think of a use case. Just as this proposal currently omits new C.?(), I think it's reasonable to start with leaving out x?.#y even if private fields are already at Stage 4 by the time this gets to Stage 2-3.

I agree with others in this thread that I'd prefer the semantics of x?.#y to just affect operation on null/undefined, and not suppress other, unrelated TypeErrors.

@claudepache
Copy link
Collaborator

I’ve not thought of a concrete use case, but the way I would try to construct one is the following. Think of a situation where you expect either an object of specific shape (in our specific case, an instance of the current class), or null/undefined. Then, think of an action to trigger with that object (get a value, call a method, etc.), that you want to skip when you have null/undefined.

More generally, that situation of “either an object of known shape or null” is (I think) a major source of use cases for optional chaining, be it optional property access, optional method call, optional function call, and even optional property deletion or optional property assignment (#18).

I agree with others in this thread that I'd prefer the semantics of x?.#y to just affect operation on null/undefined, and not suppress other, unrelated TypeErrors.

Again, optional chaining has never been intended to “suppress errors”. (At least in my mind.)

@Mouvedia
Copy link

Again, optional chaining has never been intended to “suppress errors”.

It could also have been interpreted as choose where I let it throw granularly.

@claudepache
Copy link
Collaborator

It is difficult to find use cases, because it is unusual to use private fields/methods with anything else than this, to begin with. What are the custom uses cases of private stuff with non-this base?

@ljharb
Copy link
Member

ljharb commented Dec 3, 2018

@claudepache i don't find it particularly unusual:

static isEqual(a, b) {
  return a.#id === b.#id; // or something 
}

also:

constructor(stringOrInstance) {
  if (typeof stringOrInstance === 'string') {
    this.#id = string;
  } else {
    this.#id = stringOrInstance.#id; // construct via copy
  }
}

@littledan
Copy link
Member

Not sure why discussion is starting again now, but I am wondering if we'd be OK omitting this operation for now for the reasons I explained in #28 (comment) .

@ljharb
Copy link
Member

ljharb commented Dec 6, 2018

I think it doesn't add much value to allow it now - but I also think it makes the language a bit more consistent. a?.#b still wouldn't suppress the error if a lacked the private field for #b - but it would allow pivoting on whether it was undefined or null.

Specifically, I'd envision a use case of an initializer of #id;, and then using .? in other code in the same class to short-circuit unless the #id had previously been set (while still throwing on an invalid receiver)

@littledan
Copy link
Member

I'd like to just be content with the understanding that the syntax composes well, and leave this for consideration to add in the future. This proposal is specifically not trying to add all combinations, but start with the three most useful ones.

@adrianheine
Copy link

Private element accesses can still occur as part of an optional chain, though, right?

class X {
  #c;
  c() {
     return this.a?.b.#c // return this.a != null && this.a.b.#c
  }
}

@littledan
Copy link
Member

@adrianheine Good question! I guess this is about whether short-circuiting applies. I'd argue it should apply.

@jridgewell
Copy link
Member

There’s currently not a grammar production that can handle a?.b.#c. It’s probably something we can address once class fields is at stage 4?

@claudepache
Copy link
Collaborator

claudepache commented Aug 8, 2019

Now that both private fields and optional chaining are at stage 3, the issue of their interaction becomes more pressing.

Just to be clear, cases like a.#b?.c are not problematic, except that there might be conflicts between PRs tc39/ecma262#1646 and tc39/ecma262#1655 that need to be carefully resolved, as both touch LeftHandSideExpression productions. The issue is whether to support private fields:

  • at the beginning of an optional chain: a?.#b, or a?.#b.c().d, etc.
  • inside an optional chain: a?.b.#c, etc.

@ljharb
Copy link
Member

ljharb commented Aug 8, 2019

I’d say both - is there any reason not to?

@claudepache
Copy link
Collaborator

Here is my argument why we should support it (both a?.#b and a?.b.#c):

A private field access is (from a user point-of-view) very similar to a (public) property access. Therefore it seems preferable to support both accesses at the same places, whether the access is optional or not. It would be surprising if, for example, a?.b works, but not a?.#b


As a plausible practical use of that feature, #28 (comment) above suggests the following:

static isEqual(a, b) {
    // it is assumed that `a`, resp. `b`
    // is either an instance of the current class or null.
  return a?.#id === b?.#id;
}

@leonardoraele
Copy link

leonardoraele commented Mar 9, 2020

I believe copy constructors and clone methods are valid user cases too.

constructor(other) {
    this.#prop = other?.#prop;
}
static clone(subject) {
    const result = new MyClass();
    result.#prop = subject?.#prop;
    return result;
}

Whenever you are dealing with other instances of the same class, this syntax is relevant.

jridgewell added a commit to jridgewell/proposal-class-fields that referenced this issue Mar 10, 2020
This adds the `?. PrivateIdentifier` and `OptionalChain . PrivateIdentifier` grammars and related runtime semantics.

Closes tc39/proposal-optional-chaining#28
@jridgewell
Copy link
Member

This has been added to the Class Fields proposal. Closed by tc39/proposal-class-fields#301

@claudepache
Copy link
Collaborator

This has been added to the Class Fields proposal. Closed by tc39/proposal-class-fields#301

tc39/proposal-class-fields#301 resolved a?.b.#c. See discussion in tc39/proposal-class-fields#302 for the case of a?.#b.

@jridgewell
Copy link
Member

Both are added via tc39/proposal-class-fields#301, see tc39/proposal-class-fields#301 (comment). The initial PR added both, with feedback we changed to just a?.b.#c, then committee consensus was to add both so I went back to the original.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants