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

Add representation for meta properties #32

Merged
merged 1 commit into from Mar 26, 2015
Merged

Add representation for meta properties #32

merged 1 commit into from Mar 26, 2015

Conversation

RReverser
Copy link
Member

ECMAScript gets new syntax thing call "meta properties", which is already used as new.target and, according to meeting notes, can be used in future in other places as well.

For those cases, we need a new node that (up to discussion) derives from MemberExpression but is restricted to having Identifier in both object (as it's actually a keyword used as Identifier) and property and can't be computed (i.e. new['target'] is syntax error).

@michaelficarra
Copy link
Member

The new.target production has little to do with MemberExpression, other than being at the same precedence level. In the Shift AST, we simply introduced a NewTarget node. If you're concerned about yield.input and other future meta properties, I suggest this:

interface MetaProperty <: Expression {
    type: "MetaProperty";
    object: Identifier;
    property: Identifier;
}

@RReverser
Copy link
Member Author

  1. Yeah, I'm concerned about future meta-properties and willing to design node that would be flexible enough to cover those with minimal changes.
  2. I thought about it, but I don't think that MetaProperty would be correct type here since Property in AST represents completely different syntactic construction (object property) and having name that is so similar and differs only with prefix might be super confusing.
  3. On other hand, syntactic construct like obj.prop is the closest one to meta properties from both parser and "user familiarity" perspective, and so, while I'm not saying that we must inherit from MemberExpression, I do think that MetaMemberExpression or, if you wish, MetaMember will be less confusing for consumers than MetaProperty is.

@michaelficarra
Copy link
Member

  1. That's a reasonable position.
  2. I don't think there's any reason to be concerned about people confusing MetaProperty with object properties.
  3. I can't agree with this. They really have nothing to do with each other. They just look similar in the concrete syntax. That shouldn't affect the design of the AST, as it is only concerned about the underlying structure of the program.

@sebmck
Copy link

sebmck commented Feb 19, 2015

Inheriting from MemberExpression and having computed: false doesn't make much sense because it'll never be true as it has no concept of computed even though it just looks like.

I agree with @michaelficarra here. I don't really think an ambiguous name (MetaProperty isn't ambiguous anyway IMO) is a problem since if a consumer ever wants to deal with those nodes they'll already be familiar with them and if they aren't then this is where the spec comes in.

@mikesherov
Copy link
Contributor

I agree with @michaelficarra on all points here.

@getify
Copy link
Contributor

getify commented Mar 4, 2015

They just look similar in the concrete syntax.

Building off that mindset, I don't think that object (and quite possibly property) are necessarily the right names, as they imply something like the new part being an "object", which is only an accident of the appearance in the concrete syntax, not how JS is actually treating it.

What about context instead of object? And perhaps what about meta or flag instead of property?

interface MetaProperty <: Expression {
    type: "MetaProperty";
    context: Identifier;
    meta: Identifier;
}

@michaelficarra
Copy link
Member

interface MetaProperty <: Expression {
    type: "MetaProperty";
    kind: "new.target";
}

kind can be extended in the future to allow "yield.input" or other indicators. I don't see a reason to split them on . since the components of a meta property aren't individually meaningful.

@getify
Copy link
Contributor

getify commented Mar 4, 2015

since the components of a meta property aren't individually meaningful.

Is that entirely true? The grammar says:

NewTarget :
    new . target

I'm unable to validate at the moment if that means that new.target must appear directly together as a single identifier, or if new . target or new \n .target or other variations are going to be allowed. The same grammar for super . identifier does appear to allow whitespace in/around the ., so I'm guessing the same will be true of new.target. If that's true, then we of course may have concrete syntax associated with each part individually.


If it turns out that only new.target altogether is allowed, no whitespace, then I think it should be:

kind: "newTarget";

To match the terminology used in the spec, rather than the new.target JS syntax, which we're basically then asserting is nothing more than an identifier that can have a . in it.

@michaelficarra
Copy link
Member

They are separate tokens, so whitespace is indeed allowed. But that has no bearing on whether we would represent the MetaProperty using two values or one. If we want to start storing concrete syntax, we could simply store each concrete syntax element in separate named fields for the four possible positions whitespace is allowed. We would also need to store the escape sequences used to represent the MetaProperty. Anyway, I don't think we should be concerning ourselves with those things right now, since we currently have no plan to add concrete syntax info to the AST.

