feat(ladfile_builder): First look into a native Luau (.d.luau) LAD backend#546
feat(ladfile_builder): First look into a native Luau (.d.luau) LAD backend#546jonasdedden wants to merge 1 commit into
Conversation
bevy_mod_scripting can dump its reflection registry to a LAD file and ships post-processors for the Lua Language Server (`--- @class` .lua) and mdbook, but no native Luau one — and luau-lsp cannot consume the LuaLS dialect. Luau users (the `luau` runtime feature already exists) therefore have no way to type-check their scripts from the registry. Add that backend as a feature-gated module in ladfile_builder: - `luau::lad_to_luau(&LadFile, &LuauBackendConfig) -> String`: the whole backend as a pure conversion, emitting `declare class … end` / `declare name: T`. - `luau::LuauLadPlugin`: a `LadFilePlugin` processor, wired into `default_processors()` behind the new `luau_files` feature. It needs no new dependencies (pure string generation over the `ladfile` types), which is why it's a module here rather than a separate backend crate like the LuaLS one. Handles Luau grammar edges: reserved words can't be bare identifiers (quoted `["end"]` for fields, suffixed elsewhere), the unit type is `()` only in a return arrow (`nil` otherwise), static accessor globals are excluded from real instance handles, and unknown types resolve to `any`. `focus_crates` scopes which crates' types get full classes so the output stays readable. An opt-in `HandleBranding` config emits a `Reg<T>` phantom brand: it rewrites the component getter to `get_component: <T>(…, reg: Reg<T>) -> T?` and brands each host-registered `<Component><suffix>` registration global as `Reg<Component>`, giving cast-free, statically-typed component access. Off by default since it assumes a host naming convention. Tested against `ladfile::EXAMPLE_LADFILE` (general surface, brand gating) and a small crafted fixture (full brand path); generated output verified to load and type-check under luau-lsp. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Changed Files
|
makspll
left a comment
There was a problem hiding this comment.
Hi, I had a look and I can see at least a couple problems:
- the whole implementation lives in
ladfile_builder, that crate serves to purely create ladfiles not provide language specific implementations, the whole implementation should live in its own crate - Some conventions here are somewhat opinionated and require non-default bindings (branding for example), generally in LAD backends this is not the case and type code only describes what already exists in BMS
- Escaped identifiers (with underscore suffix) don't have support in the LUA backend in BMS, so even though you type them this way they won't have anything backing them.
- Hardcoding types like
WorldandEntity, these do also live in ladfiles so backends should be able to deal with them fully dynamically with some minor exceptions (for example ladfiles store 'is_core' information which is also user customizable, which drives type priority in generated documentation for example)
Overall especially since I am not familiar with luau too much, I'd suggest the following:
- Since new LAD backends don't require to live in the BMS crate, this can exist in its own separate crate fully
- Once it has gained traction and many BMS consumers are using it, I would consider then merging the crate into the BMS project after all the kinks are worked out
🔍 Binding Differences Detected
b/crates/bindings/bevy_asset_bms_bindings/src/lib.rsindex 776c17c..312c549 100644
--- a/crates/bindings/bevy_asset_bms_bindings/src/lib.rs
+++ b/crates/bindings/bevy_asset_bms_bindings/src/lib.rs
@@ -334,7 +334,7 @@ pub(crate) fn register_render_asset_usages_functions(world: &mut World) {
};
output
},
- " The bitwise negation (`!`) of the bits in a flags value, truncating the result.",
+ " The bitwise negation (`!`) of the bits in `self`, truncating the result.",
&["_self"],
)
.register_documented(
@@ -355,7 +355,7 @@ pub(crate) fn register_render_asset_usages_functions(world: &mut World) {
};
output
},
- " Whether all set bits in a source flags value are also set in a target flags value.",
+ " Whether all set bits in `other` are also set in `self`.",
&["_self", "other"],
)
.register_documented(
@@ -376,7 +376,7 @@ pub(crate) fn register_render_asset_usages_functions(world: &mut World) {
};
output
},
- " The intersection of a source flags value with the complement of a target flags\n value (`&!`).\n This method is not equivalent to `self & !other` when `other` has unknown bits set.\n `difference` won't truncate `other`, but the `!` operator will.",
+ " The intersection of `self` with the complement of `other` (`&!`).\n This method is not equivalent to `self & !other` when `other` has unknown bits set.\n `difference` won't truncate `other`, but the `!` operator will.",
&["_self", "other"],
)
.register_documented(
@@ -466,7 +466,7 @@ pub(crate) fn register_render_asset_usages_functions(world: &mut World) {
};
output
},
- " The bitwise or (`|`) of the bits in two flags values.",
+ " The bitwise or (`|`) of the bits in `self` and `other`.",
&["_self", "other"],
)
.register_documented(
@@ -487,7 +487,7 @@ pub(crate) fn register_render_asset_usages_functions(world: &mut World) {
};
output
},
- " The bitwise and (`&`) of the bits in two flags values.",
+ " The bitwise and (`&`) of the bits in `self` and `other`.",
&["_self", "other"],
)
.register_documented(
@@ -508,7 +508,7 @@ pub(crate) fn register_render_asset_usages_functions(world: &mut World) {
};
output
},
- " Whether any set bits in a source flags value are also set in a target flags value.",
+ " Whether any set bits in `other` are also set in `self`.",
&["_self", "other"],
)
.register_documented(
@@ -542,7 +542,7 @@ pub(crate) fn register_render_asset_usages_functions(world: &mut World) {
};
output
},
- " Whether all bits in this flags value are unset.",
+ " Whether all bits in `self` are unset.",
&["_self"],
)
.register_documented(
@@ -563,7 +563,7 @@ pub(crate) fn register_render_asset_usages_functions(world: &mut World) {
};
output
},
- " The intersection of a source flags value with the complement of a target flags\n value (`&!`).\n This method is not equivalent to `self & !other` when `other` has unknown bits set.\n `remove` won't truncate `other`, but the `!` operator will.",
+ " The intersection of `self` with the complement of `other` (`&!`).\n This method is not equivalent to `self & !other` when `other` has unknown bits set.\n `remove` won't truncate `other`, but the `!` operator will.",
&["_self", "other"],
)
.register_documented(
@@ -606,7 +606,7 @@ pub(crate) fn register_render_asset_usages_functions(world: &mut World) {
};
output
},
- " The intersection of a source flags value with the complement of a target flags value (`&!`).\n This method is not equivalent to `self & !other` when `other` has unknown bits set.\n `difference` won't truncate `other`, but the `!` operator will.",
+ " The intersection of `self` with the complement of `other` (`&!`).\n This method is not equivalent to `self & !other` when `other` has unknown bits set.\n `difference` won't truncate `other`, but the `!` operator will.",
&["_self", "other"],
)
.register_documented(
@@ -627,7 +627,7 @@ pub(crate) fn register_render_asset_usages_functions(world: &mut World) {
};
output
},
- " The bitwise exclusive-or (`^`) of the bits in two flags values.",
+ " The bitwise exclusive-or (`^`) of the bits in `self` and `other`.",
&["_self", "other"],
)
.register_documented(
@@ -648,7 +648,7 @@ pub(crate) fn register_render_asset_usages_functions(world: &mut World) {
};
output
},
- " The bitwise exclusive-or (`^`) of the bits in two flags values.",
+ " The bitwise exclusive-or (`^`) of the bits in `self` and `other`.",
&["_self", "other"],
)
.register_documented(
@@ -669,7 +669,7 @@ pub(crate) fn register_render_asset_usages_functions(world: &mut World) {
};
output
},
- " The bitwise or (`|`) of the bits in two flags values.",
+ " The bitwise or (`|`) of the bits in `self` and `other`.",
&["_self", "other"],
);
let registry = world.get_resource_or_init::();
|
|
| Branch | feat/luau-lad-backend |
| Testbed | linux-gha |
🐰 View full continuous benchmarking report in Bencher
⚠️ WARNING: Truncated view!The full continuous benchmarking report exceeds the maximum length allowed on this platform.
|
I was following this to see where it would go. I've been using Luau in my project and I am interested in creating LAD files for it. If you do create a new crate with the LAD backend please let me know, I'd be happy to give it a go and give feedback. If I have the energy I might also contribute. |
|
Thanks for your feedback! I'm happy to extract this into a separate crate that I can bring upstream on my own for now. :) The current status is just a reflection of the bare minimum vibecoded status that I'm using right now locally to enable it in general, happy to clean it up based on the remarks you gave. I really want to prevent name collisions and unwarranted connections to your project: would you be fine with the name |
This is a first proposal for generating Luau type definition files (corresponding issue: #545). Please let me know whether you like the general approach or not.
Tests
src/lib.rscarries module tests that parse a real registry dump (src/test_assets/bindings.lad.json) and assert both modes:converts_general_surface— classes/fields/world/host globals present, and noReg<T>leakage in the default output.converts_branded_surface—Reg<T>, the genericget_component, and the…Typehandle branding all present when opted in.sanitizes_and_escapes— reserved-word handling.Plus a well-formedness sweep asserting no Luau keyword is emitted as a bare field/method/param name.
cargo testin this folder runs them againstladfile.Luau grammar edges handled
["end"]for fields, suffixed (end_) for methods/params/type names;()only in a return arrow — rendered asnilelsewhere;is_staticseparates those from real instance handles likeworld;any(Luau treats it permissively), kept focused viafocus_crates.