Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Lay groundwork for lexical scope & improve errors
First, this PR lays the groundwork for Ember RFC #432, which allows components, helpers and modifiers to be passed around as first-class values. Second, it improves the compiler infrastructure to enable errors to be reported (rather than thrown). Reported errors can now include a source location to aid in reporting the error to the user. *First-class components, helpers, and modifiers* Currently, helpers and modifiers have the syntactic form `{{some-helper some parameters}}`, and `some-helper` is required to be a single literal name. That name is then resolved (globally) into a helper by the environment, and the compiler emits a call to that resolved helper into the compiled program. Notably, the helper must be fully resolved at compile-time, and the following does not work as expected: ``` {{#let x as |hello|}} {{hello world}} {{/let}} ``` Glimmer does not currently have any way to construct a first-class helper, so this gap is not particularly observable. On the other hand: ``` {{#let (component "my-tab") as |tab|}} {{tab some=args}} {{/let}} ``` This *does* work in Ember, but before this commit, it was not directly supported in Glimmer. Instead, Ember does an AST transformation on the above syntax to turn `{{tab some=args}}` in this situation into `{{component tab some=args}}`. The same problems exist in Glimmer VM for modifiers, which also have no first-class representation yet. All of these problems are addressed by changing the wire format (the compilation IR) in Glimmer so that these positions are now just expressions rather strings. Additionally, this commit changes a number of disparate wire format representations into a single "free variable" representation. A free variable is a variable reference that refers to a name that is not declared in the current scope. After this PR, the name `hello` is a free variable in all of the following contexts: - `{{hello}}` - `{{hello.world}}` - `{{hello world}}` - `{{helper hello}}` - `{{#hello}}{{/hello}}` - `<hello />` To maintain compatibility, free variables in the wire format include a context. For example in `{{hello world}}`, the `hello` is a free variable whose context is a helper call, which means that the compiler will try to find a helper named `hello`, and otherwise produce an error. In `{{hello}}`, the context is `AppendSingleId`. That means that the compiler will try to find a helper named `hello`, and if it fails to find one, it will fall back to `{{this.hello}}`. These behaviors are present in compatibility mode, which is the only mode currently implemented. In strict mode (described in in-progress RFC 496), all helpers, components and modifiers must be imported, so free variables have no semantics at all. Finally, while this PR lays the groundwork for RFC 432, it does not implement contextual modifiers or helpers, nor does it implement the necessary compilation work to invoke a contextual helper. However, that work is relatively straight forward now that the groundwork is laid. *Better Errors* In addition, this PR introduces improved error reporting. Previously, the template compiler and opcode compiler threw exceptions if they encountered compilation problems. Now, the compilation stages can produce errors alongside the compilation output, which can be reported to users. For example, if the opcode compiler fails to resolve a helper, it emits an `undefined` value instead of a helper invocation, and records the error. The entire compilation process produces a runnable program as well as a list of encountered errors. In addition to improving error reporting, this PR also allows errors to include a source location, and plumbs the source locations from the parser all the way through to the opcode compiler. This PR makes a first use of that infrastructure by reporting the source location of a helper that could not be resolved when an error occurred. It does not fully eliminate all thrown exceptions. Next steps would include reporting the source location of unresolved modifiers and components, as well as beefing up existing error cases to go through the reporting infrastructure and have source locations. Once that work is done, we should plumb the locations further, so that opcodes could associate failures with source locations, and eventually associating references themselves with their original source locations. To accomplish those goals, we will need a good strategy for avoiding overhead in production.
- Loading branch information