@getify
Copy link
Contributor

getify commented Mar 4, 2015

since we currently have no plan to add concrete syntax info to the AST.

I object to making any "final decisions" on any of the stuff here that then paints us into an awkward corner when (later) trying to even decide if and how we might approach concrete syntax. No, we haven't officially decided to doing so or not, but we also shouldn't design as if there's no knowledge that such a thing might be a concern in the near future.

In the same way that we can say "you know, yield.input (or whatever) may happen, let's not unnecessarily restrict ourselves", I think we can also reasonably say "let's not rush to assume that new.target couldn't possibly benefit from individual addressability."

Does it really gain us that much to collapse to kind: "newTarget"?

@dead-claudia
Copy link
Contributor

Since the spec appears to simply whitelist it, and further discussion in ES
Discuss (there's a thread on other proposed meta properties, but there has
also always been consensus on whitelisted properties only), I don't quite
see the problem of simply collapsing it to kind.

Now, as for "newTarget" being the collapsed form, I find it easier to
reason "new.target" instead. It is easier to connect to the meta property
itself than the camel cased version, and if you ever need the keyword
itself, it's a little simpler to figure out, especially if camel cased
identifiers are eventually (inevitably) introduced as the "property" name:

// my proposal
// "new.target"
// -> keyword = "new"
// -> property = "target"
let [keyword, property] = node.kind.split(".");

// current idea
// "newTarget"
// -> keyword = "new"
// -> property = "target"
let keyword, property;
{
  let {kind} = node;
  let re = /[A-Z]/;
  re.test(kind);
  let index = re.lastIndex;
  keyword = kind.slice(0, index);
  property = kind[index].toLowerCase() +
    kind.slice(index + 1);
}

I don't see much justification for not collapsing the properties,
especially if my proposal is accepted for how it's collapsed. Even if not,
I don't see much use for not collapsing the property to a simple kind,
since there does seem to be long term consensus on the meta properties
being exclusively on a whitelist (with all unknown ones being (sadly) a
compile time error (have to feature test and then send a conditional Ajax
request or a conditional module substitution with the future module
loader...sorry for the rambling)).
On Mar 4, 2015 2:31 AM, "Kyle Simpson" notifications@github.com wrote:

since we currently have no plan to add concrete syntax info to the AST.

I object to making any "final decisions" on any of the stuff here that
then paints us into an awkward corner when (later) trying to even decide if
and how we might approach concrete syntax. No, we haven't officially
decided to doing so or not, but we also shouldn't design as if there's no
knowledge that such a thing might be a concern in the near future.

In the same way that we can say "you know, yield.input (or whatever) may
happen, let's not unnecessarily restrict ourselves", I think we can also
reasonably say "let's not rush to assume that new.target couldn't
possibly benefit from individual addressability."

Does it really gain us that much to collapse to kind: "newTarget"?


Reply to this email directly or view it on GitHub
#32 (comment).

@sebmck
Copy link

sebmck commented Mar 9, 2015

@IMPinball You shouldn't have to resort to hacks like that to get the keyword and property.

@dead-claudia
Copy link
Contributor

Actually, I take back my entire support for collapsing the node, since it would better support position information interesting for refactoring tools. Also, the spec appears to allow syntactically insignificant whitespace on either side of the dot, as in traditional member accessors.

It would still be easy to check if left as explicitly extending MemberExpression with no extra properties and some prose to explain its purpose separate from MemberExpression.

function isMetaProperty(node, type) {
  if (node.type !== "MetaProperty") {
    return false;
  }
  if (type == null) {
    return true;
  }
  let [keyword, property] = type.split(".");
  return node.object.name === keyword &&
    node.property.name === property;
}

// ...
if (isMetaProperty(node)) {
  // it's a meta property
}
if (isMetaProperty(node, "new.target")) {
  // it's new.target specifically
}

@RReverser
Copy link
Member Author

Actually, I take back my entire support for collapsing the node, since it would better support position information interesting for refactoring tools.

👍

@dead-claudia
Copy link
Contributor

Clarification (I forgot): all of these are equivalent according to the
spec, and this is why I said that.

new.target
////////////////////////
new.

    target
