simplify and generalize `@extern` support
Currently `@extern` attribute support has a few limitations
and issues:
- it only allows a single `@extern` kind per file, which means we can't
mix different kinds of `@extern` attribute (for example injection and
embedding)
- it only allows a single extern attribute of a given kind in a field,
even though it's fine to define two instances of the same field, each
with an extern attribute.
- it allows embedding an extern attribute, but only when inside a field
definition
- it is not allowed to include an extern attribute at the top level of a
file or as the value of a pattern constraint or dynamic field.
- it gets the semantics wrong when injecting an external value into an
expression.
An example of the last issue:
```
exec cue mod init
exec cue export .
-- foo.cue --
@extern(embed)
package foo
a: {
blah: *false | bool
@embed(file=foo.json)
}.blah
-- foo.json --
{"blah": true}
```
One might expect the above example to succeed: the `foo.json` value
substituted at the position of the `@embed` attribute and unified with
the `blah: *false|bool` field before the `.blah` selector is applied.
Unfortunately we actually we an error in this case:
```
> exec cue export .
[stderr]
a: 2 errors in empty disjunction:
a: conflicting values {blah:true} and bool (mismatched types struct and bool):
./foo.cue:5:4
./foo.cue:6:20
foo.json:1:3
a: conflicting values {blah:true} and false (mismatched types struct and bool):
./foo.cue:5:4
./foo.cue:6:12
foo.json:1:3
[exit status 1]
FAIL: x.txtar:2: unexpected command failure
```
This error happens because the extern logic adds the conjunction to the
innermost field (`a` in this case) even when inside some other
expression.
This change fixes all the above issues, making extern support
significantly more general.
Specifically:
- when an extern attribute appears on a field, its value is unified with the field's value
- when an extern attribute appears as an embedded attribute, its value is included
at the site of the embedding.
There are no other restrictions.
Thus:
- it allows any number of `@extern` attributes in a file
- it allows any number of extern attributes on a field or at embedding
level
- it allows an extern attribute on any field, even a field without a
regular label name. The label name is there as a hint for the
interpreter, but it doesn't seem to me that it should be mandatory (and
indeed the `@embed` interpreter ignores it).
- an embedded extern attribute is _not_ associated with a field: its
value is substituted in place, and any name must be provided explicitly.
Technically this is a breaking change, but as `embed` is the only
interpreter currently in wide use and it ignores the field name, this
should not affect any users.
Thus the following test now passes:
```
exec cue mod init
exec cue export .
cmp stdout want-stdout
-- foo.cue --
@extern(embed)
package foo
@embed(file=foo.json)
@embed(file=foo2.json)
a: {
blah: *false | bool
@embed(file=foo.json)
}.blah
-- foo.json --
{"blah": true}
-- foo2.json --
{"other": true}
-- want-stdout --
{
"a": true,
"blah": true,
"other": true
}
```
We also remove support for the legacy (and never fully enabled) non-file-level `@extern`
syntax, which hopefully no-one is using - it's certainly never been documented.
So this code will no longer work:
```
@extern(embed)
package foo
x: _ @extern(file=foo.json)
```
Signed-off-by: Roger Peppe <rogpeppe@gmail.com>
Change-Id: I707778dd20bb69b4fb7a4d7327dbe7b9307a1850
Reviewed-on: https://cue.gerrithub.io/c/cue-lang/cue/+/1233920
Reviewed-by: Matthew Sackman <matthew@cue.works>
TryBot-Result: CUEcueckoo <cueckoo@cuelang.org>
Unity-Result: CUE porcuepine <cue.porcuepine@gmail.com>