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

Revisiting getters and setters #5394

Open
edemaine opened this issue Jan 23, 2022 · 26 comments
Open

Revisiting getters and setters #5394

edemaine opened this issue Jan 23, 2022 · 26 comments

Comments

@edemaine
Copy link
Contributor

This is a feature request. I think it's time to revisit getters and setters, originally discussed in #4915, and explicitly unsupported on https://coffeescript.org/#unsupported-get-set

Input Code

class Foo
  get random: -> Math.random()
  get name: -> Db.query 'name'
  set name: (newName) -> Db.update 'name', newName

Expected Behavior

class Foo {
  get random() {
    return Math.random();
  }
  get name() {
    return Db.query('name');
  }
  set name(newName) {
    Db.update('name', newName);
  }
}

Current Behavior

error: 'get' cannot be used as a keyword, or as a function call without parentheses

Motivation

I have two arguments for why getters and setters should be added to CoffeeScript.

One argument is increased recommended usage. Previously @GeoffreyBooth argued that getters/setters seemed likely useful in reactive JavaScript libraries, but "my worry is hypothetical, so maybe it’s not worth worrying about until there’s a concrete example". Now a concrete example is SolidJS, an increasingly popular reactive library for building user interfaces, which uses getters/setters extensively as a way to detect reactivity. For example, components get a props argument, and use of props.name in e.g. <h1>Hello {props.name}</h1> automatically reacts to changes to the name property. In the case of props, that's all done by SolidJS automatically, but the SolidJS documentation encourages using getters extensively (try searching the page for get including the space). Specifically, in order to build your own reactive objects that work just like the built-in ones, you build an object with getters and/or setters. In SolidJS, use of reactive data needs to be wrapped in a closure for automatic reactivity to work. For single states, this is done via a "getter" function (a function that takes no arguments), which is fine in CS; but for complex state with multiple attributes, it's done by building an object with multiple getters.

(Incidentally, other than this issue, writing SolidJS apps in CoffeeScript works well. In fact, early versions of SolidJS were written in CoffeeScript! But before the move to getters/setters.)