////////////////////////
    new

.target

Sorry about it.
On Mar 9, 2015 5:47 PM, "Ingvar Stepanyan" notifications@github.com wrote:

Actually, I take back my entire support for collapsing the node, since it
would better support position information interesting for refactoring tools.

[image: 👍]


Reply to this email directly or view it on GitHub
#32 (comment).

@RReverser
Copy link
Member Author

So, to get conclusion on this... I agree with @IMPinball and understand concerns about inheritance from MemberExpression. I actually like keyword and property names for this, as they are pretty clear when used in context of meta properties.

How about following?

interface MetaProperty <: Expression {
    type: "MetaProperty";
    keyword: Identifier;
    property: Identifier;
}

@dead-claudia
Copy link
Contributor

LGTM
On Mar 12, 2015 12:42 PM, "Ingvar Stepanyan" notifications@github.com
wrote:

So, to get conclusion on this... I agree with @IMPinball
https://github.com/impinball and understand concerns about inheritance
from MemberExpression. I actually like keyword and property names for
this, as they are pretty clear when used in context of meta properties.

How about following?

interface MetaProperty <: Expression {
type: "MetaProperty";
keyword: Identifier;
property: Identifier;
}


Reply to this email directly or view it on GitHub
#32 (comment).

@getify
Copy link
Contributor

getify commented Mar 13, 2015

Are we (reasonably) certain that all future meta properties are going to be keyword.property? So far, new.target and a possible future yield.input have been thrown around. new and yield are definitely "keywords", though technically the spec says that yield sometimes has the semantics of "Identifier".

Is it reasonably possible that in the future there's a metaproperty which is not based on a keyword? For example, words like constructor, static, let, get, set, from, module, and as are not called "keywords" in the spec. Could there be things like static.method or let.property? Also, there's several literals like true, false, null, etc, which could theoretically be used, like null.empty, right?

Also, the module export mechanism apparently supports a special token of this module (note the space, not period). It's unclear to me if that one should be considered a "metaproperty" or not, but if other such things are conceived in the future, might they need a way to be represented in this node type?

Just don't want us to regret a name like "keyword" sometime down the road.

@getify
Copy link
Contributor

getify commented Mar 15, 2015

Further info on potential future metaproperties:

https://github.com/allenwb/ESideas/blob/master/ES7MetaProps.md

Looks like all those so far hang off function. But it clearly lets us know there's a good chance there may in fact be quite a few of these. We should design as flexibly as possible.

@getify
Copy link
Contributor

getify commented Mar 15, 2015

More info:

https://esdiscuss.org/topic/function-name-property

That thread starts out talking about function.name but morphs into a general discussion about meta properties, their original design and intent, etc. Good back-story context for them, if you haven't read it yet.

Some observations:

  1. Several messages tossed around ideas like in.function.
  2. Some people floated ideas like =>.self.

@dead-claudia
Copy link
Contributor

@getify Good catch. Maybe we should come up with some better representation/node name instead of keyword for the first. Maybe instead using host and property, so it's a little more precise?

Also, am I the only one wondering about the merge conflicts with this PR?

@getify
Copy link
Contributor

getify commented Mar 16, 2015

i'd be ok with "host". I suggested "context" earlier in the thread, too.

@jeffmo
Copy link

jeffmo commented Mar 18, 2015

After our weekly esprima meeting, it seems most of us are onboard with the structure that @RReverser suggested above, except that Identifer probably can't be used since the tokens on either side of the . can (and already are) keywords and not identifiers.

So an example of the structure that at least everyone in that meeting was ok with was:

{
  type: 'MetaProperty',
  meta: {
    type: 'Meta',
    value: 'new'
  },
  property: {
    type: 'Property',
    value: 'target'
  }
}

