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

Lay groundwork for lexical scope & improve errors #972

Merged
merged 7 commits into from
Oct 18, 2019
Merged

Conversation

wycats
Copy link
Contributor

@wycats wycats commented Sep 24, 2019

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.

@chadhietala
Copy link
Member

Might want to delete the yarn-error.log file.

package.json Outdated Show resolved Hide resolved
package.json Outdated Show resolved Hide resolved
package.json Outdated Show resolved Hide resolved
wycats and others added 2 commits October 17, 2019 18:31
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.
Replaces amd-name-resolver and broccoli-typescript-compiler forked GitHub dependencies with released versions
@tomdale tomdale merged commit c91f3e7 into master Oct 18, 2019
@Turbo87 Turbo87 deleted the lexical-scope branch October 28, 2019 20:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants