From 882f95afef3e0c7b0255352fa14168569fd03fd8 Mon Sep 17 00:00:00 2001 From: Lukasz Anforowicz Date: Mon, 21 Jul 2025 17:41:30 +0000 Subject: [PATCH 1/5] Deduplicate destructuring `syntax::file::Module` into `parse_items`. `syntax::parse::parse_items` is currently called from two places: from `macro/src/expand.rs` and from `gen/src/mod.rs`. (In the future it may be also called from a `syntax/test_support.rs` helper.) Before this commit, all those places had to destructure/interpret a `syntax::file::Module` into `content`, `namespace`, and `trusted`. After this commit, this destructuring/interpretation is deduplicated into `syntax::parse::parse_items`. This requires some minor gymnastics: * `gen/src/mod.rs` has to call `std::mem::take(&mut bridge.attrs)` instead of doing a partial destructuring and passing `bridge.attrs` directly. This is an extra complexity, but we already do the same thing in `macro/src/expand.rs` before this commit, so hopefully this is ok. * `fn parse_items` takes `&mut Module` rather than `Module` and "consumes" / `std::mem::take`s `module.content`, because `macro/src/expand.rs` needs to retain ownership of other `Module` fields. This also seems like an unfortunate extra complexity, but (again) before this commit we already do this for `bridge.attrs` in `macro/src/expand.rs`. --- gen/src/mod.rs | 13 +++---------- macro/src/expand.rs | 5 +---- syntax/parse.rs | 13 ++++++------- 3 files changed, 10 insertions(+), 21 deletions(-) diff --git a/gen/src/mod.rs b/gen/src/mod.rs index c75541ff9..5ca5b3c83 100644 --- a/gen/src/mod.rs +++ b/gen/src/mod.rs @@ -148,11 +148,11 @@ pub(super) fn generate(syntax: File, opt: &Opt) -> Result { let ref mut apis = Vec::new(); let ref mut errors = Errors::new(); let ref mut cfg_errors = Set::new(); - for bridge in syntax.modules { + for mut bridge in syntax.modules { let mut cfg = CfgExpr::Unconditional; attrs::parse( errors, - bridge.attrs, + std::mem::take(&mut bridge.attrs), attrs::Parser { cfg: Some(&mut cfg), ignore_unrecognized: true, @@ -160,14 +160,7 @@ pub(super) fn generate(syntax: File, opt: &Opt) -> Result { }, ); if cfg::eval(errors, cfg_errors, opt.cfg_evaluator.as_ref(), &cfg) { - let ref namespace = bridge.namespace; - let trusted = bridge.unsafety.is_some(); - apis.extend(syntax::parse_items( - errors, - bridge.content, - trusted, - namespace, - )); + apis.extend(syntax::parse_items(errors, &mut bridge)); } } diff --git a/macro/src/expand.rs b/macro/src/expand.rs index 712d0bf3e..2f21ce980 100644 --- a/macro/src/expand.rs +++ b/macro/src/expand.rs @@ -32,10 +32,7 @@ pub(crate) fn bridge(mut ffi: Module) -> Result { }, ); - let content = mem::take(&mut ffi.content); - let trusted = ffi.unsafety.is_some(); - let namespace = &ffi.namespace; - let ref mut apis = syntax::parse_items(errors, content, trusted, namespace); + let ref mut apis = syntax::parse_items(errors, &mut ffi); #[cfg(feature = "experimental-enum-variants-from-header")] crate::load::load(errors, apis); let ref types = Types::collect(errors, apis); diff --git a/syntax/parse.rs b/syntax/parse.rs index 4810ceefd..93fb1f26f 100644 --- a/syntax/parse.rs +++ b/syntax/parse.rs @@ -1,7 +1,7 @@ use crate::syntax::attrs::OtherAttrs; use crate::syntax::cfg::CfgExpr; use crate::syntax::discriminant::DiscriminantSet; -use crate::syntax::file::{Item, ItemForeignMod}; +use crate::syntax::file::{Item, ItemForeignMod, Module}; use crate::syntax::report::Errors; use crate::syntax::Atom::*; use crate::syntax::{ @@ -27,12 +27,11 @@ pub(crate) mod kw { syn::custom_keyword!(Result); } -pub(crate) fn parse_items( - cx: &mut Errors, - items: Vec, - trusted: bool, - namespace: &Namespace, -) -> Vec { +pub(crate) fn parse_items(cx: &mut Errors, bridge: &mut Module) -> Vec { + let items = mem::take(&mut bridge.content); + let ref namespace = bridge.namespace; + let trusted = bridge.unsafety.is_some(); + let mut apis = Vec::new(); for item in items { match item { From adf652922a1c01ae3b294f279415e86bc702ae35 Mon Sep 17 00:00:00 2001 From: Lukasz Anforowicz Date: Mon, 21 Jul 2025 17:24:45 +0000 Subject: [PATCH 2/5] Add minimal test for parsing of type aliases in `extern "C++"` section. --- syntax/mod.rs | 2 ++ syntax/test_support.rs | 32 ++++++++++++++++++++++++++++++++ syntax/types.rs | 21 +++++++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 syntax/test_support.rs diff --git a/syntax/mod.rs b/syntax/mod.rs index 2e6ab10dd..59dcd5fb5 100644 --- a/syntax/mod.rs +++ b/syntax/mod.rs @@ -25,6 +25,8 @@ pub(crate) mod report; pub(crate) mod resolve; pub(crate) mod set; pub(crate) mod symbol; +#[cfg(test)] +pub(crate) mod test_support; mod tokens; mod toposort; pub(crate) mod trivial; diff --git a/syntax/test_support.rs b/syntax/test_support.rs new file mode 100644 index 000000000..fcd5c14a9 --- /dev/null +++ b/syntax/test_support.rs @@ -0,0 +1,32 @@ +//! Test helpers for `syntax`-module-level parsing and checking of `#[cxx::bridge]`. + +use crate::syntax::check::{self}; +use crate::syntax::file::Module; +use crate::syntax::parse::parse_items; +use crate::syntax::report::Errors; +use crate::syntax::{Api, Types}; + +use proc_macro2::TokenStream; + +/// Parses a `TokenStream` containing `#[cxx::bridge] mod { ... }`. +pub fn parse_apis(cxx_bridge: TokenStream) -> syn::Result> { + let mut module = syn::parse2::(cxx_bridge)?; + let mut errors = Errors::new(); + let apis = parse_items(&mut errors, &mut module); + errors.propagate()?; + + Ok(apis) +} + +/// Collects and type-checks types used in `apis`. +pub fn collect_types(apis: &[Api]) -> syn::Result { + let mut errors = Errors::new(); + let types = Types::collect(&mut errors, apis); + errors.propagate()?; + + let generator = check::Generator::Build; + check::typecheck(&mut errors, apis, &types, generator); + errors.propagate()?; + + Ok(types) +} diff --git a/syntax/types.rs b/syntax/types.rs index 0c8b1323e..14011eecb 100644 --- a/syntax/types.rs +++ b/syntax/types.rs @@ -289,3 +289,24 @@ fn duplicate_name(cx: &mut Errors, sp: impl ToTokens, ident: &Ident) { let msg = format!("the name `{}` is defined multiple times", ident); cx.error(sp, msg); } + +#[cfg(test)] +mod test { + use crate::syntax::test_support::{collect_types, parse_apis}; + use quote::{format_ident, quote}; + + #[test] + fn test_parsing_of_cxx_type_aliases() { + let apis = parse_apis(quote! { + #[cxx::bridge] + mod ffi { + extern "C++" { + type Alias = crate::another_module::Foo; + } + } + }) + .unwrap(); + let types = collect_types(&apis).unwrap(); + assert!(types.aliases.contains_key(&format_ident!("Alias"))); + } +} From 0cc36b5b7741b0fee20287c966fed2a8bb9516e8 Mon Sep 17 00:00:00 2001 From: Lukasz Anforowicz Date: Thu, 24 Jul 2025 19:46:50 +0000 Subject: [PATCH 3/5] Fix how `test_support.rs` parses namespace from `#[cxx::bridge]`. Before this commit `fn parse_apis` in `syntax/test_support.rs` would ignore the namespace in `#[cxx::bridge(namespace = "ignored")]`. In other words, the new test added in `syntax/namespace.rs` would fail before this commit. This commit fixes this. At a high-level this commit: * Moves two pieces of code from `gen/src/file.rs` into `syntax/...` to make them reusable from `syntax/test_support.rs`: - `fn find_cxx_bridge_attr` (moved into `syntax/attrs.rs`) - `Namespace::parse_attr` (moved into `syntax/namespace.rs`) * Reuses these pieces of code from `syntax/test_support.rs` * Renames `Namespace::parse_bridge_attr_namespace` to `Namespace::parse_stream` so that all 3 parse methods are named after their input: `parse_attr`, `parse_stream`, `parse_meta`. * Adds a `syntax/`-level unit test that verifies that the namespace is indeed getting correctly parsed and propagated --- gen/src/file.rs | 57 ++++++++++++++++-------------------------- macro/src/lib.rs | 2 +- syntax/attrs.rs | 12 +++++++++ syntax/namespace.rs | 41 ++++++++++++++++++++++++++++-- syntax/test_support.rs | 6 +++++ 5 files changed, 80 insertions(+), 38 deletions(-) diff --git a/gen/src/file.rs b/gen/src/file.rs index c1ed30480..818c62fcb 100644 --- a/gen/src/file.rs +++ b/gen/src/file.rs @@ -1,8 +1,9 @@ +use crate::syntax::attrs::find_cxx_bridge_attr; use crate::syntax::file::Module; use crate::syntax::namespace::Namespace; use syn::parse::discouraged::Speculative; use syn::parse::{Error, Parse, ParseStream, Result}; -use syn::{braced, Attribute, Ident, Item, Meta, Token, Visibility}; +use syn::{braced, Attribute, Ident, Item, Token, Visibility}; pub(crate) struct File { pub modules: Vec, @@ -20,55 +21,41 @@ fn parse(input: ParseStream, modules: &mut Vec) -> Result<()> { input.call(Attribute::parse_inner)?; while !input.is_empty() { - let mut cxx_bridge = false; - let mut namespace = Namespace::ROOT; let mut attrs = input.call(Attribute::parse_outer)?; - for attr in &attrs { - let path = &attr.path().segments; - if path.len() == 2 && path[0].ident == "cxx" && path[1].ident == "bridge" { - cxx_bridge = true; - namespace = parse_args(attr)?; - break; - } - } + let cxx_bridge_attr = find_cxx_bridge_attr(&attrs); let ahead = input.fork(); ahead.parse::()?; ahead.parse::>()?; if !ahead.peek(Token![mod]) { let item: Item = input.parse()?; - if cxx_bridge { + if cxx_bridge_attr.is_some() { return Err(Error::new_spanned(item, "expected a module")); } continue; } - if cxx_bridge { - let mut module: Module = input.parse()?; - module.namespace = namespace; - attrs.extend(module.attrs); - module.attrs = attrs; - modules.push(module); - } else { - input.advance_to(&ahead); - input.parse::()?; - input.parse::()?; - let semi: Option = input.parse()?; - if semi.is_none() { - let content; - braced!(content in input); - parse(&content, modules)?; + match cxx_bridge_attr { + Some(cxx_bridge_attr) => { + let mut module: Module = input.parse()?; + module.namespace = Namespace::parse_attr(cxx_bridge_attr)?; + attrs.extend(module.attrs); + module.attrs = attrs; + modules.push(module); + } + None => { + input.advance_to(&ahead); + input.parse::()?; + input.parse::()?; + let semi: Option = input.parse()?; + if semi.is_none() { + let content; + braced!(content in input); + parse(&content, modules)?; + } } } } Ok(()) } - -fn parse_args(attr: &Attribute) -> Result { - if let Meta::Path(_) = attr.meta { - Ok(Namespace::ROOT) - } else { - attr.parse_args_with(Namespace::parse_bridge_attr_namespace) - } -} diff --git a/macro/src/lib.rs b/macro/src/lib.rs index f5dfd5126..24b7e19e5 100644 --- a/macro/src/lib.rs +++ b/macro/src/lib.rs @@ -62,7 +62,7 @@ use syn::parse_macro_input; pub fn bridge(args: TokenStream, input: TokenStream) -> TokenStream { let _ = syntax::error::ERRORS; - let namespace = match Namespace::parse_bridge_attr_namespace.parse(args) { + let namespace = match Namespace::parse_stream.parse(args) { Ok(namespace) => namespace, Err(err) => return err.to_compile_error().into(), }; diff --git a/syntax/attrs.rs b/syntax/attrs.rs index 894b82b83..f9ba09d06 100644 --- a/syntax/attrs.rs +++ b/syntax/attrs.rs @@ -310,3 +310,15 @@ impl ToTokens for OtherAttrs { } } } + +/// This finds `#[cxx::bridge]` attribute if it has been spelled in exactly this way. This means +/// that it is appropriate to use from `gen/...` and tests, but not from `macro/...` (it seems +/// somewhat prudent for the latter to handle the case where the attribute was imported under a +/// different name). +#[allow(dead_code)] // Only used from tests and cxx-build, but not from cxxbridge-macro +pub(crate) fn find_cxx_bridge_attr(attrs: &[Attribute]) -> Option<&Attribute> { + attrs.iter().find(|attr| { + let path = &attr.path().segments; + path.len() == 2 && path[0].ident == "cxx" && path[1].ident == "bridge" + }) +} diff --git a/syntax/namespace.rs b/syntax/namespace.rs index 6a23104f6..b472f1376 100644 --- a/syntax/namespace.rs +++ b/syntax/namespace.rs @@ -3,7 +3,7 @@ use quote::IdentFragment; use std::fmt::{self, Display}; use std::slice::Iter; use syn::parse::{Error, Parse, ParseStream, Result}; -use syn::{Expr, Ident, Lit, Meta, Token}; +use syn::{Attribute, Expr, Ident, Lit, Meta, Token}; mod kw { syn::custom_keyword!(namespace); @@ -23,7 +23,20 @@ impl Namespace { self.segments.iter() } - pub(crate) fn parse_bridge_attr_namespace(input: ParseStream) -> Result { + /// Parses `namespace = ...` (if present) from `attr`. + /// Typically `attr` represents `#[cxx::bridge(...)]` attribute. + #[allow(dead_code)] // Only used from tests and cxx-build, but not from cxxbridge-macro + pub(crate) fn parse_attr(attr: &Attribute) -> Result { + if let Meta::Path(_) = attr.meta { + Ok(Namespace::ROOT) + } else { + attr.parse_args_with(Namespace::parse_stream) + } + } + + /// Parses `namespace = ...` (if present) from `input`. + /// Typically `inputs` represents the "body" of a `#[cxx::bridge(...)]` attribute. + pub(crate) fn parse_stream(input: ParseStream) -> Result { if input.is_empty() { return Ok(Namespace::ROOT); } @@ -113,3 +126,27 @@ impl<'a> FromIterator<&'a Ident> for Namespace { Namespace { segments } } } + +#[cfg(test)] +mod test { + use crate::syntax::test_support::parse_apis; + use crate::syntax::Api; + use quote::quote; + + #[test] + fn test_top_level_namespace() { + let apis = parse_apis(quote! { + #[cxx::bridge(namespace = "top_level_namespace")] + mod ffi { + unsafe extern "C++" { + fn foo(); + } + } + }) + .unwrap(); + let [Api::CxxFunction(f)] = &apis[..] else { + panic!("Got unexpected apis"); + }; + assert_eq!("top_level_namespace$foo", f.name.to_symbol().to_string()); + } +} diff --git a/syntax/test_support.rs b/syntax/test_support.rs index fcd5c14a9..957026c8f 100644 --- a/syntax/test_support.rs +++ b/syntax/test_support.rs @@ -1,7 +1,9 @@ //! Test helpers for `syntax`-module-level parsing and checking of `#[cxx::bridge]`. +use crate::syntax::attrs::find_cxx_bridge_attr; use crate::syntax::check::{self}; use crate::syntax::file::Module; +use crate::syntax::namespace::Namespace; use crate::syntax::parse::parse_items; use crate::syntax::report::Errors; use crate::syntax::{Api, Types}; @@ -11,6 +13,10 @@ use proc_macro2::TokenStream; /// Parses a `TokenStream` containing `#[cxx::bridge] mod { ... }`. pub fn parse_apis(cxx_bridge: TokenStream) -> syn::Result> { let mut module = syn::parse2::(cxx_bridge)?; + let cxx_bridge_attr = find_cxx_bridge_attr(&module.attrs) + .expect("test inputs are expected to always have `#[cxx::bridge]` attribute"); + module.namespace = Namespace::parse_attr(cxx_bridge_attr)?; + let mut errors = Errors::new(); let apis = parse_items(&mut errors, &mut module); errors.propagate()?; From 1686225456ca77894ca331e4cb67c63e5561d85b Mon Sep 17 00:00:00 2001 From: Lukasz Anforowicz Date: Mon, 21 Jul 2025 16:51:58 +0000 Subject: [PATCH 4/5] Rename `aliases` field in `Types` struct to `cxx_aliases`. --- syntax/check.rs | 11 ++++++----- syntax/improper.rs | 2 +- syntax/types.rs | 15 ++++++++------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/syntax/check.rs b/syntax/check.rs index 01de638d2..d94bfcc92 100644 --- a/syntax/check.rs +++ b/syntax/check.rs @@ -95,7 +95,7 @@ fn check_type_ident(cx: &mut Check, name: &NamedType) { fn check_type_box(cx: &mut Check, ptr: &Ty1) { if let Type::Ident(ident) = &ptr.inner { if cx.types.cxx.contains(&ident.rust) - && !cx.types.aliases.contains_key(&ident.rust) + && !cx.types.cxx_aliases.contains_key(&ident.rust) && !cx.types.structs.contains_key(&ident.rust) && !cx.types.enums.contains_key(&ident.rust) { @@ -114,7 +114,7 @@ fn check_type_rust_vec(cx: &mut Check, ty: &Ty1) { match &ty.inner { Type::Ident(ident) => { if cx.types.cxx.contains(&ident.rust) - && !cx.types.aliases.contains_key(&ident.rust) + && !cx.types.cxx_aliases.contains_key(&ident.rust) && !cx.types.structs.contains_key(&ident.rust) && !cx.types.enums.contains_key(&ident.rust) { @@ -273,7 +273,8 @@ fn check_type_slice_ref(cx: &mut Check, ty: &SliceRef) { let supported = !is_unsized(cx, &ty.inner) || match &ty.inner { Type::Ident(ident) => { - cx.types.rust.contains(&ident.rust) || cx.types.aliases.contains_key(&ident.rust) + cx.types.rust.contains(&ident.rust) + || cx.types.cxx_aliases.contains_key(&ident.rust) } _ => false, }; @@ -664,7 +665,7 @@ fn is_opaque_cxx(cx: &mut Check, ty: &Ident) -> bool { cx.types.cxx.contains(ty) && !cx.types.structs.contains_key(ty) && !cx.types.enums.contains_key(ty) - && !(cx.types.aliases.contains_key(ty) && cx.types.required_trivial.contains_key(ty)) + && !(cx.types.cxx_aliases.contains_key(ty) && cx.types.required_trivial.contains_key(ty)) } fn span_for_struct_error(strct: &Struct) -> TokenStream { @@ -708,7 +709,7 @@ fn describe(cx: &mut Check, ty: &Type) -> String { "struct".to_owned() } else if cx.types.enums.contains_key(&ident.rust) { "enum".to_owned() - } else if cx.types.aliases.contains_key(&ident.rust) { + } else if cx.types.cxx_aliases.contains_key(&ident.rust) { "C++ type".to_owned() } else if cx.types.cxx.contains(&ident.rust) { "opaque C++ type".to_owned() diff --git a/syntax/improper.rs b/syntax/improper.rs index a19f5b7d6..80978ceeb 100644 --- a/syntax/improper.rs +++ b/syntax/improper.rs @@ -19,7 +19,7 @@ impl<'a> Types<'a> { } else if let Some(strct) = self.structs.get(ident) { Depends(&strct.name.rust) // iterate to fixed-point } else { - Definite(self.rust.contains(ident) || self.aliases.contains_key(ident)) + Definite(self.rust.contains(ident) || self.cxx_aliases.contains_key(ident)) } } Type::RustBox(_) diff --git a/syntax/types.rs b/syntax/types.rs index 14011eecb..9959b04c3 100644 --- a/syntax/types.rs +++ b/syntax/types.rs @@ -18,7 +18,8 @@ pub(crate) struct Types<'a> { pub enums: UnorderedMap<&'a Ident, &'a Enum>, pub cxx: UnorderedSet<&'a Ident>, pub rust: UnorderedSet<&'a Ident>, - pub aliases: UnorderedMap<&'a Ident, &'a TypeAlias>, + /// Type aliases defined in the `extern "C++"` section of `#[cxx::bridge]`. + pub cxx_aliases: UnorderedMap<&'a Ident, &'a TypeAlias>, pub untrusted: UnorderedMap<&'a Ident, &'a ExternType>, pub required_trivial: UnorderedMap<&'a Ident, Vec>>, pub impls: OrderedMap, Option<&'a Impl>>, @@ -34,7 +35,7 @@ impl<'a> Types<'a> { let mut enums = UnorderedMap::new(); let mut cxx = UnorderedSet::new(); let mut rust = UnorderedSet::new(); - let mut aliases = UnorderedMap::new(); + let mut cxx_aliases = UnorderedMap::new(); let mut untrusted = UnorderedMap::new(); let mut impls = OrderedMap::new(); let mut resolutions = UnorderedMap::new(); @@ -158,7 +159,7 @@ impl<'a> Types<'a> { duplicate_name(cx, alias, ident); } cxx.insert(ident); - aliases.insert(ident, alias); + cxx_aliases.insert(ident, alias); add_resolution(&alias.name, &alias.generics); } Api::Impl(imp) => { @@ -181,7 +182,7 @@ impl<'a> Types<'a> { | ImplKey::SharedPtr(ident) | ImplKey::WeakPtr(ident) | ImplKey::CxxVector(ident) => { - Atom::from(ident.rust).is_none() && !aliases.contains_key(ident.rust) + Atom::from(ident.rust).is_none() && !cxx_aliases.contains_key(ident.rust) } }; if implicit_impl && !impls.contains_key(&impl_key) { @@ -202,7 +203,7 @@ impl<'a> Types<'a> { enums, cxx, rust, - aliases, + cxx_aliases, untrusted, required_trivial, impls, @@ -273,7 +274,7 @@ impl<'a> Types<'a> { pub(crate) fn is_maybe_trivial(&self, ty: &Ident) -> bool { self.structs.contains_key(ty) || self.enums.contains_key(ty) - || self.aliases.contains_key(ty) + || self.cxx_aliases.contains_key(ty) } } @@ -307,6 +308,6 @@ mod test { }) .unwrap(); let types = collect_types(&apis).unwrap(); - assert!(types.aliases.contains_key(&format_ident!("Alias"))); + assert!(types.cxx_aliases.contains_key(&format_ident!("Alias"))); } } From 8dbb7b28889375ec08c88d1cf04e8670217f83c5 Mon Sep 17 00:00:00 2001 From: Lukasz Anforowicz Date: Mon, 21 Jul 2025 16:56:26 +0000 Subject: [PATCH 5/5] Support for type aliases in `extern "Rust"` section. Fixes https://crbug.com/433254489 --- book/package-lock.json | 212 ++++++++++++++------------------ book/src/extern-rust.md | 43 +++++++ macro/src/expand.rs | 39 +++--- src/lib.rs | 2 +- src/rust_type.rs | 3 + syntax/check.rs | 4 +- syntax/mod.rs | 1 + syntax/parse.rs | 7 +- syntax/types.rs | 38 +++++- tests/ffi/lib.rs | 13 ++ tests/ffi/module.rs | 21 ++++ tests/ffi/tests.cc | 3 + tests/ui/type_alias_rust.rs | 9 -- tests/ui/type_alias_rust.stderr | 5 - 14 files changed, 242 insertions(+), 158 deletions(-) delete mode 100644 tests/ui/type_alias_rust.rs delete mode 100644 tests/ui/type_alias_rust.stderr diff --git a/book/package-lock.json b/book/package-lock.json index a6af7c257..a5650411c 100644 --- a/book/package-lock.json +++ b/book/package-lock.json @@ -59,13 +59,12 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", - "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.5", + "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -73,12 +72,20 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/core": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", - "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" }, @@ -87,11 +94,10 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", - "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, - "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -110,47 +116,34 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@eslint/js": { - "version": "9.19.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.19.0.tgz", - "integrity": "sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==", + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz", + "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==", "dev": true, - "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, "node_modules/@eslint/object-schema": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", - "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", - "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz", + "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.10.0", + "@eslint/core": "^0.15.1", "levn": "^0.4.1" }, "engines": { @@ -210,11 +203,10 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", - "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, - "license": "Apache-2.0", "engines": { "node": ">=18.18" }, @@ -234,15 +226,13 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, - "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -255,7 +245,6 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, - "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -265,7 +254,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -297,15 +285,13 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" + "dev": true }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/boolbase": { "version": "1.0.0", @@ -314,11 +300,10 @@ "license": "ISC" }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -329,7 +314,6 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } @@ -417,8 +401,7 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/cross-spawn": { "version": "7.0.6", @@ -464,11 +447,10 @@ } }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dev": true, - "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -582,22 +564,22 @@ } }, "node_modules/eslint": { - "version": "9.19.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.19.0.tgz", - "integrity": "sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA==", + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz", + "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.0", - "@eslint/core": "^0.10.0", - "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.19.0", - "@eslint/plugin-kit": "^0.2.5", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", + "@eslint/core": "^0.15.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.31.0", + "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.1", + "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", @@ -605,9 +587,9 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.2.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -642,11 +624,10 @@ } }, "node_modules/eslint-scope": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", - "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -659,11 +640,10 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -672,15 +652,14 @@ } }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -707,7 +686,6 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -739,15 +717,13 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/fast-levenshtein": { "version": "2.0.6", @@ -820,6 +796,18 @@ "node": ">=10.13.0" } }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -882,17 +870,15 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, - "license": "MIT", "engines": { "node": ">= 4" } }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, - "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -949,7 +935,6 @@ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, - "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -968,8 +953,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -1030,7 +1014,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -1042,8 +1025,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/natural-compare": { "version": "1.4.0", @@ -1119,7 +1101,6 @@ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, - "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -1199,7 +1180,6 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } @@ -1209,7 +1189,6 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, - "license": "MIT", "engines": { "node": ">=4" } @@ -1248,7 +1227,6 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" }, @@ -1283,10 +1261,9 @@ } }, "node_modules/undici": { - "version": "6.21.1", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz", - "integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==", - "license": "MIT", + "version": "6.21.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", + "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", "engines": { "node": ">=18.17" } @@ -1296,7 +1273,6 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } diff --git a/book/src/extern-rust.md b/book/src/extern-rust.md index 40f223759..7840e8f03 100644 --- a/book/src/extern-rust.md +++ b/book/src/extern-rust.md @@ -163,3 +163,46 @@ mod ffi { Bounds on a lifetime (like `<'a, 'b: 'a>`) are not currently supported. Nor are type parameters or where-clauses. + +## Reusing existing binding types + +Extern Rust types support a syntax for declaring that a C++ binding of the +given Rust type already exists outside of the current bridge module. This +avoids generating a separate binding which would clash with the first one +(e.g. one possible error is: "conflicting implementations of trait `RustType`"). + +The snippet below illustrates how to unify references to a Rust type +across bridges: + +```rs +// file1.rs +#[cxx::bridge(namespace = "example")] +pub mod ffi { + extern "Rust" { + type Demo; + + fn create_demo() -> Box; + } +} + +pub struct Demo; + +pub fn create_demo() -> Box { todo!() } +``` + +```rs +// file2.rs +#[cxx::bridge(namespace = "example")] +pub mod ffi { + extern "C++" { + include!("file1.rs.h"); + } + extern "Rust" { + type Demo = crate::file1::Demo; + + fn take_ref_demo(demo: &Demo); + } +} + +pub fn take_ref_demo(demo: &crate::file1::Demo) { todo!() } +``` diff --git a/macro/src/expand.rs b/macro/src/expand.rs index 2f21ce980..922ca3889 100644 --- a/macro/src/expand.rs +++ b/macro/src/expand.rs @@ -1270,26 +1270,37 @@ fn expand_type_alias(alias: &TypeAlias) -> TokenStream { fn expand_type_alias_verify(alias: &TypeAlias, types: &Types) -> TokenStream { let attrs = &alias.attrs; let ident = &alias.name.rust; - let type_id = type_id(&alias.name); let begin_span = alias.type_token.span; let end_span = alias.semi_token.span; - let begin = quote_spanned!(begin_span=> ::cxx::private::verify_extern_type::<); let end = quote_spanned!(end_span=> >); - let mut verify = quote! { - #attrs - const _: fn() = #begin #ident, #type_id #end; - }; + match alias.lang { + Lang::Cxx | Lang::CxxUnwind => { + let type_id = type_id(&alias.name); + let begin = quote_spanned!(begin_span=> ::cxx::private::verify_extern_type::<); + let mut verify = quote! { + #attrs + const _: fn() = #begin #ident, #type_id #end; + }; - if types.required_trivial.contains_key(&alias.name.rust) { - let begin = quote_spanned!(begin_span=> ::cxx::private::verify_extern_kind::<); - verify.extend(quote! { - #attrs - const _: fn() = #begin #ident, ::cxx::kind::Trivial #end; - }); - } + if types.required_trivial.contains_key(&alias.name.rust) { + let begin = quote_spanned!(begin_span=> ::cxx::private::verify_extern_kind::<); + verify.extend(quote! { + #attrs + const _: fn() = #begin #ident, ::cxx::kind::Trivial #end; + }); + } - verify + verify + } + Lang::Rust => { + let begin = quote_spanned!(begin_span=> ::cxx::private::verify_rust_type::<); + quote! { + #attrs + const _: fn() = #begin #ident #end; + } + } + } } fn type_id(name: &Pair) -> TokenStream { diff --git a/src/lib.rs b/src/lib.rs index 649b968a5..b697cdbf8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -503,7 +503,7 @@ pub mod private { pub use crate::rust_str::RustStr; #[cfg(feature = "alloc")] pub use crate::rust_string::RustString; - pub use crate::rust_type::{ImplBox, ImplVec, RustType}; + pub use crate::rust_type::{verify_rust_type, ImplBox, ImplVec, RustType}; #[cfg(feature = "alloc")] pub use crate::rust_vec::RustVec; pub use crate::shared_ptr::SharedPtrTarget; diff --git a/src/rust_type.rs b/src/rust_type.rs index eacb5309f..188522550 100644 --- a/src/rust_type.rs +++ b/src/rust_type.rs @@ -3,3 +3,6 @@ pub unsafe trait RustType {} pub unsafe trait ImplBox {} pub unsafe trait ImplVec {} + +#[doc(hidden)] +pub fn verify_rust_type() {} diff --git a/syntax/check.rs b/syntax/check.rs index d94bfcc92..c2650fc06 100644 --- a/syntax/check.rs +++ b/syntax/check.rs @@ -710,7 +710,9 @@ fn describe(cx: &mut Check, ty: &Type) -> String { } else if cx.types.enums.contains_key(&ident.rust) { "enum".to_owned() } else if cx.types.cxx_aliases.contains_key(&ident.rust) { - "C++ type".to_owned() + "aliased C++ type".to_owned() + } else if cx.types.rust_aliases.contains_key(&ident.rust) { + "aliased Rust type".to_owned() } else if cx.types.cxx.contains(&ident.rust) { "opaque C++ type".to_owned() } else if cx.types.rust.contains(&ident.rust) { diff --git a/syntax/mod.rs b/syntax/mod.rs index 59dcd5fb5..a47b8e081 100644 --- a/syntax/mod.rs +++ b/syntax/mod.rs @@ -170,6 +170,7 @@ pub(crate) struct ExternFn { pub(crate) struct TypeAlias { #[allow(dead_code)] // only used by cxx-build, not cxxbridge-macro pub cfg: CfgExpr, + pub lang: Lang, #[allow(dead_code)] // only used by cxxbridge-macro, not cxx-build pub doc: Doc, pub derives: Vec, diff --git a/syntax/parse.rs b/syntax/parse.rs index 93fb1f26f..7984c132f 100644 --- a/syntax/parse.rs +++ b/syntax/parse.rs @@ -870,17 +870,12 @@ fn parse_type_alias( }, )); - if lang == Lang::Rust { - let span = quote!(#type_token #semi_token); - let msg = "type alias in extern \"Rust\" block is not supported"; - return Err(Error::new_spanned(span, msg)); - } - let visibility = visibility_pub(&visibility, type_token.span); let name = pair(namespace, &ident, cxx_name, rust_name); Ok(Api::TypeAlias(TypeAlias { cfg, + lang, doc, derives, attrs, diff --git a/syntax/types.rs b/syntax/types.rs index 9959b04c3..be91e0856 100644 --- a/syntax/types.rs +++ b/syntax/types.rs @@ -7,7 +7,8 @@ use crate::syntax::set::{OrderedSet, UnorderedSet}; use crate::syntax::trivial::{self, TrivialReason}; use crate::syntax::visit::{self, Visit}; use crate::syntax::{ - toposort, Api, Atom, Enum, EnumRepr, ExternType, Impl, Lifetimes, Pair, Struct, Type, TypeAlias, + toposort, Api, Atom, Enum, EnumRepr, ExternType, Impl, Lang, Lifetimes, Pair, Struct, Type, + TypeAlias, }; use proc_macro2::Ident; use quote::ToTokens; @@ -20,6 +21,8 @@ pub(crate) struct Types<'a> { pub rust: UnorderedSet<&'a Ident>, /// Type aliases defined in the `extern "C++"` section of `#[cxx::bridge]`. pub cxx_aliases: UnorderedMap<&'a Ident, &'a TypeAlias>, + /// Type aliases defined in the `extern "Rust"` section of `#[cxx::bridge]`. + pub rust_aliases: UnorderedMap<&'a Ident, &'a TypeAlias>, pub untrusted: UnorderedMap<&'a Ident, &'a ExternType>, pub required_trivial: UnorderedMap<&'a Ident, Vec>>, pub impls: OrderedMap, Option<&'a Impl>>, @@ -36,6 +39,7 @@ impl<'a> Types<'a> { let mut cxx = UnorderedSet::new(); let mut rust = UnorderedSet::new(); let mut cxx_aliases = UnorderedMap::new(); + let mut rust_aliases = UnorderedMap::new(); let mut untrusted = UnorderedMap::new(); let mut impls = OrderedMap::new(); let mut resolutions = UnorderedMap::new(); @@ -158,8 +162,16 @@ impl<'a> Types<'a> { if !type_names.insert(ident) { duplicate_name(cx, alias, ident); } - cxx.insert(ident); - cxx_aliases.insert(ident, alias); + match alias.lang { + Lang::Cxx | Lang::CxxUnwind => { + cxx.insert(ident); + cxx_aliases.insert(ident, alias); + } + Lang::Rust => { + rust.insert(ident); + rust_aliases.insert(ident, alias); + } + } add_resolution(&alias.name, &alias.generics); } Api::Impl(imp) => { @@ -182,7 +194,9 @@ impl<'a> Types<'a> { | ImplKey::SharedPtr(ident) | ImplKey::WeakPtr(ident) | ImplKey::CxxVector(ident) => { - Atom::from(ident.rust).is_none() && !cxx_aliases.contains_key(ident.rust) + Atom::from(ident.rust).is_none() + && !cxx_aliases.contains_key(ident.rust) + && !rust_aliases.contains_key(ident.rust) } }; if implicit_impl && !impls.contains_key(&impl_key) { @@ -204,6 +218,7 @@ impl<'a> Types<'a> { cxx, rust, cxx_aliases, + rust_aliases, untrusted, required_trivial, impls, @@ -310,4 +325,19 @@ mod test { let types = collect_types(&apis).unwrap(); assert!(types.cxx_aliases.contains_key(&format_ident!("Alias"))); } + + #[test] + fn test_parsing_of_rust_type_aliases() { + let apis = parse_apis(quote! { + #[cxx::bridge] + mod ffi { + extern "Rust" { + type Alias = crate::another_module::Foo; + } + } + }) + .unwrap(); + let types = collect_types(&apis).unwrap(); + assert!(types.rust_aliases.contains_key(&format_ident!("Alias"))); + } } diff --git a/tests/ffi/lib.rs b/tests/ffi/lib.rs index 11713f881..d7212a409 100644 --- a/tests/ffi/lib.rs +++ b/tests/ffi/lib.rs @@ -342,6 +342,13 @@ pub mod ffi { impl Box {} impl CxxVector {} + + extern "Rust" { + type CrossModuleRustType = crate::module::CrossModuleRustType; + fn r_get_value_from_cross_module_rust_type( + value: &CrossModuleRustType, + ) -> i32; + } } mod other { @@ -659,3 +666,9 @@ fn r_try_return_mutsliceu8(slice: &mut [u8]) -> Result<&mut [u8], Error> { fn r_aliased_function(x: i32) -> String { x.to_string() } + +fn r_get_value_from_cross_module_rust_type( + value: &crate::module::CrossModuleRustType, +) -> i32 { + value.0 +} diff --git a/tests/ffi/module.rs b/tests/ffi/module.rs index e298c0250..b58a2d6ec 100644 --- a/tests/ffi/module.rs +++ b/tests/ffi/module.rs @@ -78,3 +78,24 @@ pub mod ffi2 { impl UniquePtr {} impl UniquePtr {} } + +#[cxx::bridge(namespace = "tests")] +pub mod ffi3 { + extern "Rust" { + type CrossModuleRustType; + + #[allow(clippy::unnecessary_box_returns)] + fn r_boxed_cross_module_rust_type( + value: i32, + ) -> Box; + } +} + +pub struct CrossModuleRustType(pub i32); + +#[allow(clippy::unnecessary_box_returns)] +fn r_boxed_cross_module_rust_type( + value: i32, +) -> Box { + Box::new(CrossModuleRustType(value)) +} diff --git a/tests/ffi/tests.cc b/tests/ffi/tests.cc index 2ba67f87d..b9e18ca05 100644 --- a/tests/ffi/tests.cc +++ b/tests/ffi/tests.cc @@ -980,6 +980,9 @@ extern "C" const char *cxx_run_test() noexcept { (void)rust::Vec(); (void)rust::Vec(); + ASSERT(123 == r_get_value_from_cross_module_rust_type( + *r_boxed_cross_module_rust_type(123))); + cxx_test_suite_set_correct(); return nullptr; } diff --git a/tests/ui/type_alias_rust.rs b/tests/ui/type_alias_rust.rs deleted file mode 100644 index 67df48926..000000000 --- a/tests/ui/type_alias_rust.rs +++ /dev/null @@ -1,9 +0,0 @@ -#[cxx::bridge] -mod ffi { - extern "Rust" { - /// Incorrect. - type Alias = crate::Type; - } -} - -fn main() {} diff --git a/tests/ui/type_alias_rust.stderr b/tests/ui/type_alias_rust.stderr deleted file mode 100644 index 8cf9a56fb..000000000 --- a/tests/ui/type_alias_rust.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: type alias in extern "Rust" block is not supported - --> tests/ui/type_alias_rust.rs:5:9 - | -5 | type Alias = crate::Type; - | ^^^^^^^^^^^^^^^^^^^^^^^^^