The second argument is better TypeScript support (for #5307). The recommended workaround for getters and setters in CoffeeScript is to use Object.defineProperty. But TypeScript doesn't understand the effect of Object.defineProperty. It's not even possible to provide an explicit type because a single assignment to the variable must provide all the properties at once, and Object.defineProperty can only do one at a time and only after the object has been initialized.

Context

Conveniently, CS2 already made get foo: -> syntax invalid, and similarly for set. (Actually, get foo: -> alone is valid, but it becomes invalid if you add a blank line before it. This is presumably a bug.) It was reserved for this very purpose: get/set became useful enough to add (and to prevent confusion if someone guessed at the correct notation). So, we can use the obvious syntax and all CS2 code remains compatible.

A get/set prefix would also go well with private, protected, and public annotation prefixes, which are TypeScript features. (Note that private, protected, and public are reserved words in CoffeeScript, so they could be added while being backward incompatible. It's a separate discussion whether they are worth adding.)

Alternatives

If the ambiguity with calling functions get or set is considered too confusing, we could consider a new syntax that is not ambiguous. For example, inspired by ideas in #5367, we could add a : prefix:

class Foo
  :get random: -> Math.random()
  :get name: -> Db.query 'name'
  :set name: (newName) -> Db.update 'name', newName

I personally find this uglier, so would prefer straight get prop:/set prop: syntax.

@GeoffreyBooth
Copy link
Collaborator

An issue is that get is a really common function name. It’s not an edge case to have code like this:

import { get } from 'http'

get 'url', (res) -> # ...

I may be misremembering, but I thought we added the error on get/set without parentheses as a way to protect users from footguns. As in, someone writing get foo: -> was more likely intending to create a getter named foo than what this transpiles to in CoffeeScript 1, which is something along the lines of get({foo: function () {}}). I think this concern is still valid, and I think it wouldn’t be the best approach to remove the error and turn get and set into keywords. I think some other syntax would be acceptable, though.

@edemaine
Copy link
Contributor Author

I agree that get and set can't be changed into keywords. I was rather suggesting that they get treated like keywords in exactly the situation where the error occurs (when in an object literal or class declaration). This seems especially safe when the get/set is not the only thing in an object; then it couldn't mean anything else (you can't have a function call in the middle of an object). Another safe option would be to only allow get/set in an object when it's surrounded by explicit braces. ({ foo bar: ... } doesn't currently make sense.) But I'm not sure of a similar "fix" for classes, as they can always allow arbitrary code.

Brainstorming alternative notation:

  • :get foo: (above)
  • {get foo}: (can't currently have function call in braces like this)
  • get{foo}: or foo{get}: (no space to make this a function call)
  • get~foo: (~ is not allowed as a binary operator)
  • get@foo: (@ is not allowed as a binary operator)
  • foo var get: or foo with get: or with get foo: or foo implements get: or some other reserved word...
  • foo<-: and foo->: (but I think it would be hard to remember which is get and which is set)

I think only the first two are not too ugly. Other ideas?

@GeoffreyBooth
Copy link
Collaborator

We have the precedent that @method means “static.” We could maybe expand that a little and treat @@get as a way to output the get keyword; and likewise for static or public or any other keywords that we need to put in the body of an object or class. Something like:

screen =
  width: 1200
  ratio: 16/9
  @@get height: this.width / this.ratio
  @@set height: (val) -> this.width = val * this.ratio

Or we could use some other symbol or combination of symbols to mean “keyword.” I don’t want to use leading : because that seems like the most logical choice for labels (#5367) which it looks like we need to support now because of Svelte.

@edemaine
Copy link
Contributor Author

@@get/@@set seems like reasonable notation. Nice idea!

Looking ahead, @@ might also be reasonable notation for decorators which are stage 2 and use simple @, which we can't use. They can even be on the same line as the thing being decorated, as in @logged x = 1;.

Luckily, I think @@get x: would be unambiguous even if @@get x = 1 were a decorator, because of the colon: it wouldn't make sense to have a decorated value followed by a colon. In other words, I think @@get notation would be future-proof against decorators. Unless I'm missing something?

@GeoffreyBooth
Copy link
Collaborator

I’m not convinced that decorators will ever reach Stage 4 😉 but sure, we shouldn’t preclude a syntax that would work for them. In https://github.com/tc39/proposal-decorators#class-fields there are examples of a decorator followed by a public class field, which seems like it could be any assignment expression; like @register child1 = new Child();. There are also examples with other keywords, like @myMeta static #z = 456;. Perhaps we should reserve @@ to transpile into @ for decorators, and come up with some other prefix symbol/set of symbols to mean “keyword.”

@edemaine
Copy link
Contributor Author

I was actually thinking of @@get as a kind of special decorator. @@get foo would be a regular decorator, while @@get foo: means that the attribute foo has the special "get" dressing (something that doesn't make sense with regular decorators, as I understand them).

I'm not sure we need general syntax for "keyword" (other than back ticks?). I guess it would be useful for TypeScript's as operator which still needs a concise notation, though I guess `as` would work. But static, private, protected, public are already reserved words in CoffeeScript, so we can just use those.

@GeoffreyBooth
Copy link
Collaborator

If @@get/@@set get “reserved” then wouldn’t that mean you couldn’t have a decorator named get? I don’t think we want to require that, just like we don’t want to prevent functions named get.

@edemaine
Copy link
Contributor Author

edemaine commented Jan 25, 2022

What I had in mind was:

  • In the lexer, @@foo generates a DECORATOR token (currently forbidden by the grammar).
  • In the rewriter, if we see DECORATOR IDENTIFIER :, and the DECORATOR is among a "property decorator" set (currently, get and set), then the DECATOR token is changed to a PROPERTY_DECORATOR.

@GeoffreyBooth
Copy link
Collaborator

So you couldn’t have a decorator named get followed by :, but it seems like currently you can’t have any decorator followed by :, so this would be a restriction without consequence.

It would be nice to avoid needing to use the rewriter. Basically we want to limit the rewriting phase as much as possible, because that file is essentially all the exceptions to our grammar rules.

@edemaine
Copy link
Contributor Author

edemaine commented Jan 26, 2022

FWIW, looking at the code, this would actually be done in the lexer, in the identifierToken method (which already deals with lots of pseudo-keywords). It already deals with IDENTIFIER: all at once. The additional code would be

prev[0] = 'PROPERTY_DECORATOR' if colon and prev[0] == 'DECORATOR'

This is essentially working around the lookahead-1 limitation of the grammar, similar to a lot of other code in the compiler.

@GeoffreyBooth
Copy link
Collaborator

Well, is @@get / @@set the best syntax? I don’t mean to shut down discussion if anyone else has any alternatives.

In my mind “leading colon then identifier” or :label (as in like :foo or :$) would become the new syntax for labels to handle Svelte, and := is the leading contender for “TypeScript colon” as in foo := string = 'bar' becomes foo: string = 'bar'.

@jashkenas
Copy link
Owner

I haven't been taking a look at these various issues — and please don't take my vote as authoritative since I'm mostly just a passive observer at this point, but FWIW, my two cents are that CoffeeScript:

  • Should not add getters and setters (they're a bad language feature, even if SolidJS wants to use them)
  • Should not add labels (The Svelte syntax is an ugly hack)
  • Should not add decorators (higher order functions already work)
  • Should not add TypeScript-specific syntax (CoffeeScript should focus on targeting/simplifying JavaScript. Let's make a different language to target TypeScript)

@edemaine
Copy link
Contributor Author

@GeoffreyBooth I generally agree, except for :=. That symbol means assignment in so many languages (Python in particular) that I don't see it as type annotation... That said, I don't have a better notation, so maybe it is best. 🙂

The @@get / @@set notation makes me see get@foo and set@foo (mentioned above) in new light. I think I prefer the @@get foo: though.

@jashkenas CoffeeScript doesn't exist in a vacuum; it's part of the larger JavaScript ecosystem. TypeScript and Svelte are extremely popular, and SolidJS is growing fast. (I'm not sure the best way to measure that claim, but how about GitHub stars: 77k, 55k, and 14k respectively. For reference, CS has 16k.) Do you not want those thousands of developers to use CoffeeScript? Adding optional features to CoffeeScript that enable its use in these modern contexts seems worthwhile, like JSX which has proved immensely useful in React (180k stars) and Solid.

You might also consider the chilling effect of your comments like this one; it makes me at least less inclined to help improve/modernize CoffeeScript if you don't want it to change. If you'd rather I fork into a new language, that's fine, but like @GeoffreyBooth I see a lot of value to improving CoffeeScript.

@orenelbaum
Copy link

IMO not adding basic things like getters and setters would mean that this language will never get any significant adoption again, and even adding TS support won't help.

@GeoffreyBooth
Copy link
Collaborator

Sorry if I left the impression that I thought we should definitely add all those things. I was trying to sketch out what the syntaxes would be if they all were to land, just so that we don’t choose a syntax for one that might paint us into a corner if we wanted to add another. As in, it’s better to have workable syntaxes for everything we can think of that we might want to add, and choose to not add some of them, than to have suboptimal syntaxes for features we add because we didn’t consider as many features as possible in advance.

Whenever we add new features there’s always the question of whether the tradeoffs are worth it. Each addition has costs:

  • CoffeeScript gets a little less simple and “clean,” which is a major selling point for the language.
  • CoffeeScript’s learning curve increases a little, as even users who don’t use a new feature might need to learn what it is if they encounter it in others’ CoffeeScript.

For a feature to be worth adding, it needs benefits to outweigh these costs. In recent years just about nothing has passed this tradeoff test other than new ES features, since the learning curve there is lower (since the feature also exists in JavaScript) and it doesn’t make CoffeeScript feel meaningfully less complex since the feature is familiar from JavaScript.

For an addition that’s not new ES syntax to pass this test, it generally needs to have one thing going for it: interoperability. There are plenty of languages that are beautiful and syntactically perfect and never used for production projects. CoffeeScript is not such a language. Our simplicity needs to yield to the pragmatism of needing to work with other popular JavaScript libraries and tools. People may want to use CoffeeScript because it’s clean, but they won’t use CoffeeScript if they can’t, if CoffeeScript doesn’t interoperate with some other part of their project; and users shouldn’t need to choose between CoffeeScript and other components. It’s our job to play nice, even if it means swallowing our pride and occasionally adopting things some would consider “bad parts,” such as JSX.

With regard to the specific items under discussion, I feel like CoffeeScript needs a good interoperability story with SolidJS, Svelte, and TypeScript. That may involve adding a getter/setter shorthand syntax, or labels, or outputting TypeScript syntax static types; or we could find other ways to interoperate. In the TypeScript case, in particular, I would prefer we improve our comments output so that JSDoc works in all the cases where we would want it to, as this alone would solve TypeScript interoperability. And so on. Maybe the best way to use CoffeeScript with those projects is to add new syntax, but that’s not a foregone conclusion.

Decorators are an unusual case. I’ve forbidden their use in projects I’ve run at work, because they’re so unstable and on an especially uncertain trajectory; there’s no way that CoffeeScript will support them anytime soon, if we ever do. (And if JavaScript itself ever does, for that matter.) However, two popular web components libraries, Lit and Stencil, both use decorators extensively. Presumably they will continue to do so once decorators eventually stabilize, and we’ll be forced to support the syntax somehow in order for people to use CoffeeScript with those libraries, unless there’s some easy workaround (such as an intuitive way to convert a decorator into a higher-order function). It’s not enough that the user could look at the JavaScript transpiled out of those libraries’ decorators and write CoffeeScript against that; the same was always true with React and JSX, but that’s too terrible of a user experience to consider acceptable, especially when we want users to see examples in JavaScript for these libraries and be able to easily write the equivalent CoffeeScript.

So anyway I think the bottom line is that I understand the desire to keep CoffeeScript pure and clean, and we should try to do so as much as possible; but CoffeeScript isn’t on a pedestal, and it needs to be used to survive, and that means we need to follow the trends somewhat and interoperate with the popular libraries. It’s unfortunate that getters and setters are having a renaissance (another web components library, Polymer, uses getters and setters extensively) and I never would have guessed that labels would be rescued out of Douglas Crockford’s recycle bin, but here we are. If there’s a way to support CoffeeScript users of those libraries without adding more clutter to CoffeeScript, that would be ideal, but ultimately I think interoperability is a higher priority than purity.

@jashkenas
Copy link
Owner

Like I said — please don't take my vote as authoritative. Y'all are running the show here, and @GeoffreyBooth has the commit bit. I'd like for you to continue to develop things in the way you think is best.

@phil294
Copy link

phil294 commented Jan 26, 2022

I agree with almost everything @GeoffreyBooth says. It is so refreshing to see a popular project being maintained by such a rational, receptive and reflective person!

Do we even want to encourage programmers to build new projects with CoffeeScript? If the answer to this question is yes, then imo there is no way getters/setters can stay unsupported. Can we lead the same discussion for every new addition to the language? Keeping to the current strategy of supporting every reasonable feature of JS as soon as it hits stage 4 (consequently possibly including decorators some day) is the way to go, I'd say. If not, CoffeeScript's only future prospect is maintenance of legacy codebases, and it should have never done v2. Legacy codebases can still happily continue to use traditional CoffeeScript, regardless of whether we add getters or TypeScript support. Regarding the latter: While Svelte or React will die at some point, TS arguably never will, not until JS itself is dead, so there's no way around it. But I'm also unsure if it should be core language or a well highlighted fork or a new compile flag etc.

@edemaine
Copy link
Contributor Author

This is getting a little off topic for this thread, but given the mention of a compiler flag for TypeScript: this might be worth reconsidering. I've been noticing at the least that the output file really only makes sense with a .ts (or .tsx) extension, but the compiler uses .js. Admittedly this can be fixed with the -o option, but not if you're specifying a directory. Adding a -t or similar option that changes the default extension could also open up the possibility of adding new keywords like as and type, and possibly alternative breaking syntax for type annotation.

@GeoffreyBooth
Copy link
Collaborator

given the mention of a compiler flag for TypeScript: this might be worth reconsidering.

I think this is definitely on the table; this was on my mind on the TypeScript thread when I said I wanted to avoid breaking changes at all costs. There’s a lot of other messiness and caveats with adding a flag that I don’t want to get into here, but I think first we should get as far as we can with improving output that doesn’t involve new syntax (so the var stuff, JSDoc comments) and then we should evaluate the various options.

Getting back to getters and setters, before I initially commented on this thread I reread the old thread from the CoffeeScript 2 discussion, and basically my takeaway was that at the time we thought that the feature was one of JavaScript’s “bad parts” but only mildly so, and since there didn’t seem to have a strong need for the shorthand syntax that we could make do with the verbose syntax and stop there. (It’s worth remembering that CoffeeScript has always supported getters and setters, just via the Object.defineProperty / Object.defineProperties approach rather than the shorthand syntax.) Even if the shorthand syntax was the only syntax, there’s a strong argument for supporting the feature in at least some form: the DOM itself and many core browser APIs use getters and setters extensively. Emulating or proxying such APIs is a legitimate thing to want to be able to do, and so therefore we need to provide a way to create getters and setters. The only question now is whether we also need the shorthand syntax (and if so, what our syntax for that should be).

Here’s an example of getters in the Solid docs:

const mapped = indexArray(source, (model) => {
  return {
    get id() {
      return model().id
    }
    get firstInitial() {
      return model().firstName[0];
    },
    get fullName() {
      return `${model().firstName} ${model().lastName}`;
    },
  }
});

How would this be achieved in CoffeeScript today? Probably something like this?

mapped = indexArray source, (model) =>
  Object.defineProperties {},
    id:
      get: -> model().id
    firstInitial:
      get: -> model().firstName[0]
    fullName:
      get: -> "#{model().firstName} #{model().lastName}"

You could even get fancy and inline the value of each property:

mapped = indexArray source, (model) =>
  Object.defineProperties {},
    id: get: -> model().id
    firstInitial: get: -> model().firstName[0]
    fullName: get: -> "#{model().firstName} #{model().lastName}"

Now this looks awfully similar to some of the proposed new syntaxes.

So then the question becomes, is @@get id: -> model().id significantly better than id: get: -> model().id? I get that TypeScript doesn’t know how to infer the return value of Object.defineProperty or Object.defineProperties, but that to me feels like a shortcoming on TypeScript’s part and not a reason for us to add syntax. (It is a reason to improve our comments output so that JSDoc can be interspersed as needed to address this; it would solve @edemaine’s example above.)

The example in the docs only shows Object.defineProperty, not Object.defineProperties, and doesn’t show that either method returns the defined object (meaning that you don’t necessarily need to declare it ahead of time, as in my examples here). So perhaps an update to the docs is in order, at the very least.

@edemaine
Copy link
Contributor Author

edemaine commented Jan 26, 2022

Cool, I didn't know about Object.defineProperties; that's definitely nicer, and should at least make it possible for TypeScript to understand it's type (whereas multiple calls to Object.defineProperty wouldn't be consistent with TypeScript's preference for variables of a single unchanging type). I also didn't know that TypeScript with JSDoc allowed you to declare an initially undefined object property with a type that doesn't include undefined; as far as I can tell, you can't do that with regular : type annotation. @phil294 points out that this isn't actually working: if you hover over height in the last line of your example, then it says height: undefined.

Other than working with TypeScript today, I think where a get/set notation would really benefit is when you have a mix of regular properties and get/set properties, as in the example just above the one you mentioned in the Solid docs:

const mapped = mapArray(source, (model) => {
  const [name, setName] = createSignal(model.name);
  const [description, setDescription] = createSignal(model.description);

  return {
    id: model.id,
    get name() {
      return name();
    },
    get description() {
      return description();
    }
    setName,
    setDescription
  }
});

In CoffeeScript today, that would look something like this:

mapped = mapArray source, (model) ->
  [name, setName] = createSignal model.name
  [description, setDescription] = createSignal model.description

  Object.defineProperties {id: model.id, setName, setDescription),
    name: get: -> name()
    description: get: -> description()

It's not terrible thanks to defineProperties' first argument, but I feel like it'd be more symmetric (and let you use your preferred property order) as

mapped = mapArray source, (model) ->
  [name, setName] = createSignal model.name
  [description, setDescription] = createSignal model.description

  id: model.id
  @@get name: -> name()
  @@get description: -> description()
  setName: setName
  setDescription: setDescription

It'd also be a lot easier to switch between regular properties and get/set properties when the notation is like this.

There were a bunch of ideas in the old thread about property notation that specifies a property with a nested object with get: and set:, like Object.defineProperties. This makes me wonder about the following alternate notation based on @@...::

  id: model.id
  @@name: get: -> name()
  @@description: get: -> description()
  setName: setName
  setDescription: setDescription
  @@automagic:
    get: -> ...
    set: -> ...

This is farther from ECMAScript syntax, though, and a bit harder to parse.

@GeoffreyBooth
Copy link
Collaborator

if you hover over height in the last line of your example, then it says height: undefined.

That’s the TypeScript playground, so it’s expecting TypeScript input. If you paste it into VS Code as JavaScript and hover over height in the last line, it’ll be detected as number:

image

If property order is a concern, you could declare all the properties in the initial object declaration, with the ones that will become getters set to undefined and then replaced by Object.defineProperties (similar to how height is declared as undefined here).

@edemaine
Copy link
Contributor Author

edemaine commented Jan 26, 2022

You can actually switch the TypeScript playground to JavaScript input mode under the "TS Config" menu. If you also turn on strictNullChecks (which I believe is common), you also get the error Type 'undefined' is not assignable to type 'number'.. (I'm also seeing the error Property 'ratio' does not exist on type 'set'. for some reason, even though that doesn't happen in TS mode.)

So this isn't really a great solution. TypeScript seems fine with omitting initializers (var foo: number; or var foo; in JS mode), but generally not fine with explicit undefined initializers.

Regarding order: I didn't mean the actual order in the object (though I guess that could be a concern). I meant the order in which you want to write your code. It's nice for the programmer to have design agency here, for example, if they wanted to put the definitions of name and setName next to each other, and similarly for description and setDescription.

@GeoffreyBooth
Copy link
Collaborator

You could define the entire type explicitly:

/**
 * @typedef Rectangle
 * @property {number} width
 * @property {number} ratio
 * @property {number} [height]
 */
/** @type {Rectangle} */
var rectangle = Object.defineProperties({
  width: 1200,
  ratio: 16 / 9
}, {
  height: {
    get() {
      return this.width / this.ratio;
    },
    set(val) {
      return this.width = val * this.ratio;
    }
  }
});

console.log(rectangle.height);

And this has no errors; the explicit JSDoc overrides the implicit typing. As for Property 'ratio' does not exist on type 'set'., this appears to be a bug in TypeScript, where it mistakenly thinks that this within the set function refers to the set function itself rather than the object getting a new property. When set is written with the method syntax set(val) rather than set: function(val), TypeScript no longer errors. We already output class methods using that syntax; we could (and should) output object methods likewise, and that would eliminate this issue from CoffeeScript-generated code.

Anyway, yes, the core argument for the shorthand syntax has always been that it looks nicer or is easier to read or allows prettier code, or however you’d like to frame the desire for a clearer way to express the definition of getters and setters. And forcing users to use ugly syntax instead is not so subtly implying that getters/setters are frowned upon and shouldn’t be used. So we’re back to the tradeoffs: is adding a syntax for the shorthand syntax worth the costs. I don’t know.

@MadcapJake
Copy link

MadcapJake commented Jun 24, 2022

If you're going down the @@ sigil path, why not just place it directly on the name? The getting and setting could be determined from function signature.

class Foo
  @@random: -> Math.random()
  @@name: -> Db.query 'name'
  @@name: (newName) -> Db.update 'name', newName

I don't particularly like how close it is to @ while being very different in action.

What about a named attribute like property? You could also go with a shorter prop but I think the full version fits more with CoffeeScript ethos.

class Foo
  property random: -> Math.random()
  property name: -> Db.query 'name'
  property name: (newName) -> Db.update 'name', newName

This might be easier to implement since it's basically a function with an object argument.

You could also go down a similar path to some other languages and add a symbol after the slot name:

class Foo
  random^: -> Math.random()
  name^: -> Db.query 'name'
  name=: (newName) -> Db.update 'name', newName

^ being completely original whereas = has precedent.

I also think hacking the computed property syntax looks ok but would still lead to conflicts albeit inside of computed property expressions only.

class Foo
  [get random]: -> Math.random()
  [get name]: -> Db.query 'name'
  [set name]: (newName) -> Db.update 'name', newName

For that matter, I don't mind the {get foo}: idea mentioned above in a similar vein with less conflicts.

@johndeighan
Copy link

CoffeeScript really needs to implement getters and setters. And, it should work just like the JavaScript version - no hacks, please. For some reason, the assumption is made above that a word like 'get' or 'set' must be either a keyword or an identifier (i.e. variable name, function name, etc.). There are plenty of languages (PL/1, I seem to recall) that allow any keyword to also be used as the name of a function, variable, etc. It's all down to how the parser is implemented, and admittedly, I don't know the internals of CoffeeScript. The following BNF should work:

<classdef> ::= "class"  <identifier> <propdef>+
<propdef> ::= ("get" | "set")? <identifier> ':' <propbody>

i.e. when the parser is looking for the name of a property, it gets the first token. Then it gets another token. If the 2nd token is ':', the first token is the name of the property and it's not a getter or setter. If the 2nd token is an identifier, then it's the name of the property and 1) the first token must have been 'get' or 'set', otherwise it's an error, and the next token must be a ':' or it's an error.

@johndeighan
Copy link

I'm starting to work with parser generators again, and I've come to a realization regarding my last comment. The examples I've seen make, what I think is a mistake in their parser definitions. That is, they ask the lexer to make a distinction which should really be done by the parser - whether a word it sees is a keyword or an identifier. If the lexer makes that distinction, then it's not possible (or at least very difficult) for the parser to allow a keyword to be used as the name of a variable or function. I think that the lexer should only have to say "I found a word - and here is the string version of it". The parser should handle treating it as a keyword or identifier, depending on the context in which it finds that token.

Now, I admit that it's generally a bad practice to use a keyword as an identifier, e.g. creating a variable named "if". But here is a clear case where, because the words 'get' and 'set' have been used as names of functions/methods in the past, we want to allow that and also allow 'get' and 'set' as keywords. That should be possible.

I just did some googling and though I can't really vouch for the source, it appears that although JavaScript won't let you name a function 'if' or 'while', it will allow you to name a function 'get' or 'set'. So, clearly, the JavaScript parser doesn't force a word to be either a keyword or an identifier.

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

No branches or pull requests

7 participants