bevyengine / bevyengine/bevy

`bsn!` generates invalid code when using unqualified enum variants/generic types

Open
#24,588 2 comments 0 reactions 0 assignees View on GitHub
A-Scenes C-Bug D-Complex D-Macros S-Needs-Design
Dominant language
Rust
Stars
48.2k
Forks
4.8k
Avg merge
3d 22h
Merged PRs (30d)
161

Description

## Bevy version and features

0.19.0 RC 1 through 3

## What you did

Trying to construct unqualified enum variants in a `bsn!`, both as components or fields:
```rust
#[derive(Component, FromTemplate)]
enum Foo {
#[default]
Variant(u32),
}
// allows unqualified reference, Rust's prelude does this for `None`/`Some` et. al
use Foo::Variant;

let _ = bsn! {
Variant(42) // ERROR: expected type, found variant
};
```
```rust
#[derive(Clone, Default)]
enum Foo {
#[default]
Default,
Variant(u32),
}
use Foo::Variant;

#[derive(Component, FromTemplate)]
struct Bar { v: Foo }

let _ = bsn! { // ERROR: no field `0` on type `Foo`
Bar { v: Variant(42) } // ERROR: expected type, found variant
};
```
Note that this affects `Some`, `Ok`, `Err` etc.

Trying to construct generic types, again either as a component or a field:
```rust
#[derive(Component, FromTemplate)]
struct Baz {
v: T,
}
let _ = bsn! {
Baz { v: 42 } // ERROR: missing generics for struct `Baz`
};
```

## What went wrong
For unqualified variants, there are two separate issues: first the generated code attempts to use a variant symbol as a type, and second it attempts to assign fields of the variant which Rust does not permit. That is, `bsn!` generates code such as
```rust
let __value = _scene
.get_or_insert_template::<
::Template // needs to be `Foo as FromTemplate`
>(_context);
__value.v.0 = 42; // needs to be `__value.v = Variant(42)`
```

For generic types, `bsn!` yields code like `` but Rust does not permit such a cast to elide generics.

Unfortunately a proper fix for these is fundamentally impossible due to current limitations in Rust itself. `bsn!`'s implementation would need access to semantic information of code surrounding the invocation; i.e. whether a symbol is an enum variant and the variant's enclosing type, or if a given type has generics. Sadly there is no mechanism for proc macros to receive such information.

## Bandaid solutions
* maintain lists of symbols that if used incorrectly issue an error and suggestion to fix, e.g. `Some(x)` suggests `Option::Some(x)`, `DespawnOnExit` suggests `DespawnOnExit::<_>`
* pro: best possible learning experience, most users will first hit this on stdlib/Bevy types, error message can give rationale and link to this issue etc
* con: breaks on user/third party types which use the same names
* maintain lists of symbols which need special casing; e.g. `Some`/`Ok`/`Err`, `DespawnOnExit`/`DespawnOnEnter`
* pro: ergonomic win
* pro: allows use of voldemort types (those that cannot be named, e.g. the concrete type of closures)
* con: poor learning experience, issue will only ever come up with user/third party types and will require research to understand core issue

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.