The benefits of this structure are (a) it allows things like esprima/acorn/etc to extend the structure and include location info alongside the 'new' and 'target' tokens (since it's possible to have whitespace between them) in a consistent way with how it includes location data for other nodes (b) it makes the overall structure immediately distinct from MemberExpression.

@RReverser
Copy link
Member Author

Well, we already have a precedent of using e.g. property: Identifier in representation of obj.new - new is also generally a keyword but is used as Identifier in current context, which is similar to MetaProperty case (keyword-as-an-Identifier-when-used-in-certain-type).

Also, in:

property: {
  type: 'Property',
  value: 'target'
}

using Property type is rather incorrect since this type has different meaning and structure, see https://github.com/estree/estree/blob/master/spec.md#property for details.

@jeffmo
Copy link

jeffmo commented Mar 18, 2015

using Property type is rather incorrect since this type has different meaning and structure

Works for me. I'm not personally tied to any of those names, but I think @michaelficarra found them appealing. I'm mostly interested in the structure (I'd really like for it to be possible to extract location info for new separately from target)

@RReverser
Copy link
Member Author

@michaelficarra sometimes tries to patch ESTree to look it more like Shift :P just saying

@RReverser
Copy link
Member Author

But yeah, as for structure I'm totally with you and I actually like meta and property pair more than any of previous proposals.

@mikesherov
Copy link
Contributor

{
  type: 'MetaProperty',
  meta: {
    type: 'MetaPropertyMeta',
    value: 'new'
  },
  property: {
    type: 'MetaPropertyProperty',
    value: 'target'
  }
}

@RReverser @sebmck @jeffmo ?

@michaelficarra
Copy link
Member

I had actually forgotten that Property was a node in SpiderMonkey during today's meeting. Whoops.

👍 @mikesherov.

@nzakas
Copy link
Contributor

nzakas commented Mar 18, 2015

👍

@sebmck
Copy link

sebmck commented Mar 26, 2015

@RReverser Abstract it away into helpers then, it's painful to do any AST introspection without them. The Babel equivalent would be t.isMetaProperty(node, { meta: { name: "new" }, property: { name: "target" } }). (Although that's just as long)

@RReverser
Copy link
Member Author

Maybe kind? But it would break "uniqueness" principle (btw when are we going to merge those?)

@RReverser
Copy link
Member Author

Well yeah, utils help a lot :)

@gibson042
Copy link

I don't think runtime semantics should be taken into consideration that much when designing the AST structure

@sebmck: That is in precise contradiction to @RReverser's #32 (comment) :

What is good for description of semantics, isn't same good for syntactic manipulations, that's why those deserve separate specs and rules.

And that seems to be the real root of disagreement here.

@RReverser
Copy link
Member Author

That is in precise contradiction

Why do you think it's a contradiction?

@gibson042
Copy link

Because one view considers only syntax in designing the AST structure, while the other also factors in semantics.

@sebmck
Copy link

sebmck commented Mar 26, 2015

@gibson042 I never said that semantics don't matter.

@mikesherov
Copy link
Contributor

