Skip to content

Commit f945204

Browse files
authored
Allow multiple unique block-level attributes (#1686)
Fixes #1638 <!-- ELLIPSIS_HIDDEN --> ---- > [!IMPORTANT] > Allow multiple unique block-level attributes in BAML classes and update attribute processing logic and build configuration. > > - **Behavior**: > - Allow multiple unique block-level attributes in BAML classes in `attributes.baml`. > - Update error messages for duplicate attributes in `attributes.baml`. > - **Attribute Processing**: > - Modify `resolve_type_exp_block_attributes()` in `mod.rs` to process multiple block attributes. > - Update `visit_optional_single_attr()` in `context/mod.rs` to handle already processed attributes. > - **Build Configuration**: > - Adjust `BINDGEN_EXTRA_CLANG_ARGS` in `flake.nix` for non-Darwin systems to include additional paths. > > <sup>This description was created by </sup>[<img alt="Ellipsis" src="https://img.shields.io/badge/Ellipsis-blue?color=175173">](https://www.ellipsis.dev?ref=BoundaryML%2Fbaml&utm_source=github&utm_medium=referral)<sup> for 969a61e. It will automatically update as commits are pushed.</sup> <!-- ELLIPSIS_HIDDEN -->
1 parent 9c7800a commit f945204

File tree

4 files changed

+50
-9
lines changed

4 files changed

+50
-9
lines changed

engine/baml-lib/baml/tests/validation_files/class/attributes.baml

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,36 @@ class TestClassAlias {
77
key2 string @alias("key21")
88
key3 string @alias("key with space")
99
key4 string @alias("key.with.punctuation/123")
10-
}
10+
}
11+
12+
class HasTwoBlockAttributes {
13+
name string
14+
@@description("A test")
15+
@@alias("AKAHasTwo")
16+
}
17+
18+
class HasThreeBlockAttributes {
19+
name string
20+
@@description("A test")
21+
@@alias("AKAHasTwo")
22+
@@description("A second description")
23+
}
24+
25+
// error: Attribute "@description" can only be defined once.
26+
// --> class/attributes.baml:20
27+
// |
28+
// 19 | name string
29+
// 20 | @@description("A test")
30+
// |
31+
// error: Attribute "@description" can only be defined once.
32+
// --> class/attributes.baml:22
33+
// |
34+
// 21 | @@alias("AKAHasTwo")
35+
// 22 | @@description("A second description")
36+
// |
37+
// error: Attribute "@description" can only be defined once.
38+
// --> class/attributes.baml:20
39+
// |
40+
// 19 | name string
41+
// 20 | @@description("A test")
42+
// |

engine/baml-lib/parser-database/src/attributes/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,9 @@ fn resolve_type_exp_block_attributes<'db>(
162162
// Now validate the class attributes.
163163
ctx.assert_all_attributes_processed(type_id.into());
164164

165-
for _ in 0..ast_typexpr.attributes.len() {
166-
let attrs = to_string_attribute::visit(ctx, &span, true);
167-
class_attributes.extend_serializer(&attrs);
165+
// Process all attributes, including multiple block comments
166+
while let Some(attrs) = to_string_attribute::visit(ctx, &span, true) {
167+
class_attributes.extend_serializer(&Some(attrs));
168168
}
169169
ctx.validate_visited_attributes();
170170
ctx.types.class_attributes.insert(type_id, class_attributes);

engine/baml-lib/parser-database/src/context/mod.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,13 +151,18 @@ impl<'db> Context<'db> {
151151
attr.name.name(),
152152
attr.span.clone(),
153153
));
154-
assert!(self.attributes.unused_attributes.remove(&idx));
154+
if !self.attributes.unused_attributes.remove(&idx) {
155+
return false; // Attribute was already processed.
156+
}
155157
}
156158

157159
return false; // stop validation in this case
158160
}
159161

160-
assert!(self.attributes.unused_attributes.remove(&first_idx));
162+
// Only try to remove if it's still in the unused_attributes set
163+
if !self.attributes.unused_attributes.remove(&first_idx) {
164+
return false; // Attribute was already processed
165+
}
161166
drop(attrs);
162167
self.set_attribute(first_idx, first)
163168
}
@@ -196,7 +201,7 @@ impl<'db> Context<'db> {
196201
/// This must be called at the end of arguments validation. It will report errors for each argument that was not used by the validators. The Drop impl will helpfully panic
197202
/// otherwise.
198203
pub(crate) fn validate_visited_arguments(&mut self) {
199-
let attr = if let Some(attrid) = self.attributes.attribute {
204+
let attr: &Attribute = if let Some(attrid) = self.attributes.attribute {
200205
&self.ast[attrid]
201206
} else {
202207
panic!("State error: missing attribute in validate_visited_arguments.")

flake.nix

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,9 @@
112112
};
113113
LIBCLANG_PATH = pkgs.libclang.lib + "/lib/";
114114
BINDGEN_EXTRA_CLANG_ARGS = if pkgs.stdenv.isDarwin then
115-
"-I${pkgs.llvmPackages_19.libclang.lib}/lib/clang/19/headers "
115+
"" # Rely on default includes provided by stdenv.cc + libclang
116116
else
117-
"-isystem ${pkgs.llvmPackages_19.libclang.lib}/lib/clang/19/include -isystem ${pkgs.glibc.dev}/include";
117+
"-isystem ${pkgs.llvmPackages_19.libclang.lib}/lib/clang/19/include -isystem ${pkgs.llvmPackages_19.libclang.lib}/include -isystem ${pkgs.glibc.dev}/include";
118118

119119
cargoLock = { lockFile = ./engine/Cargo.lock; outputHashes = {
120120
"pyo3-asyncio-0.21.0" = "sha256-5ZLzWkxp3e2u0B4+/JJTwO9SYKhtmBpMBiyIsTCW5Zw=";
@@ -143,6 +143,10 @@
143143
inherit buildInputs;
144144
PATH="${clang}/bin:$PATH";
145145
LIBCLANG_PATH = pkgs.libclang.lib + "/lib/";
146+
BINDGEN_EXTRA_CLANG_ARGS = if pkgs.stdenv.isDarwin then
147+
"" # Rely on default includes provided by stdenv.cc + libclang
148+
else
149+
"-isystem ${pkgs.llvmPackages_19.libclang.lib}/lib/clang/19/include -isystem ${pkgs.llvmPackages_19.libclang.lib}/include -isystem ${pkgs.glibc.dev}/include";
146150
};
147151
}
148152
);

0 commit comments

Comments
 (0)