Guys, let's not devolve here. The theoretical arguments about syntax vs. semantics can go on for days if we don't curb it, and are completely trumped by how people are actually using the AST. The only argument that is currently contentious at the moment (because we've exhausted the rest), is the one about visitor pattern / inspection.

@sebmck
Copy link

sebmck commented Mar 26, 2015

Here's a real life scenario where splitting up all the meta properties into separate nodes is a terrible idea, at least for this specific scenario.

I have a loop with a let variable:

function foo() {
  for (let num of nums) {}
}

It has a let variable. My block scoping transformer just changes that let to a var since that's the only changes that need to be done for this scenario, producing:

function foo() {
  for (var num of nums) {}
}

But now I add a reference inside a closure to my loop body:

function foo() {
  for (let num of nums) {
    bar(function () { num; });
  }
}

Oh no. Now I need to ensure that num is retained and correct to each loop iteration. I do this by wrapping it in a closure like so:

function foo() {
  for (var num of nums) {
    (function (num) {
      bar(function () { num; });
    })(num);
  }
}

Oh wait, fuck. That means that if someone does:

function foo() {
  for (let num of nums) {
    bar(function () { num; });
    function.count;
  }
}

It'll produce:

function foo() {
  for (var num of nums) {
    (function (num) {
      bar(function () { num; });
      function.count;
    })(num);
  }
}

Shit. It references the wrong function. Looks like I'll have to memoise those references to before the closure to something like:

function foo() {
  for (var num of nums) {
    var _function$count = function.count;;
    (function (num) {
      bar(function () { num; });
      _function$count;
    })(num);
  }
}

Perfect!

Now how would I catch all the function.*?

With node types for each meta property I need to go in and special case every single one.

traverse(node, {
  enter(node) {
    if (node.type === "FunctionCountMeta" || node.type === ...) {}
  }
});

With a single node:

traverse(node, {
  MetaProperty(node) {
    if (node.meta.name === "function") {}
  }
});

Not only does that simplify the visitor logic but it's future proof against new function meta properties.

@sebmck
Copy link

sebmck commented Mar 26, 2015

Also having separate nodes for each meta property is crap for code generation:

export function FunctionCountMeta() {
  this.push("function.count");
}

export function FunctionCalleeMeta() {
  this.push("function.callee");
}

...

vs:

export function MetaProperty(node, print) {
  print(node.meta);
  this.push(".");
  print(node.property);
}

Hell, you could just share the same code generation method as MemberExpression.

@sebmck
Copy link

sebmck commented Mar 26, 2015

Also, important to note that ESTree needs to balance the needs of all consumers. A linter doesn't give a shit about the difference between function.callee and function.count so it just adds unnecessary complexity to operations on those nodes.

@sebmck
Copy link

sebmck commented Mar 26, 2015

Also another important thing to note is that you can do:

new.
target

Combing both of those distinct properties new and target into the same node means you're losing tracking information about where they start and end so a linter can't find the different tokens very easily to verify whitespace etc.

@sebmck
Copy link

sebmck commented Mar 26, 2015

Apologies for the long narratives but I'm pretty sure all the issues I raised put the nail in the coffin about distinct node types, happy to be proven wrong.

@RReverser
Copy link
Member Author

👍 sounds reasonable to me, let's just leave it as is and stop bike-shedding

@gibson042
Copy link

Tools aware of the type hierarchy can still do all that logic you just described for MetaProperty, and introducing NewTarget <: MetaProperty changes only the value of "type" (i.e., it doesn't eliminate the Identifier properties).

Lack of subtypes, on the other hand, could complicate visitor code if meta properties start being used for very different purposes—new.target and function.callee may have similar semantics, but there's no guarantee that such a basic language production will always be limited to scope introspection.

P.S. Thank you very much for the code examples. They help in clarifying issues more than anything else.

P.P.S.

Hell, you could just share the same code generation method as MemberExpression

I also like such reuse and want to enable more of it, but note that MetaProperty as committed describes the syntax like <meta> . <property> while MemberExpression is <object> . <property>.

@RReverser
Copy link
Member Author

Tools aware of the type hierarchy

That's the key. Hierarchy is needed for very few tools, and we shouldn't force all the other tools to complicate their codebase by either checking even more different types or introducing hierarchy.

@sebmck
Copy link

sebmck commented Mar 26, 2015

(i.e., it doesn't eliminate the Identifier properties).

Right, in the case I must have misinterpreted what was being proposed. If anything that makes it worse since then you have two pieces of linking information that can go out of sync. It's been said before that changes/additions that duplicate metadata and information wont be accepted due to how dangerous they are.

@gibson042
Copy link

two pieces of linking information that can go out of sync

An excellent point. I'm sold.

@mikesherov
Copy link
Contributor

I just wanted to say, now, that this thread is concluded, that having discussion until everyone is satisfied is important, and a different activity then getting a thread to the point of mergeability.

While this was a long and arduous thread, the fact that we stuck around and talked it through despite getting heated trumps any frustration any of us experienced.

All members should keep this in mind in further discussions: aside from suggestions for throwing out the AST completely, or seriously breaking es5 BC, we must be willing to fully discuss the points here.

@monolithed
Copy link

So, what the current status?

@sebmck
Copy link

sebmck commented Aug 16, 2015

@monolithed Well it was merged?

@monolithed
Copy link

@sebmck, I don't think so :)

@sebmck
Copy link

sebmck commented Aug 16, 2015

@monolithed No I think so. I was the one who merged it :) The merge commit is 3560243

screen shot 2015-08-15 at 9 13 54 pm

@monolithed
Copy link

@sebmck, I got it, could you please explain what are we waiting for babel/babel#1088?

@sebmck
Copy link

sebmck commented Aug 16, 2015

@monolithed new.target semantics are intricate and I haven't had enough time to determine how much I can replicate them with just static analysis and some trickery. It's not a super high priority.

@monolithed
Copy link

@sebmck, thanks!

@JLHwung JLHwung mentioned this pull request Aug 10, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet