diff --git a/baml_language/Cargo.lock b/baml_language/Cargo.lock index 4059e0db921..924aa5e3dc8 100644 --- a/baml_language/Cargo.lock +++ b/baml_language/Cargo.lock @@ -1264,6 +1264,7 @@ name = "baml_type" version = "0.0.0-beta" dependencies = [ "baml_base", + "baml_compiler_lexer", "baml_type_macros", "bitflags 2.11.1", "borsh", diff --git a/baml_language/TYPE_SYSTEM.md b/baml_language/TYPE_SYSTEM.md index 52c1febae09..186d27c6e84 100644 --- a/baml_language/TYPE_SYSTEM.md +++ b/baml_language/TYPE_SYSTEM.md @@ -68,7 +68,13 @@ A declaration (class, enum, interface) and a type are different things. A declar A package has no intrinsic name. It may declare one (`[package].name`, or the fixed name of a standard-library package), but that is display metadata and the default a dependent spells it by, not its identity. Names live on dependency edges: a package reaches another under the name its own manifest gives that edge, so two packages may reach one package under different names, and a package can name only itself, the packages it lists, and the prelude (the implicitly available standard-library packages). Everything else is invisible to it. Today (09/2026) a user package declares no dependencies and reaches exactly the prelude. -Source always references declarations fully namespaced as `..` where `` is the name of a dependency graph edge (including `root` which is always the edge to its own package, similar to Rust's `crate`). `root` is reserved: no item, binding, type parameter, or namespace may be named `root`. The only exception is when referencing local items within the same namespace, which may be referenced by their item name (``) alone. An item of the writer's own namespace spelled like a package name shadows that package entirely: `.` reads the item, and the package is unreachable by that name from that namespace. +Source always references declarations fully namespaced as `..` where `` is the name of a dependency graph edge (including `root` which is always the edge to its own package, similar to Rust's `crate`). `root` is a keyword, so nothing can be named `root`. The only exception is when referencing local items within the same namespace, which may be referenced by their item name (``) alone. An item of the writer's own namespace spelled like a package name shadows that package entirely: `.` reads the item, and the package is unreachable by that name from that namespace. + +Three kinds of names are reserved: + +- **Keywords** (`class`, `function`, `if`, `let`, `const`, `is`, `true`, `false`, `null`, `self`, `root`, ...: every keyword token of the lexer) never name a declaration, binding, or parameter. After a `.` they name members and path segments, which only the standard library may declare (`t.implements(u)`, `baml.spawn`). +- **Built-in type names** (`int`, `string`, `map`, `json`, `image`, `never`, ...: every single-word spelling in the compiler alias table) cannot name an item, namespace, type parameter, or type binding, since a bare reference reads the built-in type. They stay legal as fields, methods, enum variants, parameters, and locals. +- **Weak keywords** (`client`, `test`, `testset`, `retry_policy`, `template_string`, `generator`, `type`, `as`, `with`) are ordinary identifiers except where they begin their own syntax. Every rendering of a type is from a viewpoint: the canonical rendering spells every package fully; the user-facing rendering elides the viewer's own package and writes `root.` for its other namespaces. Canonical order of union members and throw sets is deterministic within a program but is not a stable key across programs; nothing may persist it as an identity. diff --git a/baml_language/crates/baml_builtins2/baml_std/baml/ns_spawn/spawn.baml b/baml_language/crates/baml_builtins2/baml_std/baml/ns_spawn/spawn.baml index e2449d7527a..b05e06eec65 100644 --- a/baml_language/crates/baml_builtins2/baml_std/baml/ns_spawn/spawn.baml +++ b/baml_language/crates/baml_builtins2/baml_std/baml/ns_spawn/spawn.baml @@ -86,11 +86,11 @@ class Plan { /// Cancelling any of these tokens cancels the task. cancel_tokens: CancelToken[], /// Whether the task keeps running when its spawner is cancelled. - root: bool, + is_root: bool, /// A plan that runs `body`, with nothing added to it. function new(body: () -> T throws E, name: string? = null) -> Plan throws never { - Plan { body: body, name: name, limits: [], cancel_tokens: [], root: false } + Plan { body: body, name: name, limits: [], cancel_tokens: [], is_root: false } } /// Returns this plan with a new body: `wrapper`, called with the old one. @@ -112,7 +112,7 @@ class Plan { name: self.name, limits: self.limits, cancel_tokens: self.cancel_tokens, - root: self.root, + is_root: self.is_root, } } @@ -195,7 +195,7 @@ implements Modifier for Limit { implements Modifier for Root { function apply(self, plan: Plan) -> Plan throws never { - Plan { ...plan, root: true } + Plan { ...plan, is_root: true } } } diff --git a/baml_language/crates/baml_compiler2_ast/src/lib.rs b/baml_language/crates/baml_compiler2_ast/src/lib.rs index 4d33ef33f4f..ef5dd4278e8 100644 --- a/baml_language/crates/baml_compiler2_ast/src/lib.rs +++ b/baml_language/crates/baml_compiler2_ast/src/lib.rs @@ -1222,10 +1222,10 @@ class Response { } #[test] - fn ast_lowers_keyword_named_class_field() { + fn ast_lowers_weak_keyword_named_class_field() { let source = r#" -class InterfaceTwo { - interface string +class Session { + client string } "#; let items = parse_and_lower(source); @@ -1238,7 +1238,7 @@ class InterfaceTwo { .expect("expected ClassDef"); let field = class.fields.first().expect("expected field"); - assert_eq!(field.name.as_str(), "interface"); + assert_eq!(field.name.as_str(), "client"); assert_eq!(field.type_expr.to_string(), "string"); } @@ -1287,10 +1287,10 @@ function Foo(t: TypeValue) -> string { let Expr::Call { callee, .. } = &body.exprs[tail_expr] else { panic!("expected call expression"); }; - let Expr::MemberAccess { member, .. } = &body.exprs[*callee] else { - panic!("expected member access callee"); + let Expr::Path(path) = &body.exprs[*callee] else { + panic!("expected path callee"); }; - assert_eq!(member.as_str(), "implements"); + assert_eq!(path.to_string(), "t.implements"); } #[test] diff --git a/baml_language/crates/baml_compiler2_ast/src/lower_expr_body.rs b/baml_language/crates/baml_compiler2_ast/src/lower_expr_body.rs index 7a03c8a1515..084e34c3fb0 100644 --- a/baml_language/crates/baml_compiler2_ast/src/lower_expr_body.rs +++ b/baml_language/crates/baml_compiler2_ast/src/lower_expr_body.rs @@ -33,34 +33,14 @@ pub struct EnvVarRef { pub range: TextRange, } -/// Returns true if `kind` can serve as an identifier token in expression position. -/// -/// The parser allows `KW_CLIENT` (and `WORD`) inside `PATH_EXPR` / `FIELD_ACCESS_EXPR` -/// nodes when `client` is used as a variable or field name. It likewise allows -/// `KW_SPAWN` / `KW_AWAIT` as path segments (e.g. the `baml.spawn` namespace), -/// since they are unambiguous after a `.`, and the interface-related keywords -/// (`implements`, `interface`, `extends`) as member names — e.g. -/// `dog_t.implements(animal_t)` on the reflection `type` value. This must -/// match exactly what `parse_path_or_ident` / `at_member_name` accept in the -/// parser; adding a new keyword there requires adding it here too. +/// Returns true if `kind` can serve as an identifier or literal-word token in +/// expression position. The parser emits every name as a `WORD`, keywords +/// used as names included; the remaining kinds lower by text (the +/// literal/identifier arms below switch on it). pub(crate) fn is_ident_token(kind: SyntaxKind) -> bool { matches!( kind, SyntaxKind::WORD - | SyntaxKind::KW_CLIENT - | SyntaxKind::KW_SPAWN - | SyntaxKind::KW_AWAIT - | SyntaxKind::KW_CLASS - | SyntaxKind::KW_ENUM - | SyntaxKind::KW_FUNCTION - | SyntaxKind::KW_IMPLEMENTS - | SyntaxKind::KW_IMPLEMENT - | SyntaxKind::KW_INTERFACE - | SyntaxKind::KW_EXTENDS - | SyntaxKind::KW_REQUIRES - // Contextual keywords re-lexed from a `Word`: still lower by text - // (the literal/identifier arms below switch on the text), so they - // must read as ident tokens just as they did when they were `Word`. | SyntaxKind::KW_AS | SyntaxKind::KW_TYPE | SyntaxKind::KW_TRUE @@ -1933,10 +1913,6 @@ impl LoweringContext { SyntaxKind::STRING_LITERAL => Some(Expr::Literal(Literal::String( strip_string_delimiters(t.text()), ))), - // `spawn`/`await` pass `is_ident_token` (they're - // valid path SEGMENTS) but here they're the keywords - // themselves — never a name/with expression. - SyntaxKind::KW_SPAWN | SyntaxKind::KW_AWAIT => None, k if is_ident_token(k) => Some(match t.text() { "true" => Expr::Literal(Literal::Bool(true)), "false" => Expr::Literal(Literal::Bool(false)), @@ -2100,11 +2076,6 @@ impl LoweringContext { SyntaxKind::CARET => op = Some(BinaryOp::BitXor), SyntaxKind::LESS_LESS => op = Some(BinaryOp::Shl), SyntaxKind::GREATER_GREATER => op = Some(BinaryOp::Shr), - SyntaxKind::KW_INSTANCEOF => { - self.diags - .push(LoweringDiagnostic::InstanceofRemoved { span }); - return self.alloc_expr(Expr::Missing, node.span_range()); - } SyntaxKind::QUESTION_QUESTION => op = Some(BinaryOp::NullCoalesce), SyntaxKind::QUESTION if op.is_none() => { // Two consecutive QUESTION tokens = null coalescing (??) @@ -2896,15 +2867,6 @@ impl LoweringContext { .filter_map(rowan::NodeOrToken::into_token) .find(|t| t.kind() == SyntaxKind::WORD); - if let Some(token) = &name_token - && token.text() == "const" - { - self.diags - .push(LoweringDiagnostic::ReservedConstBindingName { - span: token.text_range(), - }); - } - let name_span = name_token.as_ref().map(rowan::SyntaxToken::text_range); let name = name_token.map(|t| Name::new(t.text())); @@ -3835,9 +3797,6 @@ impl LoweringContext { .iter() .rposition(|token| token.kind() == SyntaxKind::DOT) .and_then(|dot| tokens.get(dot + 1)) - // The full member-name set, not just `WORD`: an interface method - // may be named with a contextual keyword (`implements`, `extends`), - // and the parser accepts those here. .filter(|token| is_ident_token(token.kind())); let Some(member_token) = member_token else { return self.alloc_expr(Expr::Missing, span); diff --git a/baml_language/crates/baml_compiler2_ast/src/lowering_diagnostic.rs b/baml_language/crates/baml_compiler2_ast/src/lowering_diagnostic.rs index cfa91b1a3e9..dd0672fb66e 100644 --- a/baml_language/crates/baml_compiler2_ast/src/lowering_diagnostic.rs +++ b/baml_language/crates/baml_compiler2_ast/src/lowering_diagnostic.rs @@ -107,9 +107,6 @@ pub enum LoweringDiagnostic { /// A byte string literal contains an invalid escape sequence. InvalidByteStringEscape { message: String, span: TextRange }, - /// The `instanceof` operator was used; it has been removed. Use `match` instead. - InstanceofRemoved { span: TextRange }, - /// A backtick template has a malformed block tag — an unclosed, /// mismatched, or stray `${for}`/`${if}` open/close. MalformedTemplateBlock { @@ -141,10 +138,6 @@ pub enum LoweringDiagnostic { /// `const` currently parses as a non-immutable alias for `let`. ConstBindingIntroducer { span: TextRange }, - /// `const` is reserved as future language surface and cannot be used as a - /// binding name. - ReservedConstBindingName { span: TextRange }, - /// An assignment operator (`=`, `+=`, …) appeared in expression position, /// e.g. `(x = 5)`. Assignment is statement-only in BAML, so the expression /// has no value; without this diagnostic it would lower to a `Missing` that @@ -451,13 +444,6 @@ impl LoweringDiagnostic { *span, "invalid escape", ), - LoweringDiagnostic::InstanceofRemoved { span } => ( - DiagnosticId::RemovedFeature, - Severity::Error, - "`instanceof` is no longer supported. Use a `match` expression for type checking instead.".to_string(), - *span, - "use `match` instead", - ), LoweringDiagnostic::MalformedTemplateBlock { kind, span } => { use baml_compiler_syntax::BacktickStructuralErrorKind as K; let (message, label): (&str, &str) = match kind { @@ -552,13 +538,6 @@ impl LoweringDiagnostic { *span, "`const` behaves like `let` for now", ), - LoweringDiagnostic::ReservedConstBindingName { span } => ( - DiagnosticId::InvalidSyntax, - Severity::Error, - "`const` is reserved and cannot be used as a binding name".to_string(), - *span, - "`const` is reserved here", - ), LoweringDiagnostic::AssignmentInExpressionPosition { span } => ( DiagnosticId::InvalidSyntax, Severity::Error, diff --git a/baml_language/crates/baml_compiler2_hir/src/builder.rs b/baml_language/crates/baml_compiler2_hir/src/builder.rs index 9d67c5a786b..7e23dea5b37 100644 --- a/baml_language/crates/baml_compiler2_hir/src/builder.rs +++ b/baml_language/crates/baml_compiler2_hir/src/builder.rs @@ -11,12 +11,13 @@ use std::sync::Arc; use baml_base::{AttributePosition, Name, SourceFile}; use baml_compiler_diagnostics::{diagnostic::DiagnosticId, runtime_type::SerializedKeyContainer}; use baml_compiler2_ast::{self as ast, LoweringDiagnostic}; +use baml_type::reserved::ReservedName; use rustc_hash::{FxHashMap, FxHashSet}; use text_size::{TextRange, TextSize}; use crate::{ contributions::{Contribution, Definition, DefinitionKind, FileSymbolContributions}, - diagnostic::{Hir2Diagnostic, MemberSite}, + diagnostic::{Hir2Diagnostic, MemberSite, ReservedNameSite}, file_package::file_package, ids::{FunctionMarker, LocalItemId}, item_tree::{ImplBlock, ImplSubject, InterfaceFieldLink}, @@ -75,6 +76,9 @@ pub struct SemanticIndexBuilder<'db> { /// Depth of class scopes we're inside (> 0 means methods shouldn't /// contribute to top-level symbols — they belong to the class scope). class_depth: u32, + /// Whether the file belongs to a language package, which is exempt from + /// the reserved-name policy ([`ReservedName::of`]). + in_language_package: bool, /// Expression to lexical scope mappings, sorted by arena-safe key at the end. expr_scopes: Vec<(ExprMetadataKey, FileScopeId)>, @@ -112,6 +116,7 @@ impl<'db> SemanticIndexBuilder<'db> { item_scopes: FxHashMap::default(), scope_stack: Vec::new(), class_depth: 0, + in_language_package: false, expr_scopes: Vec::new(), lambda_scopes: Vec::new(), path_resolutions: Vec::new(), @@ -150,6 +155,9 @@ impl<'db> SemanticIndexBuilder<'db> { pub fn build(mut self, items: &[ast::Item], file_range: TextRange) -> FileSemanticIndex<'db> { let pkg_info = file_package(self.db, self.file); self.namespace_path.clone_from(&pkg_info.namespace_path); + self.in_language_package = crate::package::lang_roots(self.db) + .package_of(pkg_info.root) + .is_some(); // Build scope chain: Project → Package → Namespace* → File self.push_scope(ScopeKind::Project, None, file_range); @@ -161,13 +169,12 @@ impl<'db> SemanticIndexBuilder<'db> { file_range, ); for ns in &pkg_info.namespace_path { - // `ns_root/` would make `root.root.X` a path: refused like every - // other `root`, once per file in the directory. - if ns.as_str() == crate::package::ROOT_SEGMENT { - self.diagnostics.push(Hir2Diagnostic::RootNamespace { - span: TextRange::empty(file_range.start()), - }); - } + // Reported once per file in the directory. + self.reject_reserved_name( + ns, + TextRange::empty(file_range.start()), + ReservedNameSite::NamespaceDirectory, + ); self.push_scope(ScopeKind::Namespace, Some(ns.clone()), file_range); } let file_name = self @@ -326,9 +333,15 @@ impl<'db> SemanticIndexBuilder<'db> { /// uniformly to positional and defaulted parameters — both share the /// same `params` vector, and any name collision among them would make /// later references to the name ambiguous within the body. - fn emit_duplicate_param_diagnostics(&mut self, params: &[ast::Param]) { + /// + /// Also rejects a keyword parameter name. `allows_receiver` admits + /// `self` as the first parameter of a method. + fn emit_duplicate_param_diagnostics(&mut self, params: &[ast::Param], allows_receiver: bool) { let mut seen: FxHashMap> = FxHashMap::default(); - for param in params { + for (idx, param) in params.iter().enumerate() { + if !(allows_receiver && idx == 0 && param.name.as_str() == "self") { + self.reject_keyword_name(¶m.name, param.name_span, "a parameter"); + } seen.entry(param.name.clone()) .or_default() .push(MemberSite { @@ -339,28 +352,66 @@ impl<'db> SemanticIndexBuilder<'db> { self.emit_duplicate_diagnostics(seen); } - /// `root` is the path to the package itself - /// ([`crate::package::ROOT_SEGMENT`]) and names nothing else: not a - /// binding, an item, or a type parameter — so `root.X` always means this - /// package's `X`. - fn reject_root_name(&mut self, name: &Name, span: TextRange) { - if name.as_str() == crate::package::ROOT_SEGMENT { - self.diagnostics - .push(Hir2Diagnostic::RootIsReserved { span }); + /// Reject `name` if it is reserved ([`ReservedName::of`]): a keyword or + /// built-in type name declared outside a language package. Applies to + /// everything in the item and type namespaces, where a bare reference to + /// the name would read the keyword or built-in instead. + fn reject_reserved_name(&mut self, name: &Name, span: TextRange, site: ReservedNameSite) { + if self.in_language_package { + return; + } + if let Some(reason) = ReservedName::of(name.as_str()) { + self.diagnostics.push(Hir2Diagnostic::ReservedName { + name: name.clone(), + reason, + site, + span, + }); + } + } + + /// Reject a keyword naming a member, parameter, or local, outside a + /// language package. Only keywords are reserved here: members are reached + /// after a `.` and locals shadow, so a built-in type name stays legal. + fn reject_keyword_name(&mut self, name: &Name, span: TextRange, kind: &'static str) { + if !self.in_language_package + && ReservedName::of(name.as_str()) == Some(ReservedName::Keyword) + { + self.diagnostics.push(Hir2Diagnostic::ReservedName { + name: name.clone(), + reason: ReservedName::Keyword, + site: ReservedNameSite::Declaration(kind), + span, + }); } } - /// [`Self::reject_root_name`] over an item's type parameters. - fn reject_root_generics(&mut self, generics: &[ast::GenericParam]) { + fn reject_reserved_generics(&mut self, generics: &[ast::GenericParam]) { for generic in generics { - self.reject_root_name(&generic.name, generic.name_span); + self.reject_reserved_name( + &generic.name, + generic.name_span, + ReservedNameSite::Declaration("a type parameter"), + ); } } - /// Emit `DuplicateDefinition` diagnostics for any name with more than one site. + /// Emit `DuplicateDefinition` diagnostics for any name with more than one + /// site, and reject keyword member names. Methods are checked where they + /// are lowered, which also reaches those in `implements` blocks, and + /// parameters in [`Self::emit_duplicate_param_diagnostics`]. fn emit_duplicate_diagnostics(&mut self, seen: FxHashMap>) { let scope = self.current_scope_path(); for (name, sites) in seen { + for site in &sites { + let kind = match site.kind { + DefinitionKind::Field => "a field", + DefinitionKind::AssociatedType => "an associated type", + DefinitionKind::Variant => "an enum variant", + _ => continue, + }; + self.reject_keyword_name(&name, site.range, kind); + } if sites.len() > 1 { self.diagnostics.push(Hir2Diagnostic::DuplicateDefinition { name, @@ -463,7 +514,11 @@ impl<'db> SemanticIndexBuilder<'db> { .. } => { if let Some(span) = source_map.type_binding_name_span(stmt_id) { - self.reject_root_name(name, span); + self.reject_reserved_name( + name, + span, + ReservedNameSite::Declaration("a type binding"), + ); } // The operand and the `else` block are ordinary expressions // in the enclosing scope (`T` is not yet in scope in either). @@ -880,7 +935,7 @@ impl<'db> SemanticIndexBuilder<'db> { } for (name, (name_range, bind_pattern)) in names.names { - self.reject_root_name(&name, name_range); + self.reject_keyword_name(&name, name_range, "a binding"); self.scope_bindings[scope_id.index() as usize] .bindings .push(LocalBinding { @@ -1170,12 +1225,11 @@ impl<'db> SemanticIndexBuilder<'db> { let scope_id = self.current_scope_id(); self.lambda_scopes.push((key, scope_id)); for (idx, param) in lambda.params.iter().enumerate() { - self.reject_root_name(¶m.name, param.name_span); self.scope_bindings[scope_id.index() as usize] .params .push((param.name.clone(), idx)); } - self.emit_duplicate_param_diagnostics(&lambda.params); + self.emit_duplicate_param_diagnostics(&lambda.params, false); self.lambda_stack.push(scope_id); self.walk_parameter_defaults(&lambda.params, &lambda.defaults); @@ -1327,12 +1381,16 @@ impl<'db> SemanticIndexBuilder<'db> { ); let local_id = self.item_tree.alloc_function(f); let loc = FunctionLoc::new(self.db, self.file, local_id); - self.reject_root_generics(&f.generic_params); + self.reject_reserved_generics(&f.generic_params); // Only contribute as a top-level symbol if not inside a class. // Methods belong to the class scope, not the package namespace. if self.class_depth == 0 { - self.reject_root_name(&f.name, f.name_span); + self.reject_reserved_name( + &f.name, + f.name_span, + ReservedNameSite::Declaration("a function"), + ); self.value_contributions.push(( f.name.clone(), Contribution { @@ -1340,6 +1398,8 @@ impl<'db> SemanticIndexBuilder<'db> { definition: Definition::Function(loc), }, )); + } else { + self.reject_keyword_name(&f.name, f.name_span, "a method"); } self.push_scope(ScopeKind::Function, Some(f.name.clone()), f.span); @@ -1347,12 +1407,12 @@ impl<'db> SemanticIndexBuilder<'db> { self.record_scope_owner(scope_id, ItemScopeOwner::Function(local_id)); for (idx, param) in f.params.iter().enumerate() { - self.reject_root_name(¶m.name, param.name_span); self.scope_bindings[scope_id.index() as usize] .params .push((param.name.clone(), idx)); } - self.emit_duplicate_param_diagnostics(&f.params); + let is_method = self.class_depth > 0; + self.emit_duplicate_param_diagnostics(&f.params, is_method); self.walk_parameter_defaults(&f.params, &f.defaults); if let Some(ast::FunctionBodyDef::Expr(ref body, ref source_map)) = f.body { @@ -1385,8 +1445,12 @@ impl<'db> SemanticIndexBuilder<'db> { ); let local_id = self.item_tree.alloc_class(c, attrs, field_attrs); let loc = ClassLoc::new(self.db, self.file, local_id); - self.reject_root_name(&c.name, c.name_span); - self.reject_root_generics(&c.generic_params); + self.reject_reserved_name( + &c.name, + c.name_span, + ReservedNameSite::Declaration("a class"), + ); + self.reject_reserved_generics(&c.generic_params); self.type_contributions.push(( c.name.clone(), Contribution { @@ -1501,7 +1565,7 @@ impl<'db> SemanticIndexBuilder<'db> { fn lower_implements_for(&mut self, imp: &ast::ImplementsForDef) { self.class_depth += 1; - self.reject_root_generics(&imp.generic_params); + self.reject_reserved_generics(&imp.generic_params); // For blanket impls (implements I for C), push a class-like scope // so TIR can resolve `self` and type variables in method bodies. let has_generic_params = !imp.generic_params.is_empty(); @@ -1593,10 +1657,13 @@ impl<'db> SemanticIndexBuilder<'db> { AttributePosition::Function, &mut self.diagnostics, ); - self.reject_root_generics(&m.generic_params); - for param in &m.params { - self.reject_root_name(¶m.name, param.name_span); + self.reject_keyword_name(&m.name, m.name_span, "a method"); + for (idx, param) in m.params.iter().enumerate() { + if !(idx == 0 && param.name.as_str() == "self") { + self.reject_keyword_name(¶m.name, param.name_span, "a parameter"); + } } + self.reject_reserved_generics(&m.generic_params); self.item_tree.alloc_function_signature(m) })); @@ -1618,8 +1685,12 @@ impl<'db> SemanticIndexBuilder<'db> { let local_id = self.item_tree.alloc_interface(i, method_ids); self.record_scope_owner(interface_scope, ItemScopeOwner::Interface(local_id)); let loc = InterfaceLoc::new(self.db, self.file, local_id); - self.reject_root_name(&i.name, i.name_span); - self.reject_root_generics(&i.generic_params); + self.reject_reserved_name( + &i.name, + i.name_span, + ReservedNameSite::Declaration("an interface"), + ); + self.reject_reserved_generics(&i.generic_params); self.type_contributions.push(( i.name.clone(), Contribution { @@ -1685,7 +1756,11 @@ impl<'db> SemanticIndexBuilder<'db> { ); let local_id = self.item_tree.alloc_enum(e, attrs, variant_attrs); let loc = EnumLoc::new(self.db, self.file, local_id); - self.reject_root_name(&e.name, e.name_span); + self.reject_reserved_name( + &e.name, + e.name_span, + ReservedNameSite::Declaration("an enum"), + ); self.type_contributions.push(( e.name.clone(), Contribution { @@ -1716,7 +1791,11 @@ impl<'db> SemanticIndexBuilder<'db> { fn lower_type_alias(&mut self, ta: &ast::TypeAliasDef) { let local_id = self.item_tree.alloc_type_alias(ta); let loc = TypeAliasLoc::new(self.db, self.file, local_id); - self.reject_root_name(&ta.name, ta.name_span); + self.reject_reserved_name( + &ta.name, + ta.name_span, + ReservedNameSite::Declaration("a type alias"), + ); self.type_contributions.push(( ta.name.clone(), Contribution { @@ -1734,7 +1813,11 @@ impl<'db> SemanticIndexBuilder<'db> { fn lower_let(&mut self, l: &ast::LetDef) { let local_id = self.item_tree.alloc_let(l); let loc = LetLoc::new(self.db, self.file, local_id); - self.reject_root_name(&l.name, l.name_span); + self.reject_reserved_name( + &l.name, + l.name_span, + ReservedNameSite::Declaration("a top-level `let`"), + ); self.value_contributions.push(( l.name.clone(), Contribution { diff --git a/baml_language/crates/baml_compiler2_hir/src/diagnostic.rs b/baml_language/crates/baml_compiler2_hir/src/diagnostic.rs index 9e150b146fc..e45e378f9f5 100644 --- a/baml_language/crates/baml_compiler2_hir/src/diagnostic.rs +++ b/baml_language/crates/baml_compiler2_hir/src/diagnostic.rs @@ -10,6 +10,7 @@ use baml_compiler_diagnostics::{ diagnostic::{Diagnostic, DiagnosticId, DiagnosticPhase}, runtime_type::{self, DuplicateMemberKind, SerializedKeyContainer}, }; +use baml_type::reserved::ReservedName; use text_size::TextRange; use crate::contributions::DefinitionKind; @@ -21,6 +22,16 @@ pub struct MemberSite { pub kind: DefinitionKind, } +/// Where a reserved name was declared. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ReservedNameSite { + /// A declaration of the given kind, with its article (`a class`, + /// `an enum`, ...). + Declaration(&'static str), + /// A namespace, which takes its name from an `ns_/` directory. + NamespaceDirectory, +} + /// Per-file diagnostic produced during HIR2 semantic indexing. #[derive(Debug, Clone, PartialEq, Eq)] pub enum Hir2Diagnostic { @@ -54,11 +65,13 @@ pub enum Hir2Diagnostic { }, /// Builtin-only syntax used outside builtin stdlib files. BuiltinOnlySyntax { feature: String, span: TextRange }, - /// A binding, item, or type parameter named `root`, the path to the - /// package itself. - RootIsReserved { span: TextRange }, - /// A file under a directory `ns_root/`: a namespace named `root`. - RootNamespace { span: TextRange }, + /// A declaration named by a reserved name ([`ReservedName::of`]). + ReservedName { + name: Name, + reason: ReservedName, + site: ReservedNameSite, + span: TextRange, + }, /// Generic single-span diagnostic for builtin contract validation. DiagnosticMessage { diagnostic_id: DiagnosticId, @@ -412,34 +425,39 @@ impl Hir2Diagnostic { .with_primary(Span { file_id, range: *span }, "not valid here") .with_phase(DiagnosticPhase::Hir) } - Hir2Diagnostic::RootIsReserved { span } => Diagnostic::error( - DiagnosticId::RootIsReserved, - "`root` is the path to this package and cannot name a binding, an item, or a \ - type parameter" - .to_string(), - ) - .with_primary( - Span { - file_id, - range: *span, - }, - "`root` is this package", - ) - .with_phase(DiagnosticPhase::Hir), - Hir2Diagnostic::RootNamespace { span } => Diagnostic::error( - DiagnosticId::RootIsReserved, - "`root` is the path to this package and cannot name a namespace: rename the \ - directory `ns_root/`" - .to_string(), - ) - .with_primary( - Span { - file_id, - range: *span, - }, - "this file is in `ns_root/`", - ) - .with_phase(DiagnosticPhase::Hir), + Hir2Diagnostic::ReservedName { + name, + reason, + site, + span, + } => { + let what = match reason { + ReservedName::Keyword => "a keyword", + ReservedName::BuiltinType => "a built-in type name", + }; + let (message, label) = match site { + ReservedNameSite::Declaration(kind) => ( + format!("`{name}` is {what} and cannot name {kind}"), + format!("`{name}` is reserved"), + ), + ReservedNameSite::NamespaceDirectory => ( + format!( + "`{name}` is {what} and cannot name a namespace: rename the \ + directory `ns_{name}/`" + ), + format!("this file is in `ns_{name}/`"), + ), + }; + Diagnostic::error(DiagnosticId::ReservedName, message) + .with_primary( + Span { + file_id, + range: *span, + }, + label, + ) + .with_phase(DiagnosticPhase::Hir) + } Hir2Diagnostic::BuiltinOnlySyntax { feature, span } => Diagnostic::error( DiagnosticId::InvalidAttributeContext, format!("builtin-only syntax `{feature}` is only allowed in builtin stdlib files"), diff --git a/baml_language/crates/baml_compiler2_hir/src/package.rs b/baml_language/crates/baml_compiler2_hir/src/package.rs index 51295de7414..aa3bcef29c0 100644 --- a/baml_language/crates/baml_compiler2_hir/src/package.rs +++ b/baml_language/crates/baml_compiler2_hir/src/package.rs @@ -253,9 +253,7 @@ pub fn path_head( segments: &[Name], ) -> PathHead { match segments { - // `root` first: nothing may be named `root` (E0176), so a refused - // item of that name cannot take every `root.X` of its namespace - // down with it. + // `root` is a keyword, so no item can claim it. [first, _, ..] if first.as_str() == ROOT_SEGMENT => PathHead::Package(own), [first, _, ..] => { if crate::namespace::namespace_declares(db, own, namespace, first) { diff --git a/baml_language/crates/baml_compiler_diagnostics/src/diagnostic.rs b/baml_language/crates/baml_compiler_diagnostics/src/diagnostic.rs index 7c0ed26f5d7..1cc4e8e382b 100644 --- a/baml_language/crates/baml_compiler_diagnostics/src/diagnostic.rs +++ b/baml_language/crates/baml_compiler_diagnostics/src/diagnostic.rs @@ -176,7 +176,7 @@ pub enum DiagnosticId { LoweringError, // Removed feature errors (E0098) — shared by all removed-syntax - // diagnostics (`instanceof`, legacy BEP-066 TypeBuilder syntax, ...). + // diagnostics (legacy BEP-066 TypeBuilder syntax, ...). RemovedFeature, // Namespace diagnostics (E0099) @@ -405,9 +405,9 @@ pub enum DiagnosticId { /// thrown type a published clause would carry, or a type still being /// inferred that would be decided as one. ScopedTypeEscapesBlock, - /// A binding, item, type parameter, or namespace was named `root`, the - /// path to the package itself. - RootIsReserved, + /// A declaration or namespace was named by a keyword or a built-in type + /// name. + ReservedName, /// A class literal named a class that holds a `$rust_type` field. That /// state is created only by the class's own native functions; a literal /// could only copy a handle out of another value, which relabels what the @@ -642,7 +642,7 @@ impl DiagnosticId { DiagnosticId::InvalidBuiltinTypeArguments => "E0171", DiagnosticId::ScopedTypeEscapesBlock => "E0172", DiagnosticId::CannotConstructOpaqueClass => "E0173", - DiagnosticId::RootIsReserved => "E0176", + DiagnosticId::ReservedName => "E0176", DiagnosticId::ServedInterfaceExportsFunctionsOnly => "E0178", DiagnosticId::CallSiteBuiltinValue => "E0177", } diff --git a/baml_language/crates/baml_compiler_diagnostics/src/errors/type_error.rs b/baml_language/crates/baml_compiler_diagnostics/src/errors/type_error.rs index 1d0ce38f380..0f06074c255 100644 --- a/baml_language/crates/baml_compiler_diagnostics/src/errors/type_error.rs +++ b/baml_language/crates/baml_compiler_diagnostics/src/errors/type_error.rs @@ -143,7 +143,4 @@ pub enum TypeError { unused_types: Vec, location: C::Location, }, - - /// `instanceof` operator is no longer supported; use `match` instead. - InstanceofRemoved { location: C::Location }, } diff --git a/baml_language/crates/baml_compiler_diagnostics/src/to_diagnostic.rs b/baml_language/crates/baml_compiler_diagnostics/src/to_diagnostic.rs index b6b9fb6751a..1953a122332 100644 --- a/baml_language/crates/baml_compiler_diagnostics/src/to_diagnostic.rs +++ b/baml_language/crates/baml_compiler_diagnostics/src/to_diagnostic.rs @@ -301,12 +301,6 @@ impl TypeError { format!("type `{type_name}` is not allowed in catch bindings"), ) .with_primary_span(loc_fn(location)), - - TypeError::InstanceofRemoved { location } => Diagnostic::error( - DiagnosticId::RemovedFeature, - "`instanceof` is no longer supported. Use a `match` expression for type checking instead.".to_string(), - ) - .with_primary_span(loc_fn(location)), }; diag.with_phase(DiagnosticPhase::Type) } diff --git a/baml_language/crates/baml_compiler_lexer/src/lib.rs b/baml_language/crates/baml_compiler_lexer/src/lib.rs index 4cb48323d77..76406b8dd8e 100644 --- a/baml_language/crates/baml_compiler_lexer/src/lib.rs +++ b/baml_language/crates/baml_compiler_lexer/src/lib.rs @@ -6,7 +6,9 @@ use baml_base::SourceFile; mod tokens; -pub use tokens::{Token, TokenKind, is_baml_identifier, lex_lossless, reconstruct_source}; +pub use tokens::{ + Token, TokenKind, is_baml_identifier, is_keyword, lex_lossless, reconstruct_source, +}; /// Tracked: tokenize a source file /// This function performs lexical analysis on a BAML source file, diff --git a/baml_language/crates/baml_compiler_lexer/src/tokens.rs b/baml_language/crates/baml_compiler_lexer/src/tokens.rs index 6c99f4665d1..c4c25ee06fc 100644 --- a/baml_language/crates/baml_compiler_lexer/src/tokens.rs +++ b/baml_language/crates/baml_compiler_lexer/src/tokens.rs @@ -58,20 +58,6 @@ pub enum TokenKind { Requires, #[token("function")] Function, - #[token("client")] - Client, - /// Deprecated: `generator` blocks moved to `baml.toml`. Still lexed so the - /// parser can recognize stale blocks and raise a migration diagnostic. - #[token("generator")] - Generator, - #[token("test")] - Test, - #[token("testset")] - TestSet, - #[token("retry_policy")] - RetryPolicy, - #[token("template_string")] - TemplateString, // Control flow keywords #[token("if")] If, @@ -107,12 +93,26 @@ pub enum TokenKind { Await, #[token("defer")] Defer, + #[token("catch_all_panics")] + CatchAllPanics, // Other keywords - #[token("instanceof")] - Instanceof, #[token("is")] Is, + #[token("const")] + Const, + #[token("true")] + True, + #[token("false")] + False, + #[token("null")] + Null, + /// The method receiver, `self`. + #[token("self")] + SelfValue, + /// The path to the current package, as in `root.X`. + #[token("root")] + Root, // ============ Identifiers and Literals ============ /// Any identifier-like word (non-keyword) /// Also matches $-prefixed identifiers and `$`-separated names. @@ -342,6 +342,49 @@ pub enum TokenKind { Error, } +impl TokenKind { + /// Whether this token is a keyword. Keywords never name a declaration; + /// after a `.` they are member and path-segment names like any word. + pub fn is_keyword(self) -> bool { + matches!( + self, + TokenKind::Class + | TokenKind::Enum + | TokenKind::Interface + | TokenKind::Implements + | TokenKind::Implement + | TokenKind::Extends + | TokenKind::Requires + | TokenKind::Function + | TokenKind::If + | TokenKind::Else + | TokenKind::For + | TokenKind::While + | TokenKind::Let + | TokenKind::In + | TokenKind::Break + | TokenKind::Continue + | TokenKind::Return + | TokenKind::Throw + | TokenKind::Match + | TokenKind::Catch + | TokenKind::CatchAll + | TokenKind::Throws + | TokenKind::Spawn + | TokenKind::Await + | TokenKind::Defer + | TokenKind::CatchAllPanics + | TokenKind::Is + | TokenKind::Const + | TokenKind::True + | TokenKind::False + | TokenKind::Null + | TokenKind::SelfValue + | TokenKind::Root + ) + } +} + impl std::fmt::Display for TokenKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let s = match self { @@ -354,12 +397,6 @@ impl std::fmt::Display for TokenKind { TokenKind::Extends => "extends", TokenKind::Requires => "requires", TokenKind::Function => "function", - TokenKind::Client => "client", - TokenKind::Generator => "generator", - TokenKind::Test => "test", - TokenKind::TestSet => "testset", - TokenKind::RetryPolicy => "retry_policy", - TokenKind::TemplateString => "template_string", TokenKind::If => "if", TokenKind::Else => "else", TokenKind::For => "for", @@ -377,8 +414,14 @@ impl std::fmt::Display for TokenKind { TokenKind::Spawn => "spawn", TokenKind::Await => "await", TokenKind::Defer => "defer", - TokenKind::Instanceof => "instanceof", + TokenKind::CatchAllPanics => "catch_all_panics", TokenKind::Is => "is", + TokenKind::Const => "const", + TokenKind::True => "true", + TokenKind::False => "false", + TokenKind::Null => "null", + TokenKind::SelfValue => "self", + TokenKind::Root => "root", // Identifiers and literals TokenKind::Word => "identifier", TokenKind::Quote => "'\"'", @@ -483,27 +526,18 @@ pub struct Token { /// Return whether `value` is one complete, non-keyword BAML identifier token. /// -/// Lexer keywords are rejected automatically by their dedicated [`TokenKind`]. -/// The additional spellings are contextual parser keywords: they intentionally -/// remain `Word` tokens so the parser can recognize them only in the grammar -/// positions where they are meaningful. +/// Every keyword has a dedicated [`TokenKind`], so this is the whole rule. +/// Weak keywords (`client`, `test`, `type`, `as`, ...) lex as `Word` and are +/// identifiers everywhere the parser does not read them as a declaration. pub fn is_baml_identifier(value: &str) -> bool { - const CONTEXTUAL_KEYWORDS: &[&str] = &[ - "as", - "catch_all_panics", - "const", - "false", - "map", - "null", - "true", - "type", - "with", - ]; + let mut lexer = TokenKind::lexer(value); + matches!(lexer.next(), Some(Ok(TokenKind::Word))) && lexer.next().is_none() +} +/// Return whether `value` is exactly one keyword. +pub fn is_keyword(value: &str) -> bool { let mut lexer = TokenKind::lexer(value); - matches!(lexer.next(), Some(Ok(TokenKind::Word))) - && lexer.next().is_none() - && !CONTEXTUAL_KEYWORDS.contains(&value) + matches!(lexer.next(), Some(Ok(kind)) if kind.is_keyword()) && lexer.next().is_none() } /// Lossless lexer that preserves all source text. @@ -1059,21 +1093,18 @@ mod tests { } #[test] - fn baml_identifiers_exclude_lexer_and_contextual_keywords() { + fn baml_identifiers_exclude_keywords() { for keyword in [ "class", - "test", "return", "is", - "as", "catch_all_panics", "const", "false", - "map", "null", "true", - "type", - "with", + "self", + "root", ] { assert!( !is_baml_identifier(keyword), @@ -1081,7 +1112,6 @@ mod tests { ); } - // `unreflect` was a contextual keyword until `let type` replaced it. for identifier in [ "Thing", "field_name", @@ -1089,6 +1119,19 @@ mod tests { "$companion", "Foo$bar", "unreflect", + "client", + "test", + "testset", + "template_string", + "retry_policy", + "type", + "as", + "with", + "map", + "instanceof", + "generator", + "self_ref", + "rooted", ] { assert!( is_baml_identifier(identifier), @@ -1102,9 +1145,11 @@ mod tests { let tokens = lex_no_whitespace("throw catch"); assert_eq!(tokens, vec![TokenKind::Throw, TokenKind::Catch,]); - // catch_all is a keyword; catch_all_panics lexes as a plain identifier let tokens2 = lex_no_whitespace("catch_all catch_all_panics"); - assert_eq!(tokens2, vec![TokenKind::CatchAll, TokenKind::Word,]); + assert_eq!( + tokens2, + vec![TokenKind::CatchAll, TokenKind::CatchAllPanics] + ); } #[test] diff --git a/baml_language/crates/baml_compiler_parser/src/parser.rs b/baml_language/crates/baml_compiler_parser/src/parser.rs index b73656179a8..ef9596ea355 100644 --- a/baml_language/crates/baml_compiler_parser/src/parser.rs +++ b/baml_language/crates/baml_compiler_parser/src/parser.rs @@ -35,12 +35,6 @@ fn token_kind_to_syntax_kind(kind: TokenKind) -> SyntaxKind { TokenKind::Extends => SyntaxKind::KW_EXTENDS, TokenKind::Requires => SyntaxKind::KW_REQUIRES, TokenKind::Function => SyntaxKind::KW_FUNCTION, - TokenKind::Client => SyntaxKind::KW_CLIENT, - TokenKind::Generator => SyntaxKind::KW_GENERATOR, - TokenKind::Test => SyntaxKind::KW_TEST, - TokenKind::TestSet => SyntaxKind::KW_TESTSET, - TokenKind::RetryPolicy => SyntaxKind::KW_RETRY_POLICY, - TokenKind::TemplateString => SyntaxKind::KW_TEMPLATE_STRING, TokenKind::If => SyntaxKind::KW_IF, TokenKind::Else => SyntaxKind::KW_ELSE, TokenKind::For => SyntaxKind::KW_FOR, @@ -51,7 +45,6 @@ fn token_kind_to_syntax_kind(kind: TokenKind) -> SyntaxKind { TokenKind::Continue => SyntaxKind::KW_CONTINUE, TokenKind::Return => SyntaxKind::KW_RETURN, TokenKind::Throw => SyntaxKind::KW_THROW, - TokenKind::Instanceof => SyntaxKind::KW_INSTANCEOF, TokenKind::Is => SyntaxKind::KW_IS, TokenKind::Match => SyntaxKind::KW_MATCH, TokenKind::Catch => SyntaxKind::KW_CATCH, @@ -60,6 +53,14 @@ fn token_kind_to_syntax_kind(kind: TokenKind) -> SyntaxKind { TokenKind::Spawn => SyntaxKind::KW_SPAWN, TokenKind::Await => SyntaxKind::KW_AWAIT, TokenKind::Defer => SyntaxKind::KW_DEFER, + TokenKind::CatchAllPanics => SyntaxKind::KW_CATCH_ALL_PANICS, + TokenKind::Const => SyntaxKind::KW_CONST, + TokenKind::True => SyntaxKind::KW_TRUE, + TokenKind::False => SyntaxKind::KW_FALSE, + TokenKind::Null => SyntaxKind::KW_NULL, + // `self` and `root` are reserved names rather than syntax: where they + // are legal they read exactly like any other name. + TokenKind::SelfValue | TokenKind::Root => SyntaxKind::WORD, // Literals TokenKind::Word => SyntaxKind::WORD, @@ -177,6 +178,46 @@ enum BacktickInterpForm { Expression, } +/// A declaration introduced by a weak keyword. See +/// [`Parser::weak_declaration_at`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum WeakDeclaration { + Client, + /// Deprecated: generators moved to `baml.toml`. Still recognized so a + /// stale block gets a migration diagnostic. + Generator, + Test, + TestSet, + RetryPolicy, + TemplateString, +} + +impl WeakDeclaration { + const ALL: [WeakDeclaration; 6] = [ + WeakDeclaration::Client, + WeakDeclaration::Generator, + WeakDeclaration::Test, + WeakDeclaration::TestSet, + WeakDeclaration::RetryPolicy, + WeakDeclaration::TemplateString, + ]; + + fn keyword(self) -> (&'static str, SyntaxKind) { + match self { + WeakDeclaration::Client => ("client", SyntaxKind::KW_CLIENT), + WeakDeclaration::Generator => ("generator", SyntaxKind::KW_GENERATOR), + WeakDeclaration::Test => ("test", SyntaxKind::KW_TEST), + WeakDeclaration::TestSet => ("testset", SyntaxKind::KW_TESTSET), + WeakDeclaration::RetryPolicy => ("retry_policy", SyntaxKind::KW_RETRY_POLICY), + WeakDeclaration::TemplateString => ("template_string", SyntaxKind::KW_TEMPLATE_STRING), + } + } + + fn from_keyword(text: &str) -> Option { + Self::ALL.into_iter().find(|d| d.keyword().0 == text) + } +} + /// Events for building the syntax tree. #[derive(Debug, Clone)] enum Event { @@ -398,9 +439,8 @@ impl<'a> Parser<'a> { .unwrap_or(false) } - /// Check if the current token is a `Word` with the given text. - /// Used for contextual keywords like `with` and binding-position `const` - /// that should not be reserved globally. + /// Check if the current token is a `Word` with the given text: a weak + /// keyword such as `with` or `type`, special only in its own syntax. fn at_contextual_kw(&self, kw: &str) -> bool { Self::token_is_contextual_kw(self.current(), kw) } @@ -415,7 +455,9 @@ impl<'a> Parser<'a> { fn at_let_type_binding_stmt(&self) -> bool { self.at(TokenKind::Let) && self.peek_is_contextual_kw(1, "type") - && self.peek(2).map(|token| token.kind) == Some(TokenKind::Word) + && self + .peek(2) + .is_some_and(|token| Self::kind_is_member_name(token.kind)) } /// The removed body-level `type Name = …` statement. `type` is contextual @@ -428,10 +470,22 @@ impl<'a> Parser<'a> { } /// Consume the current contextual keyword token, re-labelling it as - /// `syntax_kind` in the syntax tree. Handles leading trivia just like - /// [`Self::bump`]. + /// `syntax_kind` in the syntax tree. fn bump_contextual_kw_as(&mut self, kw: &str, syntax_kind: SyntaxKind) { debug_assert!(self.at_contextual_kw(kw)); + self.bump_as(syntax_kind); + } + + /// Consume the current token as a name: a word, or a keyword standing + /// where only a name can (`x.class`, `baml.spawn`). The syntax tree + /// holds it as a `WORD`, so every reader of names sees one kind. + fn bump_name(&mut self) { + self.bump_as(SyntaxKind::WORD); + } + + /// Consume the current token, re-labelling it as `syntax_kind` in the + /// syntax tree. Handles leading trivia just like [`Self::bump`]. + fn bump_as(&mut self, syntax_kind: SyntaxKind) { // Emit leading trivia (whitespace, newlines, comments) before the keyword, // matching the same pattern as `bump_impl`. while self.current < self.tokens.len() { @@ -454,7 +508,6 @@ impl<'a> Parser<'a> { } break; } - // Emit the contextual Word token as the requested keyword kind. if self.current < self.tokens.len() { self.events.push(Event::Token { kind: syntax_kind, @@ -470,39 +523,16 @@ impl<'a> Parser<'a> { self.bump_contextual_kw_as("with", SyntaxKind::KW_WITH); } - fn binding_intro_follower(kind: Option) -> bool { - matches!(kind, Some(TokenKind::Word | TokenKind::LBracket)) - } - - /// True at either `let` or contextual `const`. + /// True at `let` or `const`. fn at_binding_intro(&self) -> bool { - self.at(TokenKind::Let) || self.at_contextual_kw("const") - } - - /// True for positions that dispatch to a binding statement or - /// statement-like binding head. `let` stays a real keyword; contextual - /// `const` only claims the position when a pattern-shaped token follows. - fn at_binding_intro_stmt(&self) -> bool { - self.at(TokenKind::Let) - || (self.at_contextual_kw("const") - && Self::binding_intro_follower(self.peek(1).map(|t| t.kind))) - } - - fn at_binding_intro_pattern(&self) -> bool { - self.at(TokenKind::Let) - || (self.at_contextual_kw("const") - && Self::binding_intro_follower(self.peek(1).map(|t| t.kind))) + self.at(TokenKind::Let) || self.at(TokenKind::Const) } fn peek_is_binding_intro(&self, n: usize) -> bool { - self.peek(n).map(|t| t.kind) == Some(TokenKind::Let) - || self.peek_is_contextual_kw(n, "const") - } - - fn peek_is_binding_intro_stmt(&self, n: usize) -> bool { - self.peek(n).map(|t| t.kind) == Some(TokenKind::Let) - || (self.peek_is_binding_intro(n) - && Self::binding_intro_follower(self.peek(n + 1).map(|t| t.kind))) + matches!( + self.peek(n).map(|t| t.kind), + Some(TokenKind::Let | TokenKind::Const) + ) } fn binding_intro_is_followed_by_array_pattern(&self) -> bool { @@ -510,10 +540,8 @@ impl<'a> Parser<'a> { } fn bump_binding_intro(&mut self) { - if self.at(TokenKind::Let) { + if self.at_binding_intro() { self.bump(); - } else if self.at_contextual_kw("const") { - self.bump_contextual_kw_as("const", SyntaxKind::KW_CONST); } else { self.error_unexpected_token("'let' or 'const'".to_string()); } @@ -523,7 +551,7 @@ impl<'a> Parser<'a> { /// Valid type starts: Word (type name), string literal, integer/float literal, /// `-` followed by an integer/float literal (negative literal type), `LParen` (tuple). fn is_at_type_start(&self) -> bool { - self.at(TokenKind::Word) + self.at_type_name_start() || self.at(TokenKind::Quote) // string literal type || self.at(TokenKind::Hash) // removed hash string recovery || self.at(TokenKind::BigintLiteral) @@ -549,41 +577,77 @@ impl<'a> Parser<'a> { matches!(kind, TokenKind::Whitespace | TokenKind::Newline) } - /// Token kinds legal as a `.member` name — identifiers plus the contextual - /// keywords a declaration may use as a member name (`parse_interface_method` - /// accepts the same set, so a method named `implements` must stay reachable - /// through every access spelling). + /// Token kinds legal as a `.member` name: any word or keyword. Keywords + /// never name a user declaration, but the stdlib may (`t.implements(u)`, + /// `baml.spawn`), and every such name stays reachable after a `.`. /// /// Kind-based so lookahead can test a token at an arbitrary index; see /// [`Self::at_member_name`] for the position-based form. fn kind_is_member_name(kind: TokenKind) -> bool { + kind == TokenKind::Word || kind.is_keyword() + } + + fn at_member_name(&self) -> bool { + self.current() + .is_some_and(|token| Self::kind_is_member_name(token.kind)) + } + + /// Whether the current token can be taken as a declared name: a word, or + /// a keyword standing where only a name can. Like rustc's identifier + /// recovery, the keyword is kept as the name, so the name check reports + /// it once (`baml_type::reserved::ReservedName`) instead of a cascade of + /// parse errors. A keyword that starts a declaration is left to it. + fn at_declared_name(&self) -> bool { + self.current().is_some_and(|t| { + t.kind == TokenKind::Word || (t.kind.is_keyword() && !self.at_top_level_keyword()) + }) + } + + /// [`Self::at_declared_name`] for a field or enum variant, whose name + /// begins the member: a keyword counts only at the start of a line or + /// after `{`, `,`, or `;`, so a stray keyword after a complete member + /// (`a () -> int @stream.done throws never`) stays a parse error. + fn at_member_declared_name(&self) -> bool { + if self.at(TokenKind::Word) { + return true; + } + if !self.at_declared_name() { + return false; + } + let mut i = self.skip_trivia_and_comments_from(self.current); + while i > 0 { + i -= 1; + match self.tokens[i].kind { + TokenKind::Whitespace => {} + TokenKind::Newline + | TokenKind::LBrace + | TokenKind::Comma + | TokenKind::Semicolon => return true, + _ => return false, + } + } + true + } + + /// Token kinds that can begin a path: a name, or one of the reserved + /// names `self` and `root`. + fn kind_is_path_head(kind: TokenKind) -> bool { matches!( kind, - TokenKind::Word - | TokenKind::Class - | TokenKind::Enum - | TokenKind::Function - | TokenKind::Implements - | TokenKind::Implement - | TokenKind::Extends - | TokenKind::Requires - | TokenKind::Interface - // `client` is a keyword for LLM config/declarations, but stays - // valid as a class field name and member-access name so - // BEP-049 §10's `ctx.client` (on the `Context` type) parses. - // Unambiguous here: class bodies and `.member` access have no - // `client` construct. - | TokenKind::Client + TokenKind::Word | TokenKind::SelfValue | TokenKind::Root ) } - /// True when the current token can serve as a member name after `.`. - /// - /// Declaration keywords remain valid as member names — e.g. - /// `dog_t.implements(animal_t)` on the reflection `type` value. - fn at_member_name(&self) -> bool { + fn at_path_head(&self) -> bool { self.current() - .is_some_and(|token| Self::kind_is_member_name(token.kind)) + .is_some_and(|token| Self::kind_is_path_head(token.kind)) + } + + fn at_type_name_start(&self) -> bool { + self.at_path_head() + || self.at(TokenKind::True) + || self.at(TokenKind::False) + || self.at(TokenKind::Null) } /// True for `field as class_field` inside an `implements` block. @@ -592,15 +656,7 @@ impl<'a> Parser<'a> { let Some(first_token) = self.tokens.get(first) else { return false; }; - if !matches!( - first_token.kind, - TokenKind::Word - | TokenKind::Implements - | TokenKind::Implement - | TokenKind::Extends - | TokenKind::Requires - | TokenKind::Interface - ) { + if first_token.kind != TokenKind::Word { return false; } let second = self.skip_trivia_and_comments_from(first + 1); @@ -1236,45 +1292,124 @@ impl<'a> Parser<'a> { // ============ Error Recovery Helpers ============` - /// Check if the current token is a top-level keyword. + /// The weak keyword that starts a declaration at token index `i`, if any. + /// + /// A weak keyword is a `Word` that reads as a keyword only where it begins + /// a declaration of its form, so the same word stays an ordinary + /// identifier everywhere else (`client.call()`, `test(x)`, `client: C?`). + fn weak_declaration_at(&self, i: usize) -> Option { + let token = self.tokens.get(i).filter(|t| t.kind == TokenKind::Word)?; + let after = self.skip_trivia_and_comments_from(i + 1); + let next = self.tokens.get(after).map(|t| t.kind); + let next_next = self + .tokens + .get(self.skip_trivia_and_comments_from(after + 1)) + .map(|t| t.kind); + let declaration = WeakDeclaration::from_keyword(&token.text)?; + let starts = match declaration { + // `client Name { … }` or `client Name = …`. + WeakDeclaration::Client => { + next == Some(TokenKind::Less) + || (next == Some(TokenKind::Word) && next_next == Some(TokenKind::Equals)) + } + // A test name is any expression. `(` also continues `test` as a + // call, so a parenthesized name counts only when the test body + // follows it: `test (5 + i) { … }` but not `test(x)`. + WeakDeclaration::Test | WeakDeclaration::TestSet => match next { + Some(TokenKind::LParen) => self.paren_group_precedes_test_body(after), + Some(TokenKind::Backtick) => true, + Some(kind) => Self::is_start_of_expression(kind), + None => false, + }, + WeakDeclaration::Generator => { + matches!(next, Some(TokenKind::Word | TokenKind::LBrace)) + } + WeakDeclaration::RetryPolicy | WeakDeclaration::TemplateString => { + next == Some(TokenKind::Word) + } + }; + starts.then_some(declaration) + } + + /// Whether the parenthesized group opening at token index `open` is + /// followed by a test body: `{` or a `with` runner clause. + fn paren_group_precedes_test_body(&self, open: usize) -> bool { + let mut depth = 0_usize; + let mut i = open; + while let Some(token) = self.tokens.get(i) { + match token.kind { + TokenKind::LParen => depth += 1, + TokenKind::RParen => { + depth -= 1; + if depth == 0 { + let next = self.skip_trivia_and_comments_from(i + 1); + return self.tokens.get(next).is_some_and(|t| { + t.kind == TokenKind::LBrace + || (t.kind == TokenKind::Word && t.text == "with") + }); + } + } + _ => {} + } + i += 1; + } + false + } + + fn at_weak_declaration(&self) -> Option { + self.weak_declaration_at(self.skip_trivia_and_comments_from(self.current)) + } + + /// Whether a declaration starts at token index `i`. + /// + /// A declaration keyword starts one unless the next token can only follow + /// a name (`class C { function: int }`, `enum E { class }`, + /// ``): then it is a keyword standing where a name was expected, + /// which [`Self::at_declared_name`] takes as the name. + fn declaration_starts_at(&self, i: usize) -> bool { + let Some(token) = self.tokens.get(i) else { + return false; + }; + match token.kind { + TokenKind::Class + | TokenKind::Enum + | TokenKind::Interface + | TokenKind::Function + | TokenKind::Implements + | TokenKind::Implement => !matches!( + self.tokens + .get(self.skip_trivia_and_comments_from(i + 1)) + .map(|t| t.kind), + Some( + TokenKind::Colon + | TokenKind::Comma + | TokenKind::Semicolon + | TokenKind::LBrace + | TokenKind::RBrace + | TokenKind::RParen + | TokenKind::Greater + | TokenKind::Equals + | TokenKind::Question + ) + ), + _ => self.weak_declaration_at(i).is_some(), + } + } + + /// Check if the current token starts a top-level declaration. /// Used for error recovery to break out of malformed blocks. fn at_top_level_keyword(&self) -> bool { - matches!( - self.current().map(|t| t.kind), - Some( - TokenKind::Class - | TokenKind::Enum - | TokenKind::Interface - | TokenKind::Function - | TokenKind::Implements - | TokenKind::Implement - | TokenKind::Client - | TokenKind::Generator - | TokenKind::Test - | TokenKind::TestSet - | TokenKind::RetryPolicy - | TokenKind::TemplateString - ) - ) + self.declaration_starts_at(self.skip_trivia_and_comments_from(self.current)) } - /// Distinguishes a top-level `client Name { … }` declaration (which, - /// inside a class body, signals a missing `}` to recover from) from a class - /// field named `client` (BEP-049 §10 `ctx.client`). The declaration form is - /// `client<…>`; a field is `client Type` / `client:`. - fn looks_like_client_declaration_start(&self) -> bool { - let current = self.skip_trivia_and_comments_from(self.current); - if self - .tokens - .get(current) - .is_none_or(|t| t.kind != TokenKind::Client) - { - return false; + /// Consume the weak keyword that starts `declaration`. + fn bump_weak_keyword(&mut self, declaration: WeakDeclaration) { + let (keyword, kind) = declaration.keyword(); + if self.at_contextual_kw(keyword) { + self.bump_contextual_kw_as(keyword, kind); + } else { + self.error_unexpected_token(format!("`{keyword}`")); } - let next = self.skip_trivia_and_comments_from(current + 1); - self.tokens - .get(next) - .is_some_and(|t| t.kind == TokenKind::Less) } fn looks_like_interface_declaration_start(&self) -> bool { @@ -1318,27 +1453,19 @@ impl<'a> Parser<'a> { false } - /// [`at_top_level_keyword`] minus tokens that are valid in statement position: - /// - `client` can start `client.method(...)` (parameter named `client`) - /// - `test` starts an expression-body test (valid in blocks) - /// - `testset` with a string literal is a testset declaration (valid in blocks) - fn at_top_level_keyword_except_client(&self) -> bool { - if !self.at_top_level_keyword() { - return false; - } - if self.at(TokenKind::Client) { - return false; - } - // Expression-body test/testset are valid inside block expressions - if self.at(TokenKind::Test) || self.at(TokenKind::TestSet) { - return false; - } - true + /// [`Self::at_top_level_keyword`] minus `test` and `testset`, which are + /// also statements inside a testset body. + fn at_top_level_keyword_outside_testset(&self) -> bool { + self.at_top_level_keyword() + && !matches!( + self.at_weak_declaration(), + Some(WeakDeclaration::Test | WeakDeclaration::TestSet) + ) } fn at_statement_recovery_boundary(&self) -> bool { - self.at_top_level_keyword_except_client() - || self.at_binding_intro_stmt() + self.at_top_level_keyword_outside_testset() + || self.at_binding_intro() || matches!( self.current().map(|t| t.kind), Some( @@ -1616,7 +1743,7 @@ impl<'a> Parser<'a> { _ => false, } }); - if !self.at(TokenKind::Test) + if !self.at_contextual_kw("test") || self.peek(1).map(|token| token.kind) != Some(TokenKind::Word) || self.peek(2).map(|token| token.kind) != Some(TokenKind::LBrace) || !starts_legacy_config @@ -1684,18 +1811,7 @@ impl<'a> Parser<'a> { && !self.at(TokenKind::LBrace) && !self.at(TokenKind::RBrace) { - // Stop at keywords that would start a new declaration - if matches!( - self.current().map(|t| t.kind), - Some( - TokenKind::Class - | TokenKind::Enum - | TokenKind::Function - | TokenKind::Client - | TokenKind::Generator - | TokenKind::Test - ) - ) { + if self.at_top_level_keyword() { break; } self.bump(); @@ -2427,19 +2543,7 @@ impl<'a> Parser<'a> { // keyword *begins a line* (where module items actually start): a // keyword sitting mid-line is backtick content (e.g. // ````function name````) and must not suppress a valid close run. - if matches!( - self.tokens[i].kind, - TokenKind::Class - | TokenKind::Enum - | TokenKind::Function - | TokenKind::Client - | TokenKind::Generator - | TokenKind::Test - | TokenKind::TestSet - | TokenKind::RetryPolicy - | TokenKind::TemplateString - ) && self.token_starts_line(i) - { + if self.declaration_starts_at(i) && self.token_starts_line(i) { return false; } @@ -2825,7 +2929,7 @@ impl<'a> Parser<'a> { // Mirror the host `for` parser: a binding intro is `let` OR the // contextual `const`, so `${for (const x in xs)}` parses like host // syntax instead of falling into the C-style path and erroring. - if self.at_binding_intro_stmt() { + if self.at_binding_intro() { if self.looks_like_for_in_loop() { self.parse_for_in_pattern(); self.expect(TokenKind::In); @@ -2985,10 +3089,11 @@ impl<'a> Parser<'a> { { // Expression block: {{ }} self.parse_expression_block(); - } else if self.at(TokenKind::Word) { - // Unquoted string: only permit one word + } else if self.at_type_name_start() { + // Unquoted string: one word, `true`/`false`/`null` included + // (`@stream.starts_as(null)`), read by its text. self.with_node(SyntaxKind::UNQUOTED_STRING, |p| { - p.bump(); + p.bump_name(); }); } else { self.error_unexpected_token("attribute argument".to_string()); @@ -3184,26 +3289,16 @@ impl<'a> Parser<'a> { return; } - if self.at(TokenKind::Word) { - // Base type name, generic type, or boolean literal (true/false) - // Note: true/false are Word tokens, and they become BoolLiteral types - self.bump(); + if self.at_type_name_start() { + // Type name, generic type, or a `true` / `false` / `null` type, + // which the type lowering reads by name. + self.bump_name(); - // Consume dot-separated path segments (e.g., baml.http.Request). - // `spawn`/`await` are reserved keywords but valid as namespace - // segments after a `.` (e.g. `baml.spawn.Plan` in a type - // annotation), mirroring `parse_path_or_ident`'s segment set. + // Dot-separated path segments (e.g., baml.http.Request). while self.at(TokenKind::Dot) { self.bump(); // dot - if self.at(TokenKind::Word) - || self.at(TokenKind::Spawn) - || self.at(TokenKind::Await) - || self.at(TokenKind::Class) - || self.at(TokenKind::Enum) - || self.at(TokenKind::Interface) - || self.at(TokenKind::Function) - { - self.bump(); // next segment + if self.at_member_name() { + self.bump_name(); } else { self.error_unexpected_token("type name segment after '.'".to_string()); break; @@ -3279,7 +3374,7 @@ impl<'a> Parser<'a> { self.expect(TokenKind::RParen); self.expect(TokenKind::Dot); if self.at_member_name() { - self.bump(); + self.bump_name(); } else { self.error_unexpected_token("projected item name".to_string()); } @@ -3435,7 +3530,7 @@ impl<'a> Parser<'a> { fn parse_function_type_param_inner(&mut self) -> bool { // Check if this is `name: type` by looking ahead // If we see WORD followed by COLON (skipping trivia), it's a named param - let is_named = (self.at(TokenKind::Word) || self.at(TokenKind::Client)) + let is_named = self.at(TokenKind::Word) && matches!( (self.peek(1).map(|t| t.kind), self.peek(2).map(|t| t.kind)), (Some(TokenKind::Colon), _) | (Some(TokenKind::Question), Some(TokenKind::Colon)) @@ -3479,9 +3574,8 @@ impl<'a> Parser<'a> { // 'enum' keyword p.expect(TokenKind::Enum); - // Enum name - if p.at(TokenKind::Word) { - p.bump(); // name + if p.at_declared_name() { + p.bump_name(); } else { p.error_unexpected_token("enum name".to_string()); } @@ -3502,7 +3596,7 @@ impl<'a> Parser<'a> { if p.at(TokenKind::AtAt) { // Block attribute p.parse_atat_attribute(); - } else if p.at(TokenKind::Word) { + } else if p.at_member_declared_name() { // Enum variant p.parse_enum_variant(); // Optional delimiter after a variant. Commas are canonical, @@ -3525,8 +3619,7 @@ impl<'a> Parser<'a> { fn parse_enum_variant(&mut self) { self.with_node(SyntaxKind::ENUM_VARIANT, |p| { - // Variant name - p.bump(); + p.bump_name(); // Check if a type expression follows (which is invalid for enum variants) // We need to distinguish between: @@ -3608,9 +3701,8 @@ impl<'a> Parser<'a> { // 'class' keyword p.expect(TokenKind::Class); - // Class name - if p.at(TokenKind::Word) { - p.bump(); // name + if p.at_declared_name() { + p.bump_name(); } else { p.error_unexpected_token("class name".to_string()); } @@ -3636,12 +3728,6 @@ impl<'a> Parser<'a> { // assume we missed a closing brace let recover_top_level_item = if p.at(TokenKind::Interface) { p.looks_like_interface_declaration_start() - } else if p.at(TokenKind::Client) { - // `client` is a top-level keyword (`client Name { … }`), - // but also a valid class field name (BEP-049 §10 `ctx.client` - // on `Context`). Only treat it as a missing-brace recovery - // when it's the `client<…>` declaration form. - p.looks_like_client_declaration_start() } else { p.at_top_level_keyword() }; @@ -3661,13 +3747,15 @@ impl<'a> Parser<'a> { } else if p.at(TokenKind::AtAt) { // Block attribute p.parse_atat_attribute(); - } else if p.at(TokenKind::Function) { + } else if p.at(TokenKind::Function) && p.at_top_level_keyword() { // Method definition p.parse_function(); - } else if p.at(TokenKind::Implements) || p.at(TokenKind::Implement) { + } else if (p.at(TokenKind::Implements) || p.at(TokenKind::Implement)) + && p.at_top_level_keyword() + { // Interface implementation block p.parse_implements_block(); - } else if p.at_member_name() { + } else if p.at_member_declared_name() { // Field declaration p.parse_field(); if !p.eat(TokenKind::Comma) { @@ -3705,8 +3793,8 @@ impl<'a> Parser<'a> { p.expect(TokenKind::Interface); - if p.at(TokenKind::Word) { - p.bump(); // name + if p.at_declared_name() { + p.bump_name(); } else { p.error_unexpected_token("interface name".to_string()); } @@ -3742,14 +3830,14 @@ impl<'a> Parser<'a> { p.parse_interface_method(); } else if p.at(TokenKind::AtAt) { p.parse_atat_attribute(); - } else if p.at(TokenKind::Function) { + } else if p.at(TokenKind::Function) && p.at_top_level_keyword() { p.parse_interface_method(); } else if p.at_contextual_kw("type") { p.parse_associated_type_decl(false); if !p.eat(TokenKind::Comma) { p.eat(TokenKind::Semicolon); } - } else if p.at(TokenKind::Word) { + } else if p.at_member_declared_name() { p.parse_field(); if !p.eat(TokenKind::Comma) { p.eat(TokenKind::Semicolon); @@ -3815,16 +3903,9 @@ impl<'a> Parser<'a> { p.expect(TokenKind::Function); - // Accept BEP-044 keyword tokens as method names — see - // the matching block in `parse_function`. - if p.at(TokenKind::Word) - || p.at(TokenKind::Implements) - || p.at(TokenKind::Implement) - || p.at(TokenKind::Extends) - || p.at(TokenKind::Requires) - || p.at(TokenKind::Interface) - { - p.bump(); + // A keyword parses here for the same reason as in `parse_function`. + if p.at_member_name() { + p.bump_name(); } else { p.error_unexpected_token("method name".to_string()); } @@ -3992,7 +4073,7 @@ impl<'a> Parser<'a> { if p.at_top_level_keyword() && !p.at(TokenKind::Function) { break; } - if p.at(TokenKind::Function) + if (p.at(TokenKind::Function) && p.at_top_level_keyword()) || (p.at(TokenKind::AtAt) && p.item_keyword_after_leading_block_attributes() == Some(TokenKind::Function)) @@ -4008,7 +4089,7 @@ impl<'a> Parser<'a> { if !p.eat(TokenKind::Comma) { p.eat(TokenKind::Semicolon); } - } else if p.at_member_name() { + } else if p.at_member_declared_name() { p.parse_field(); if !p.eat(TokenKind::Comma) { p.eat(TokenKind::Semicolon); @@ -4029,7 +4110,7 @@ impl<'a> Parser<'a> { /// `interface_field as class_field`. fn parse_interface_field_link(&mut self) { self.with_node(SyntaxKind::INTERFACE_FIELD_LINK, |p| { - if p.at_member_name() { + if p.at(TokenKind::Word) { p.bump(); } else { p.error_unexpected_token("interface field name".to_string()); @@ -4041,7 +4122,7 @@ impl<'a> Parser<'a> { p.error_unexpected_token("`as`".to_string()); } - if p.at_member_name() { + if p.at(TokenKind::Word) { p.bump(); } else { p.error_unexpected_token("class field name".to_string()); @@ -4094,7 +4175,7 @@ impl<'a> Parser<'a> { if p.at_top_level_keyword() && !p.at(TokenKind::Function) { break; } - if p.at(TokenKind::Function) + if (p.at(TokenKind::Function) && p.at_top_level_keyword()) || (p.at(TokenKind::AtAt) && p.item_keyword_after_leading_block_attributes() == Some(TokenKind::Function)) @@ -4110,7 +4191,7 @@ impl<'a> Parser<'a> { if !p.eat(TokenKind::Comma) { p.eat(TokenKind::Semicolon); } - } else if p.at_member_name() { + } else if p.at_member_declared_name() { p.parse_field(); if !p.eat(TokenKind::Comma) { p.eat(TokenKind::Semicolon); @@ -4187,8 +4268,8 @@ impl<'a> Parser<'a> { break; } p.with_node(SyntaxKind::GENERIC_PARAM, |p| { - if p.at(TokenKind::Word) { - p.bump(); // type parameter name + if p.at_declared_name() { + p.bump_name(); } else { p.error_unexpected_token("type parameter name".to_string()); } @@ -4259,7 +4340,7 @@ impl<'a> Parser<'a> { // Field name - capture span and text before bumping let field_name_span = p.current().map(|t| t.span); let field_name_text = p.current().map(|t| t.text.clone()); - p.bump(); + p.bump_name(); let has_colon = p.eat(TokenKind::Colon); @@ -4295,17 +4376,11 @@ impl<'a> Parser<'a> { // 'function' keyword p.expect(TokenKind::Function); - // Function name. The lexer produces dedicated keyword tokens for - // BEP-044 syntax, but those words remain valid as method names — - // notably on the `TypeValue` reflection class. Accept them here. - if p.at(TokenKind::Word) - || p.at(TokenKind::Implements) - || p.at(TokenKind::Implement) - || p.at(TokenKind::Extends) - || p.at(TokenKind::Requires) - || p.at(TokenKind::Interface) - { - p.bump(); + // Function name. A keyword parses here so the name check can + // admit the stdlib's keyword-named methods (`t.implements(u)`) + // and reject every other one with a targeted diagnostic. + if p.at_member_name() { + p.bump_name(); } else { p.error_unexpected_token("function name".to_string()); // Recovery: skip until we see '(', '{', or '->' @@ -4412,12 +4487,13 @@ impl<'a> Parser<'a> { fn parse_parameter(&mut self) { self.with_node(SyntaxKind::PARAMETER, |p| { p.reject_reserved_trace_parameter(); - // Check if this is a 'self' parameter (no type annotation allowed) - let is_self = p.current().map(|t| t.text == "self").unwrap_or(false); + // The `self` receiver takes no type annotation; a `self: T` + // parameter parses like any other for the name check to reject. + let is_self = p.at(TokenKind::SelfValue) + && p.peek(1).is_none_or(|next| next.kind != TokenKind::Colon); - // Parameter name (`client` lexes as KW_CLIENT, not Word) - if p.at(TokenKind::Word) || p.at(TokenKind::Client) { - p.bump(); + if p.at_declared_name() { + p.bump_name(); } else { p.error_unexpected_token("parameter name".to_string()); } @@ -4479,8 +4555,9 @@ impl<'a> Parser<'a> { let second = self.tokens.get(second).map(|token| token.kind); match first { - Some((TokenKind::Client, _)) => second == Some(TokenKind::Colon), - Some((TokenKind::Word, "prompt" | "tools")) => second == Some(TokenKind::Colon), + Some((TokenKind::Word, "client" | "prompt" | "tools")) => { + second == Some(TokenKind::Colon) + } _ => false, } } @@ -4494,13 +4571,12 @@ impl<'a> Parser<'a> { let mut has_tools = false; while !p.at(TokenKind::RBrace) && !p.at_end() { - // Error recovery: if we see a top-level keyword other than Client, - // assume we missed a closing brace. - if p.at_top_level_keyword() && !p.at(TokenKind::Client) { + // Error recovery: a declaration here means a missed closing brace. + if p.at_top_level_keyword() { break; } - if p.at(TokenKind::Client) { + if p.at_contextual_kw("client") { if has_client { p.error_unexpected_token("Duplicate 'client' field".to_string()); } @@ -4564,7 +4640,7 @@ impl<'a> Parser<'a> { fn parse_client_field(&mut self) { self.with_node(SyntaxKind::CLIENT_FIELD, |p| { - p.expect(TokenKind::Client); + p.bump_weak_keyword(WeakDeclaration::Client); p.expect(TokenKind::Colon); // The client value is either: @@ -4696,9 +4772,8 @@ impl<'a> Parser<'a> { fn parse_lambda_parameter(&mut self) { self.with_node(SyntaxKind::PARAMETER, |p| { p.reject_reserved_trace_parameter(); - // Parameter name - if p.at(TokenKind::Word) || p.at(TokenKind::Client) { - p.bump(); + if p.at_declared_name() { + p.bump_name(); } else { p.error_unexpected_token("parameter name".to_string()); } @@ -4733,7 +4808,7 @@ impl<'a> Parser<'a> { } // Error recovery: if we see a top-level keyword, assume we missed a closing brace - if p.at_top_level_keyword_except_client() { + if p.at_top_level_keyword_outside_testset() { break; } @@ -4755,7 +4830,7 @@ impl<'a> Parser<'a> { if self.at_let_type_binding_stmt() { self.parse_type_binding_stmt(); - } else if self.at_binding_intro_stmt() { + } else if self.at_binding_intro() { self.parse_let_stmt(); } else if self.at_removed_type_binding_stmt() { self.parse_removed_type_binding_stmt(); @@ -4773,7 +4848,7 @@ impl<'a> Parser<'a> { self.parse_throw_stmt(); } else if self.at(TokenKind::Defer) { self.parse_defer_stmt(); - } else if self.at(TokenKind::Test) { + } else if self.at_weak_declaration() == Some(WeakDeclaration::Test) { if self.testset_body_depth > 0 { self.parse_test_expr(); } else { @@ -4782,7 +4857,7 @@ impl<'a> Parser<'a> { ); self.parse_test_expr(); // still parse to recover } - } else if self.at(TokenKind::TestSet) { + } else if self.at_weak_declaration() == Some(WeakDeclaration::TestSet) { if self.testset_body_depth > 0 { self.parse_testset(); } else { @@ -4809,8 +4884,8 @@ impl<'a> Parser<'a> { self.with_node(SyntaxKind::TYPE_BINDING_STMT, |p| { p.expect(TokenKind::Let); p.bump_contextual_kw_as("type", SyntaxKind::KW_TYPE); - if p.at(TokenKind::Word) { - p.bump(); + if p.at_declared_name() { + p.bump_name(); } else { p.error_unexpected_token("type binding name".to_string()); } @@ -4943,7 +5018,7 @@ impl<'a> Parser<'a> { // itself is permissive (it parses any pattern shape, including // ones with no binding), so we enforce the `let` keyword here // before delegating. The keyword is consumed inside parse_pattern. - if !p.at_binding_intro_stmt() { + if !p.at_binding_intro() { p.error_unexpected_token("'let'".to_string()); } if p.binding_intro_is_followed_by_array_pattern() { @@ -5098,7 +5173,7 @@ impl<'a> Parser<'a> { // Decided here by peeking past `if` for a `let` token — patterns // always start with `let` (BAML's binding marker), and no normal // expression starts with `let`, so this is unambiguous. - if self.peek_is_binding_intro_stmt(1) { + if self.peek_is_binding_intro(1) { self.parse_if_let_expr(); return; } @@ -5246,7 +5321,7 @@ impl<'a> Parser<'a> { } // Error recovery: if we see a top-level keyword, assume we missed a closing brace - if p.at_top_level_keyword_except_client() { + if p.at_top_level_keyword_outside_testset() { break; } let before = p.current; @@ -5397,7 +5472,7 @@ impl<'a> Parser<'a> { return; } - if self.at_binding_intro_pattern() { + if self.at_binding_intro() { self.parse_let_pattern(); return; } @@ -5428,7 +5503,7 @@ impl<'a> Parser<'a> { // In condition position (`if x is Class { … }`) we suppress this // so the `{` stays available for the outer block; users can still // write destructure via `(x is Class { f })`. - if self.at(TokenKind::Word) + if self.at_path_head() && self.suppress_destructure_pattern_depth == 0 && self.looks_like_destructure_pattern() { @@ -5589,6 +5664,11 @@ impl<'a> Parser<'a> { self.current().map(|t| t.kind), Some( TokenKind::Word + | TokenKind::SelfValue + | TokenKind::Root + | TokenKind::True + | TokenKind::False + | TokenKind::Null | TokenKind::BigintLiteral | TokenKind::IntegerLiteral | TokenKind::FloatLiteral @@ -5599,8 +5679,9 @@ impl<'a> Parser<'a> { | TokenKind::LBracket | TokenKind::Less | TokenKind::Let + | TokenKind::Const ) - ) || self.at_contextual_kw("const") + ) } /// Look ahead from the current position (which should be at `WORD`) past @@ -5621,17 +5702,7 @@ impl<'a> Parser<'a> { match self.tokens[idx].kind { TokenKind::Dot => { let next = self.skip_trivia_and_comments_from(idx + 1); - if next < self.tokens.len() - && matches!( - self.tokens[next].kind, - TokenKind::Word - | TokenKind::Spawn - | TokenKind::Await - | TokenKind::Class - | TokenKind::Enum - | TokenKind::Interface - | TokenKind::Function - ) + if next < self.tokens.len() && Self::kind_is_member_name(self.tokens[next].kind) { idx = self.skip_trivia_and_comments_from(next + 1); } else { @@ -5717,7 +5788,6 @@ impl<'a> Parser<'a> { } } TokenKind::Word - | TokenKind::Dot | TokenKind::Comma | TokenKind::Equals | TokenKind::LBracket @@ -5739,15 +5809,22 @@ impl<'a> Parser<'a> { | TokenKind::Colon | TokenKind::Arrow | TokenKind::FatArrow - // `spawn`/`await` are valid namespace segments inside type - // args (`foo>(x)`), mirroring - // the type-path parser's segment set. - | TokenKind::Spawn - | TokenKind::Await - | TokenKind::Class - | TokenKind::Enum - | TokenKind::Interface - | TokenKind::Function => {} + | TokenKind::True + | TokenKind::False + | TokenKind::Null + | TokenKind::Root => {} + // A path segment after `.` may be a keyword + // (`foo>(x)`). + TokenKind::Dot => { + let next = self.skip_trivia_and_comments_from(i + 1); + if self + .tokens + .get(next) + .is_some_and(|t| Self::kind_is_member_name(t.kind)) + { + i = next; + } + } _ => return None, } i = self.skip_trivia_and_comments_from(i + 1); @@ -5764,26 +5841,16 @@ impl<'a> Parser<'a> { let start = self.events.len(); self.bump_binding_intro(); - if !self.at(TokenKind::Word) { - self.error_unexpected_token("identifier after 'let'".to_string()); - self.wrap_events_in_node(start, SyntaxKind::BINDING_PATTERN); - self.finish_node(); - return; - } - - // `let _` is a wildcard, not a binding to a name called `_`. - if self.at_wildcard_word() { - self.bump(); // _ - self.wrap_events_in_node(start, SyntaxKind::WILDCARD_PATTERN); - self.finish_node(); - return; - } - // Decide between simple binding (`let x`) and destructure - // (`let Class { ... }`) by peeking past dotted segments for `{`. + // (`let Class { ... }`, `let root.ns.Class { ... }`) by peeking past + // dotted segments for `{`. `let _` is a wildcard even before `{`. // Same condition-position suppression as `parse_pattern_atom` — // `if x is let Class { f }` would otherwise eat the then-block. - if self.suppress_destructure_pattern_depth == 0 && self.looks_like_destructure_pattern() { + if self.at_path_head() + && !self.at_wildcard_word() + && self.suppress_destructure_pattern_depth == 0 + && self.looks_like_destructure_pattern() + { self.parse_path(); if self.at(TokenKind::Less) && self.looks_like_generic_args() { self.parse_type_args(); @@ -5794,7 +5861,21 @@ impl<'a> Parser<'a> { return; } - self.bump(); // WORD + if !self.at_declared_name() { + self.error_unexpected_token("identifier after 'let'".to_string()); + self.wrap_events_in_node(start, SyntaxKind::BINDING_PATTERN); + self.finish_node(); + return; + } + + if self.at_wildcard_word() { + self.bump(); // _ + self.wrap_events_in_node(start, SyntaxKind::WILDCARD_PATTERN); + self.finish_node(); + return; + } + + self.bump_name(); // Optional `: ` sub-pattern. Folded directly into the // BINDING_PATTERN node. The right side is any pattern — type // ascription (`let x: int`), aliasing chain (`let x: let y`), @@ -5834,27 +5915,18 @@ impl<'a> Parser<'a> { /// Parse a dotted path: `WORD ('.' WORD)*`. Tokens are emitted into the /// current node — no wrapper node is added. fn parse_path(&mut self) { - if !self.at(TokenKind::Word) { + if !self.at_path_head() { self.error_unexpected_token("identifier".to_string()); return; } - self.bump(); // first WORD + self.bump_name(); while self.at(TokenKind::Dot) - && self.peek(1).is_some_and(|t| { - matches!( - t.kind, - TokenKind::Word - | TokenKind::Spawn - | TokenKind::Await - | TokenKind::Class - | TokenKind::Enum - | TokenKind::Interface - | TokenKind::Function - ) - }) + && self + .peek(1) + .is_some_and(|t| Self::kind_is_member_name(t.kind)) { self.bump(); // . - self.bump(); // WORD + self.bump_name(); } } @@ -5865,7 +5937,7 @@ impl<'a> Parser<'a> { } while !self.at(TokenKind::RBrace) && !self.at_end() { - if self.at_top_level_keyword_except_client() { + if self.at_top_level_keyword_outside_testset() { break; } self.parse_field_pattern(); @@ -5964,10 +6036,7 @@ impl<'a> Parser<'a> { fn at_catch_clause_start(&self) -> bool { self.at(TokenKind::Catch) || self.at(TokenKind::CatchAll) - // `catch_all_panics` is a contextual keyword: the lexer treats it as - // a plain identifier so it stays usable as one elsewhere, but in - // catch-clause position it introduces a clause like `catch_all`. - || self.at_contextual_kw("catch_all_panics") + || self.at(TokenKind::CatchAllPanics) } fn parse_catch_expr(&mut self, expr_start: usize) { @@ -5981,12 +6050,8 @@ impl<'a> Parser<'a> { fn parse_catch_clause(&mut self) { self.with_node(SyntaxKind::CATCH_CLAUSE, |p| { - if p.at(TokenKind::Catch) || p.at(TokenKind::CatchAll) { + if p.at_catch_clause_start() { p.bump(); - } else if p.at_contextual_kw("catch_all_panics") { - // Re-label the contextual `Word` as a dedicated keyword so the - // AST lowering and tooling can recognize the clause kind. - p.bump_contextual_kw_as("catch_all_panics", SyntaxKind::KW_CATCH_ALL_PANICS); } else { p.error_unexpected_token("catch clause keyword".to_string()); return; @@ -5998,8 +6063,8 @@ impl<'a> Parser<'a> { // The catch binding is always a bare identifier (like a function // parameter), not a full pattern. p.with_node(SyntaxKind::CATCH_BINDING, |p| { - if p.at(TokenKind::Word) { - p.bump(); + if p.at_declared_name() { + p.bump_name(); } else { p.error_unexpected_token("catch binding identifier".to_string()); } @@ -6031,7 +6096,7 @@ impl<'a> Parser<'a> { break; } - if p.at_top_level_keyword_except_client() { + if p.at_top_level_keyword_outside_testset() { break; } let before = p.current; @@ -6152,7 +6217,7 @@ impl<'a> Parser<'a> { // always start with `let` (BAML's binding marker), and no normal // expression starts with `let`, so this is unambiguous. Mirrors // `parse_if_expr`'s `if` vs `if let` decision. - if self.peek_is_binding_intro_stmt(1) { + if self.peek_is_binding_intro(1) { self.parse_while_let_stmt(); return; } @@ -6229,7 +6294,7 @@ impl<'a> Parser<'a> { p.bump(); // ( // Check if this is iterator-style: for (let var in expr) or C-style: for (init; cond; update) - if p.at_binding_intro_stmt() { + if p.at_binding_intro() { // Peek ahead to check if this is iterator-style (has 'in' keyword) // For iterator-style: for (let i in expr) / for (const i in expr) // For C-style: for (let i = 0; ...) / for (const i = 0; ...) @@ -6275,7 +6340,7 @@ impl<'a> Parser<'a> { } p.expect(TokenKind::RParen); - } else if p.at_binding_intro_stmt() && p.looks_like_for_in_loop() { + } else if p.at_binding_intro() && p.looks_like_for_in_loop() { // Non-parenthesized form: `for let in { }`. p.parse_for_in_pattern(); p.expect(TokenKind::In); @@ -6291,7 +6356,7 @@ impl<'a> Parser<'a> { // `for ;;i += x {}` is reinterpreted as an object constructor. p.expect(TokenKind::LParen); p.suppress_object_literal_depth += 1; - if p.at_binding_intro_stmt() { + if p.at_binding_intro() { p.parse_let_stmt(); p.parse_c_style_for_tail(); } else { @@ -6435,7 +6500,7 @@ impl<'a> Parser<'a> { self.with_node(SyntaxKind::LET_STMT, |p| { // For-in pattern shares the let-statement shape: must start with // `let`/`const`. No initializer. - if !p.at_binding_intro_stmt() { + if !p.at_binding_intro() { p.error_unexpected_token("'let'".to_string()); } if p.binding_intro_is_followed_by_array_pattern() { @@ -6604,7 +6669,7 @@ impl<'a> Parser<'a> { self.wrap_events_in_node(lhs_start, SyntaxKind::OPTIONAL_FIELD_ACCESS_EXPR); self.bump(); // ?. if self.at_member_name() { - self.bump(); + self.bump_name(); } else { self.error_unexpected_token( "field name, `[`, or `(` after `?.`".to_string(), @@ -6655,7 +6720,7 @@ impl<'a> Parser<'a> { self.wrap_events_in_node(lhs_start, SyntaxKind::FIELD_ACCESS_EXPR); self.bump(); // . or $ if self.at_member_name() { - self.bump(); + self.bump_name(); } else { let punct = if op == TokenKind::Dollar { "'$'" @@ -6841,12 +6906,10 @@ impl<'a> Parser<'a> { return *kind == SyntaxKind::PATH_EXPR; } } - Event::Token { kind, text, .. } => { + Event::Token { kind, .. } => { if depth == 0 { - // Only WORD tokens can be type names, and literals - // like null/true/false are never constructors. - return *kind == SyntaxKind::WORD - && !matches!(text.as_str(), "null" | "true" | "false"); + // Only WORD tokens can be type names. + return *kind == SyntaxKind::WORD; } } Event::UnexpectedToken { .. } @@ -6869,9 +6932,7 @@ impl<'a> Parser<'a> { match self.peek(1) { Some(t) if t.kind == TokenKind::DotDotDot => true, // spread Some(t) if t.kind == TokenKind::RBrace => true, // empty braces - // `client` is a keyword but a valid field name (BEP-049 §10 - // `Context { client: ... }`), so an object literal can begin with it. - Some(t) if t.kind == TokenKind::Word || t.kind == TokenKind::Client => { + Some(t) if t.kind == TokenKind::Word => { let mut i = 2; while self.peek(i).map(|t| t.kind) == Some(TokenKind::Dot) && self.peek(i + 1).map(|t| t.kind) == Some(TokenKind::Word) @@ -7078,18 +7139,14 @@ impl<'a> Parser<'a> { { // env.FIELD sugar (not followed by `(`) — desugar to baml.env.get_or_panic("FIELD") self.parse_env_access(); - } else if text == "true" { - self.bump_contextual_kw_as("true", SyntaxKind::KW_TRUE); - } else if text == "false" { - self.bump_contextual_kw_as("false", SyntaxKind::KW_FALSE); - } else if text == "null" { - self.bump_contextual_kw_as("null", SyntaxKind::KW_NULL); } else { // Identifier or path (could be multi-segment like baml.HttpMethod.Get) self.parse_path_or_ident(); } - } else if self.at(TokenKind::Client) { - // `client` is KW_CLIENT; allow as identifier (e.g. parameter named `client`, `client.execute(...)`) + } else if self.at(TokenKind::True) || self.at(TokenKind::False) || self.at(TokenKind::Null) + { + self.bump(); + } else if self.at_path_head() { self.parse_path_or_ident(); } else if self.at(TokenKind::LParen) && self.looks_like_lambda() { // Lambda expression: (params) -> [RetType] { body } @@ -7181,9 +7238,7 @@ impl<'a> Parser<'a> { fn parse_call_arg(&mut self) { self.with_node(SyntaxKind::CALL_ARG, |p| { - if (p.at(TokenKind::Word) || p.at(TokenKind::Client)) - && p.peek(1).map(|t| t.kind) == Some(TokenKind::Equals) - { + if p.at(TokenKind::Word) && p.peek(1).map(|t| t.kind) == Some(TokenKind::Equals) { p.bump(); p.expect(TokenKind::Equals); } @@ -7310,7 +7365,6 @@ impl<'a> Parser<'a> { // - `LParen` / `RParen` for parenthesized union types // (`(int | string)`) TokenKind::Word - | TokenKind::Dot | TokenKind::Comma | TokenKind::Equals | TokenKind::LBracket @@ -7330,15 +7384,20 @@ impl<'a> Parser<'a> { | TokenKind::Colon | TokenKind::Arrow | TokenKind::FatArrow - // `spawn`/`await` are valid namespace segments inside type - // args (`foo>(x)`), mirroring - // the type-path parser's segment set. - | TokenKind::Spawn - | TokenKind::Await - | TokenKind::Class - | TokenKind::Enum - | TokenKind::Interface - | TokenKind::Function => {} + | TokenKind::True + | TokenKind::False + | TokenKind::Null + | TokenKind::Root => {} + // A path segment after `.` may be a keyword + // (`foo>(x)`). + TokenKind::Dot => { + if self + .peek(i + 1) + .is_some_and(|t| Self::kind_is_member_name(t.kind)) + { + i += 1; + } + } // Anything else — operators, braces, EOF-ish tokens — can't // appear in a type, so this `<` is a comparison. _ => return false, @@ -7400,9 +7459,9 @@ impl<'a> Parser<'a> { /// unary/prefix starts of `parse_prefix`. fn is_start_of_expression(kind: TokenKind) -> bool { use TokenKind::{ - Await, BigintLiteral, Client, FloatLiteral, Hash, If, IntegerLiteral, LBrace, LBracket, - LParen, Less, Match, Minus, MinusMinus, Not, PlusPlus, Quote, Spawn, Throw, Tilde, - Word, + Await, BigintLiteral, False, FloatLiteral, Hash, If, IntegerLiteral, LBrace, LBracket, + LParen, Less, Match, Minus, MinusMinus, Not, Null, PlusPlus, Quote, Root, SelfValue, + Spawn, Throw, Tilde, True, Word, }; matches!( kind, @@ -7413,7 +7472,11 @@ impl<'a> Parser<'a> { | Quote // string literal | Hash // removed hash string recovery | Word - | Client + | SelfValue + | Root + | True + | False + | Null | LParen | LBracket | LBrace @@ -7840,51 +7903,25 @@ impl<'a> Parser<'a> { /// This distinction is made at parse time because we can determine syntactically /// whether the base is a simple identifier chain or a complex expression. fn parse_path_or_ident(&mut self) { - if !self.at(TokenKind::Word) && !self.at(TokenKind::Client) { + if !self.at_path_head() { return; } - // `spawn` / `await` are reserved keywords but are valid as path - // segments after a `.` (e.g. the `baml.spawn` namespace). They are - // unambiguous in segment position — a leading `spawn`/`await` is - // handled before this point. `is_ident_token` in - // `baml_compiler2_ast::lower_expr_body` must mirror this set. - let path_start = |k: TokenKind| { - matches!( - k, - TokenKind::Word - | TokenKind::Client - | TokenKind::Spawn - | TokenKind::Await - | TokenKind::Class - | TokenKind::Enum - | TokenKind::Interface - | TokenKind::Function - ) - }; - // Once a path has started, any `.member` name continues it, so a member - // named by a keyword stays reachable deep in a path - // (`reflect.Class.extends`). Right after the head (`t.implements`) it - // is not a path start, and parses as member access instead. - let segment = |k: TokenKind| path_start(k) || Self::kind_is_member_name(k); - - // Check if this looks like a path (ident.client followed by dot and another ident) if self.peek(1).map(|t| t.kind) == Some(TokenKind::Dot) - && self.peek(2).map(|t| path_start(t.kind)).unwrap_or(false) + && self + .peek(2) + .is_some_and(|t| Self::kind_is_member_name(t.kind)) && !(self .peek(2) .is_some_and(|t| t.kind == TokenKind::Word && t.text == "as") && self.peek(3).map(|t| t.kind) == Some(TokenKind::Less)) { - // It's a path - all segments are identifiers self.with_node(SyntaxKind::PATH_EXPR, |p| { - p.bump(); // First segment - - // Parse remaining segments + p.bump_name(); while p.at(TokenKind::Dot) && !p.looks_like_as_projection() { p.bump(); - if p.current().map(|t| segment(t.kind)).unwrap_or(false) { - p.bump(); // Next segment + if p.at_member_name() { + p.bump_name(); } else { p.error_unexpected_token("path segment after '.'".to_string()); break; @@ -7892,8 +7929,7 @@ impl<'a> Parser<'a> { } }); } else { - // Simple identifier (no dots) - self.bump(); + self.bump_name(); } } @@ -7902,7 +7938,11 @@ impl<'a> Parser<'a> { matches!( token.kind, TokenKind::Word - | TokenKind::Client + | TokenKind::SelfValue + | TokenKind::Root + | TokenKind::True + | TokenKind::False + | TokenKind::Null | TokenKind::Quote | TokenKind::Backtick | TokenKind::Hash @@ -7981,10 +8021,7 @@ impl<'a> Parser<'a> { } } } - // Check for valid field start (`client` is a keyword but a valid - // field name — BEP-049 §10 `Context { client: ... }`). } else if self.at(TokenKind::Word) - || self.at(TokenKind::Client) || self.at(TokenKind::Quote) || self.at(TokenKind::Hash) { @@ -8036,10 +8073,8 @@ impl<'a> Parser<'a> { fn parse_object_field(&mut self) { self.with_node(SyntaxKind::OBJECT_FIELD, |p| { // Field name - can be identifier, qualified identifier, or string literal. - // `client` is a keyword but a valid field name (BEP-049 §10 - // `Context { client: ... }`). let mut shorthand_candidate = false; - if p.at(TokenKind::Word) || p.at(TokenKind::Client) { + if p.at(TokenKind::Word) { shorthand_candidate = true; p.bump(); // identifier field name while p.at(TokenKind::Dot) { @@ -8077,9 +8112,9 @@ impl<'a> Parser<'a> { fn infix_binding_power(op: TokenKind) -> Option<(u8, u8)> { use TokenKind::{ And, AndAnd, AndEquals, Caret, CaretEquals, Equals, EqualsEquals, Greater, - GreaterEquals, GreaterGreater, GreaterGreaterEquals, Instanceof, Less, LessEquals, - LessLess, LessLessEquals, Minus, MinusEquals, NotEquals, OrOr, Percent, PercentEquals, - Pipe, PipeEquals, Plus, PlusEquals, Slash, SlashEquals, Star, StarEquals, + GreaterEquals, GreaterGreater, GreaterGreaterEquals, Less, LessEquals, LessLess, + LessLessEquals, Minus, MinusEquals, NotEquals, OrOr, Percent, PercentEquals, Pipe, + PipeEquals, Plus, PlusEquals, Slash, SlashEquals, Star, StarEquals, }; Some(match op { @@ -8107,8 +8142,8 @@ impl<'a> Parser<'a> { // Equality (left associative) EqualsEquals | NotEquals => (16, 17), - // Comparison (left associative) - includes instanceof - Less | Greater | LessEquals | GreaterEquals | Instanceof => (18, 19), + // Comparison (left associative) + Less | Greater | LessEquals | GreaterEquals => (18, 19), // Bitwise shift (left associative) LessLess | GreaterGreater => (20, 21), @@ -8136,7 +8171,7 @@ impl<'a> Parser<'a> { && self.peek(2).map(|t| t.kind) == Some(TokenKind::Equals) { self.with_node(SyntaxKind::CLIENT_VALUE_DEF, |p| { - p.expect(TokenKind::Client); + p.bump_weak_keyword(WeakDeclaration::Client); p.bump(); // name p.bump(); // = p.parse_expr(); @@ -8145,8 +8180,7 @@ impl<'a> Parser<'a> { return; } self.with_node(SyntaxKind::CLIENT_DEF, |p| { - // 'client' keyword - p.expect(TokenKind::Client); + p.bump_weak_keyword(WeakDeclaration::Client); // Optional client type: if p.at(TokenKind::Less) { @@ -8208,7 +8242,7 @@ impl<'a> Parser<'a> { // - Enum: `enum ["celsius", "fahrenheit"]` inside nested option maps // - Class: `class "MyClass"` inside nested option maps if p.at_top_level_keyword() - && !p.at(TokenKind::RetryPolicy) + && !p.at_contextual_kw("retry_policy") && !p.at(TokenKind::Enum) && !p.at(TokenKind::Class) { @@ -8248,20 +8282,11 @@ impl<'a> Parser<'a> { // config keys (e.g., `enum ["celsius", "fahrenheit"]`). self.with_node(SyntaxKind::CONFIG_ITEM, |p| { - // Config key: identifier, keyword-as-identifier, quoted string, or - // removed hash string parsed for a targeted diagnostic - // Note: Some top-level keywords are also valid as config keys: - // - RetryPolicy: `retry_policy MyPolicy` inside client blocks - // - Enum: `enum ["celsius", "fahrenheit"]` inside nested option maps - // - Class: `class "MyClass"` inside nested option maps - // We explicitly allow them here so they parse as config items rather than - // triggering error recovery that would break out of the config block. - if p.at(TokenKind::Word) - || p.at(TokenKind::RetryPolicy) - || p.at(TokenKind::Enum) - || p.at(TokenKind::Class) - { - p.bump(); + // Config key: a name (keywords included, e.g. `enum [...]` in + // nested option maps), quoted string, or removed hash string + // parsed for a targeted diagnostic. + if p.at_member_name() { + p.bump_name(); } else if p.at(TokenKind::Quote) || p.at(TokenKind::Hash) { // Quoted string or removed hash string key if !p.parse_any_string() { @@ -8397,17 +8422,7 @@ impl<'a> Parser<'a> { } } - // Boolean literals - if self.at(TokenKind::Word) { - if let Some(token) = self.current() { - let text = token.text.as_str(); - if text == "true" || text == "false" { - return true; - } - } - } - - false + self.at(TokenKind::True) || self.at(TokenKind::False) } /// Parse an array in config context - uses config-style parsing for nested objects @@ -8466,7 +8481,7 @@ impl<'a> Parser<'a> { pub(crate) fn parse_test_expr(&mut self) { self.with_node(SyntaxKind::TEST_EXPR_DEF, |p| { // 'test' keyword - p.expect(TokenKind::Test); + p.bump_weak_keyword(WeakDeclaration::Test); // Same rule as `parse_testset`: a bare identifier as a // top-level test name can never resolve — report the syntax @@ -8507,7 +8522,7 @@ impl<'a> Parser<'a> { pub(crate) fn parse_testset(&mut self) { self.with_node(SyntaxKind::TESTSET_DEF, |p| { // 'testset' keyword - p.expect(TokenKind::TestSet); + p.bump_weak_keyword(WeakDeclaration::TestSet); // A bare identifier as a top-level testset name can never // resolve (there are no local bindings at top level), and the @@ -8560,7 +8575,7 @@ impl<'a> Parser<'a> { pub(crate) fn parse_retry_policy(&mut self) { self.with_node(SyntaxKind::RETRY_POLICY_DEF, |p| { // 'retry_policy' keyword - p.expect(TokenKind::RetryPolicy); + p.bump_weak_keyword(WeakDeclaration::RetryPolicy); // Policy name if p.at(TokenKind::Word) { @@ -8588,8 +8603,7 @@ impl<'a> Parser<'a> { /// `GENERATOR_DEF` node (see `lower_cst`). pub(crate) fn parse_generator(&mut self) { self.with_node(SyntaxKind::GENERATOR_DEF, |p| { - // 'generator' keyword - p.expect(TokenKind::Generator); + p.bump_weak_keyword(WeakDeclaration::Generator); // Optional generator name — kept as the first WORD child so // lowering can name it in the diagnostic. @@ -8626,7 +8640,7 @@ impl<'a> Parser<'a> { /// Parse a template string declaration pub(crate) fn parse_template_string(&mut self) { self.with_node(SyntaxKind::TEMPLATE_STRING_DEF, |p| { - p.expect(TokenKind::TemplateString); + p.bump_weak_keyword(WeakDeclaration::TemplateString); // Template name if p.at(TokenKind::Word) { @@ -8665,9 +8679,8 @@ impl<'a> Parser<'a> { p.error_unexpected_token("'type' keyword".to_string()); } - // Type alias name - if p.at(TokenKind::Word) { - p.bump(); + if p.at_declared_name() { + p.bump_name(); } else { p.error_unexpected_token("type alias name".to_string()); } @@ -8715,20 +8728,19 @@ fn parse_impl(tokens: &[Token], cache: Option<&mut NodeCache>) -> (GreenNode, Ve parser.parse_function(); } else if parser.at(TokenKind::Implements) || parser.at(TokenKind::Implement) { parser.parse_implements_for(); - } else if parser.at(TokenKind::Client) { - parser.parse_client(); - } else if parser.at(TokenKind::Generator) { - parser.parse_generator(); - } else if parser.at(TokenKind::Test) { - if !parser.try_recover_removed_legacy_test() { - parser.parse_test_expr(); - } - } else if parser.at(TokenKind::TestSet) { - parser.parse_testset(); - } else if parser.at(TokenKind::RetryPolicy) { - parser.parse_retry_policy(); - } else if parser.at(TokenKind::TemplateString) { - parser.parse_template_string(); + } else if let Some(declaration) = parser.at_weak_declaration() { + match declaration { + WeakDeclaration::Client => parser.parse_client(), + WeakDeclaration::Generator => parser.parse_generator(), + WeakDeclaration::Test => { + if !parser.try_recover_removed_legacy_test() { + parser.parse_test_expr(); + } + } + WeakDeclaration::TestSet => parser.parse_testset(), + WeakDeclaration::RetryPolicy => parser.parse_retry_policy(), + WeakDeclaration::TemplateString => parser.parse_template_string(), + } } else if parser.at(TokenKind::Word) && parser.current().map(|t| t.text == "type").unwrap_or(false) { @@ -8736,7 +8748,7 @@ fn parse_impl(tokens: &[Token], cache: Option<&mut NodeCache>) -> (GreenNode, Ve } else if parser.at_let_type_binding_stmt() { // Only a body can bind a runtime type; lowering reports it here. parser.parse_type_binding_stmt(); - } else if parser.at_binding_intro_stmt() { + } else if parser.at_binding_intro() { parser.parse_let_stmt(); } else if parser.at_header_comment_start() { parser.consume_header_comment(); @@ -10399,7 +10411,7 @@ client Foo { #[test] fn parses_function_with_client_as_parameter_name() { - // `client` is a keyword (KW_CLIENT); it must still be valid as a parameter name. + // `client` is a weak keyword: an ordinary parameter name. let source = r#" function call_llm_function(client: Client, function_name: string) -> unknown { let _ = client @@ -10455,9 +10467,9 @@ function f() -> int { #[test] fn expression_body_calling_a_client_param_is_not_llm_body() { - // `client` lexes as KW_CLIENT, so a call THROUGH a parameter named - // `client` looks like the start of an LLM directive unless `(` is - // excluded — the body would then be parsed as an LLM block and fail. + // A call THROUGH a parameter named `client` looks like the start of an + // LLM directive unless `(` is excluded — the body would then be + // parsed as an LLM block and fail. let source = r#" function f(client: (int) -> int) -> int { client(1) @@ -10755,7 +10767,7 @@ function Foo() -> { } /// The LLM-vs-expression classifier walks raw tokens, where string - /// CONTENTS lex as ordinary tokens (`client` even lexes as `KW_CLIENT`). + /// CONTENTS lex as ordinary tokens. /// Prose that happens to mention `client`/`prompt`/`tools` must never /// flip a plain function into an LLM body — that produced a cascade of /// "Only 'client', 'tools' and 'prompt' allowed in LLM function" errors. @@ -11036,9 +11048,8 @@ function Demo() -> int { assert_eq!(child_kinds[1], SyntaxKind::CATCH_CLAUSE); } - /// `catch_all_panics` is a contextual keyword: the lexer emits a plain - /// `Word`, and the parser must still recognize it in catch-clause position - /// (B-504 — it previously only handled `catch`/`catch_all`). + /// `catch_all_panics` introduces a catch clause like `catch`/`catch_all` + /// (B-504). #[test] fn parses_catch_all_panics_clause() { let source = r#" @@ -11071,21 +11082,20 @@ function Demo() -> int { assert!(has_kw, "expected a KW_CATCH_ALL_PANICS token in the clause"); } - /// `catch_all_panics` is contextual, not reserved: outside catch-clause - /// position it is still a perfectly good identifier (e.g. a function name). #[test] - fn catch_all_panics_is_a_normal_identifier_outside_catch() { + fn catch_all_panics_is_a_keyword() { let source = r#" -function catch_all_panics() -> int { 1 } - function Demo() -> int { - let x = catch_all_panics() - x + let catch_all_panics = 1 + catch_all_panics } "#; let (_root, errors) = parse_source(source); - assert_no_errors(&errors); + assert!( + !errors.is_empty(), + "`catch_all_panics` must not bind a name" + ); } #[test] diff --git a/baml_language/crates/baml_compiler_syntax/src/ast.rs b/baml_language/crates/baml_compiler_syntax/src/ast.rs index 53c31b511a6..e450b2ce8b4 100644 --- a/baml_language/crates/baml_compiler_syntax/src/ast.rs +++ b/baml_language/crates/baml_compiler_syntax/src/ast.rs @@ -29,16 +29,7 @@ fn extract_dotted_name<'a>(tokens: impl Iterator) -> Opt break; } let Some(word) = iter.next() else { break }; - if !matches!( - word.kind(), - SyntaxKind::WORD - | SyntaxKind::KW_SPAWN - | SyntaxKind::KW_AWAIT - | SyntaxKind::KW_CLASS - | SyntaxKind::KW_ENUM - | SyntaxKind::KW_INTERFACE - | SyntaxKind::KW_FUNCTION - ) { + if word.kind() != SyntaxKind::WORD { break; } parts.push(word.text().to_string()); @@ -183,7 +174,7 @@ impl CallArg { .children_with_tokens() .filter(|element| !element.kind().is_trivia()); let first = elements.next()?.into_token()?; - if !matches!(first.kind(), SyntaxKind::WORD | SyntaxKind::KW_CLIENT) { + if first.kind() != SyntaxKind::WORD { return None; } (elements.next()?.kind() == SyntaxKind::EQUALS).then_some(first) @@ -200,7 +191,7 @@ impl ObjectField { .children_with_tokens() .find(|element| !element.kind().is_trivia()) .and_then(rowan::NodeOrToken::into_token) - .filter(|token| matches!(token.kind(), SyntaxKind::WORD | SyntaxKind::KW_CLIENT)) + .filter(|token| token.kind() == SyntaxKind::WORD) } } @@ -253,19 +244,7 @@ impl UnionMemberParts { let name: Vec<_> = self .tokens .iter() - .take_while(|t| { - matches!( - t.kind(), - SyntaxKind::WORD - | SyntaxKind::DOT - | SyntaxKind::KW_SPAWN - | SyntaxKind::KW_AWAIT - | SyntaxKind::KW_CLASS - | SyntaxKind::KW_ENUM - | SyntaxKind::KW_INTERFACE - | SyntaxKind::KW_FUNCTION - ) - }) + .take_while(|t| matches!(t.kind(), SyntaxKind::WORD | SyntaxKind::DOT)) .collect(); if let (Some(first), Some(last)) = (name.first(), name.last()) { return Some(rowan::TextRange::new( @@ -808,7 +787,7 @@ impl FunctionTypeParam { self.syntax .children_with_tokens() .filter_map(rowan::NodeOrToken::into_token) - .find(|t| t.kind() == SyntaxKind::WORD || t.kind() == SyntaxKind::KW_CLIENT) + .find(|t| t.kind() == SyntaxKind::WORD) .map(|t| t.text().to_string()) } else { None @@ -826,7 +805,7 @@ impl FunctionTypeParam { matches!( (tokens.next(), tokens.next(), tokens.next()), (Some(first), Some(second), Some(third)) - if (first.kind() == SyntaxKind::WORD || first.kind() == SyntaxKind::KW_CLIENT) + if first.kind() == SyntaxKind::WORD && second.kind() == SyntaxKind::QUESTION && third.kind() == SyntaxKind::COLON ) @@ -921,14 +900,7 @@ impl FunctionDef { .children_with_tokens() .filter_map(rowan::NodeOrToken::into_token) .filter(|token| { - let kind = token.kind(); - let is_name = kind == SyntaxKind::WORD - || kind == SyntaxKind::KW_IMPLEMENTS - || kind == SyntaxKind::KW_IMPLEMENT - || kind == SyntaxKind::KW_EXTENDS - || kind == SyntaxKind::KW_REQUIRES - || kind == SyntaxKind::KW_INTERFACE; - is_name && token.parent() == Some(self.syntax.clone()) + token.kind() == SyntaxKind::WORD && token.parent() == Some(self.syntax.clone()) }) .nth(0) } @@ -1802,10 +1774,7 @@ impl Parameter { self.syntax .children_with_tokens() .filter_map(rowan::NodeOrToken::into_token) - .find(|token| { - // `client` is KW_CLIENT in the CST, not WORD. - token.kind() == SyntaxKind::WORD || token.kind() == SyntaxKind::KW_CLIENT - }) + .find(|token| token.kind() == SyntaxKind::WORD) } /// Get the parameter type. @@ -1846,7 +1815,7 @@ impl CallArg { .collect(); if tokens.len() >= 2 - && (tokens[0].kind() == SyntaxKind::WORD || tokens[0].kind() == SyntaxKind::KW_CLIENT) + && tokens[0].kind() == SyntaxKind::WORD && tokens[1].kind() == SyntaxKind::EQUALS { Some(tokens[0].clone()) @@ -1971,29 +1940,13 @@ impl ImplementsBlock { } } -fn is_member_name_token(kind: SyntaxKind) -> bool { - matches!( - kind, - SyntaxKind::WORD - | SyntaxKind::KW_IMPLEMENTS - | SyntaxKind::KW_IMPLEMENT - | SyntaxKind::KW_EXTENDS - | SyntaxKind::KW_REQUIRES - | SyntaxKind::KW_INTERFACE - ) -} - impl InterfaceFieldLink { /// The interface field on the left side of `field as class_field`. pub fn interface_field(&self) -> Option { self.syntax .children_with_tokens() .filter_map(rowan::NodeOrToken::into_token) - .find(|token| { - !token.kind().is_trivia() - && is_member_name_token(token.kind()) - && token.kind() != SyntaxKind::KW_AS - }) + .find(|token| token.kind() == SyntaxKind::WORD) } /// The class field on the right side of `field as class_field`. @@ -2009,7 +1962,7 @@ impl InterfaceFieldLink { after_as = true; continue; } - if after_as && is_member_name_token(token.kind()) { + if after_as && token.kind() == SyntaxKind::WORD { return Some(token); } } @@ -2122,15 +2075,7 @@ impl MethodSig { self.syntax .children_with_tokens() .filter_map(rowan::NodeOrToken::into_token) - .find(|token| { - let kind = token.kind(); - kind == SyntaxKind::WORD - || kind == SyntaxKind::KW_IMPLEMENTS - || kind == SyntaxKind::KW_IMPLEMENT - || kind == SyntaxKind::KW_EXTENDS - || kind == SyntaxKind::KW_REQUIRES - || kind == SyntaxKind::KW_INTERFACE - }) + .find(|token| token.kind() == SyntaxKind::WORD) } pub fn param_list(&self) -> Option { @@ -2154,20 +2099,7 @@ impl Field { self.syntax .children_with_tokens() .filter_map(rowan::NodeOrToken::into_token) - .find(|token| { - matches!( - token.kind(), - SyntaxKind::WORD - | SyntaxKind::KW_IMPLEMENTS - | SyntaxKind::KW_IMPLEMENT - | SyntaxKind::KW_EXTENDS - | SyntaxKind::KW_REQUIRES - | SyntaxKind::KW_INTERFACE - // `client` keyword stays valid as a field name - // (BEP-049 §10 `ctx.client` on `Context`). - | SyntaxKind::KW_CLIENT - ) - }) + .find(|token| token.kind() == SyntaxKind::WORD) } /// Get the field type. @@ -2223,7 +2155,7 @@ impl ClientDef { .filter(|token| { token.kind() == SyntaxKind::WORD && token.parent() == Some(self.syntax.clone()) }) - .nth(0) // Get the first WORD (client keyword is KW_CLIENT, not WORD) + .nth(0) } /// Get the config block. @@ -2258,15 +2190,12 @@ impl ConfigBlock { } impl ConfigItem { - /// Get the config item key (first WORD or keyword token). - /// - /// Config items can have keyword tokens as keys (e.g., `retry_policy` inside - /// a client block is lexed as `KW_RETRY_POLICY`, not `WORD`). + /// Get the config item key (the first WORD token). pub fn key(&self) -> Option { self.syntax .children_with_tokens() .filter_map(rowan::NodeOrToken::into_token) - .find(|token| matches!(token.kind(), SyntaxKind::WORD | SyntaxKind::KW_RETRY_POLICY)) + .find(|token| token.kind() == SyntaxKind::WORD) } /// Get the config item value (WORD token inside `CONFIG_VALUE`, if present). @@ -2633,7 +2562,6 @@ impl BlockExpr { | SyntaxKind::FLOAT_LITERAL | SyntaxKind::STRING_LITERAL | SyntaxKind::RAW_STRING_LITERAL - // Boolean / null literals are re-lexed contextual keywords. | SyntaxKind::KW_TRUE | SyntaxKind::KW_FALSE | SyntaxKind::KW_NULL => Some(BlockElement::ExprToken(t)), diff --git a/baml_language/crates/baml_compiler_syntax/src/syntax_kind.rs b/baml_language/crates/baml_compiler_syntax/src/syntax_kind.rs index a5df2340b4b..70a8a2db6cd 100644 --- a/baml_language/crates/baml_compiler_syntax/src/syntax_kind.rs +++ b/baml_language/crates/baml_compiler_syntax/src/syntax_kind.rs @@ -17,6 +17,7 @@ pub enum SyntaxKind { KW_EXTENDS, KW_REQUIRES, KW_FUNCTION, + // Weak declaration keywords, re-labelled from a `Word` at parse time. KW_CLIENT, KW_GENERATOR, KW_TEST, @@ -46,15 +47,14 @@ pub enum SyntaxKind { KW_DEFER, // Other keywords - KW_INSTANCEOF, KW_IS, + KW_TRUE, + KW_FALSE, + KW_NULL, + // Weak keywords re-labelled from a `Word` at parse time (no lexer token). KW_WITH, - // Contextual keywords re-lexed from a `Word` at parse time (no lexer token). - KW_AS, // `.as` cast / `(T as I)` / `field as field` - KW_TYPE, // associated-type / type-alias `type Name ...` - KW_TRUE, // `true` boolean literal - KW_FALSE, // `false` boolean literal - KW_NULL, // `null` literal + KW_AS, // `.as` cast / `(T as I)` / `field as field` + KW_TYPE, // associated-type / type-alias `type Name ...` // Literals WORD, // Any word (non-keyword identifier) @@ -568,7 +568,6 @@ impl SyntaxKind { | Self::KW_SPAWN | Self::KW_AWAIT | Self::KW_DEFER - | Self::KW_INSTANCEOF | Self::KW_WITH | Self::KW_AS | Self::KW_TYPE diff --git a/baml_language/crates/baml_fmt/src/ast/declarations.rs b/baml_language/crates/baml_fmt/src/ast/declarations.rs index 8d3db33ed48..cf7d2a67c37 100644 --- a/baml_language/crates/baml_fmt/src/ast/declarations.rs +++ b/baml_language/crates/baml_fmt/src/ast/declarations.rs @@ -2698,27 +2698,17 @@ pub enum ConfigItemKey { String(t::QuotedString), // parser allows raw strings as keys, but that's not a good idea // RawString(t::RawString), - RetryPolicy(t::RetryPolicy), - Enum(t::Enum), - Class(t::Class), } impl FromCST for ConfigItemKey { fn from_cst(elem: SyntaxElement) -> Result { match elem.kind() { - SyntaxKind::WORD | SyntaxKind::KW_CLIENT => { - t::Word::from_cst(elem).map(ConfigItemKey::Word) - } + SyntaxKind::WORD => t::Word::from_cst(elem).map(ConfigItemKey::Word), SyntaxKind::STRING_LITERAL => { t::QuotedString::from_cst(elem).map(ConfigItemKey::String) } - SyntaxKind::KW_RETRY_POLICY => { - t::RetryPolicy::from_cst(elem).map(ConfigItemKey::RetryPolicy) - } - SyntaxKind::KW_ENUM => t::Enum::from_cst(elem).map(ConfigItemKey::Enum), - SyntaxKind::KW_CLASS => t::Class::from_cst(elem).map(ConfigItemKey::Class), _ => Err(StrongAstError::UnexpectedKindDesc { - expected_desc: "WORD, STRING_LITERAL, KW_RETRY_POLICY, KW_ENUM, or KW_CLASS".into(), + expected_desc: "WORD or STRING_LITERAL".into(), found: elem.kind(), at: elem.text_range(), }), @@ -2734,36 +2724,18 @@ impl Printable for ConfigItemKey { PrintInfo::default_single_line() } ConfigItemKey::String(string) => printer.print(string, shape), - ConfigItemKey::RetryPolicy(retry_policy) => { - printer.print_raw_token(retry_policy); - PrintInfo::default_single_line() - } - ConfigItemKey::Enum(enum_) => { - printer.print_raw_token(enum_); - PrintInfo::default_single_line() - } - ConfigItemKey::Class(class) => { - printer.print_raw_token(class); - PrintInfo::default_single_line() - } } } fn leftmost_token(&self) -> TextRange { match self { ConfigItemKey::Word(word) => word.span(), ConfigItemKey::String(string) => string.leftmost_token(), - ConfigItemKey::RetryPolicy(retry_policy) => retry_policy.span(), - ConfigItemKey::Enum(enum_) => enum_.span(), - ConfigItemKey::Class(class) => class.span(), } } fn rightmost_token(&self) -> TextRange { match self { ConfigItemKey::Word(word) => word.span(), ConfigItemKey::String(string) => string.rightmost_token(), - ConfigItemKey::RetryPolicy(retry_policy) => retry_policy.span(), - ConfigItemKey::Enum(enum_) => enum_.span(), - ConfigItemKey::Class(class) => class.span(), } } } diff --git a/baml_language/crates/baml_fmt/src/ast/expressions.rs b/baml_language/crates/baml_fmt/src/ast/expressions.rs index 1ebebd6cbcd..26fa0085d2d 100644 --- a/baml_language/crates/baml_fmt/src/ast/expressions.rs +++ b/baml_language/crates/baml_fmt/src/ast/expressions.rs @@ -160,9 +160,7 @@ impl FromCST for Expression { SyntaxKind::KW_TRUE | SyntaxKind::KW_FALSE | SyntaxKind::KW_NULL => { Literal::from_cst(elem).map(Expression::Literal)? } - SyntaxKind::WORD | SyntaxKind::KW_CLIENT => { - PathExpr::from_cst(elem).map(Expression::Path)? - } + SyntaxKind::WORD => PathExpr::from_cst(elem).map(Expression::Path)?, SyntaxKind::PATH_EXPR => { // The parser wraps any postfix `<...>` in a PATH_EXPR. When the // base is a plain path (word / nested PATH_EXPR) it is a @@ -170,7 +168,7 @@ impl FromCST for Expression { // is a generic instantiation on a non-path base. let node = StrongAstError::assert_is_node(elem.clone())?; let base_is_path = SyntaxNodeIter::new(&node).next().is_some_and(|c| { - is_path_segment_kind(c.kind()) || c.kind() == SyntaxKind::PATH_EXPR + c.kind() == SyntaxKind::WORD || c.kind() == SyntaxKind::PATH_EXPR }); if base_is_path { PathExpr::from_cst(elem).map(Expression::Path)? @@ -515,16 +513,9 @@ pub struct PathExpr { pub generic_args: Option, } -/// Mirrors the segment set of `parse_path_or_ident` in `baml_compiler_parser`: -/// every word-like keyword, so a member such as `reflect.Class.extends` keeps -/// its path shape. -fn is_path_segment_kind(kind: SyntaxKind) -> bool { - t::is_word_like(kind) -} - fn path_segment_from_cst(elem: SyntaxElement) -> Result { let token = StrongAstError::assert_is_token(elem)?; - if is_path_segment_kind(token.kind()) { + if token.kind() == SyntaxKind::WORD { Ok(t::Word::new_from_span(token.text_range())) } else { Err(StrongAstError::UnexpectedKindDesc { @@ -537,7 +528,7 @@ fn path_segment_from_cst(elem: SyntaxElement) -> Result impl FromCST for PathExpr { fn from_cst(elem: SyntaxElement) -> Result { - if is_path_segment_kind(elem.kind()) { + if elem.kind() == SyntaxKind::WORD { let first = path_segment_from_cst(elem)?; return Ok(PathExpr { first, @@ -557,7 +548,7 @@ impl FromCST for PathExpr { .ok_or_else(|| StrongAstError::missing(SyntaxKind::WORD, it.parent))?; let (first, mut rest) = match next.kind() { - kind if is_path_segment_kind(kind) => (path_segment_from_cst(next)?, Vec::new()), + SyntaxKind::WORD => (path_segment_from_cst(next)?, Vec::new()), SyntaxKind::PATH_EXPR => { let nested = PathExpr::from_cst(next)?; if nested.generic_args.is_some() { @@ -2776,7 +2767,7 @@ impl FromCST for CallArg { .collect(); let (label, expr_elem) = if children.len() >= 3 - && matches!(children[0].kind(), SyntaxKind::WORD | SyntaxKind::KW_CLIENT) + && children[0].kind() == SyntaxKind::WORD && children[1].kind() == SyntaxKind::EQUALS { let name = t::Word::new_from_span(children[0].text_range()); @@ -4763,9 +4754,7 @@ impl FromCST for ObjectFieldKey { SyntaxKind::KW_TRUE | SyntaxKind::KW_FALSE | SyntaxKind::KW_NULL => Ok( ObjectFieldKey::Expression(Box::new(Expression::from_cst(elem)?)), ), - // `client` (KW_CLIENT) is a keyword but a valid field name, e.g. - // `Agent { client: ... }` — mirror `parse_object_field`. - kind if t::is_word_like(kind) => Ok(ObjectFieldKey::Word(t::Word::from_cst(elem)?)), + SyntaxKind::WORD => Ok(ObjectFieldKey::Word(t::Word::from_cst(elem)?)), SyntaxKind::STRING_LITERAL => { Ok(ObjectFieldKey::String(t::QuotedString::from_cst(elem)?)) } diff --git a/baml_language/crates/baml_fmt/src/ast/tokens.rs b/baml_language/crates/baml_fmt/src/ast/tokens.rs index caf1051e0f8..de571f0e728 100644 --- a/baml_language/crates/baml_fmt/src/ast/tokens.rs +++ b/baml_language/crates/baml_fmt/src/ast/tokens.rs @@ -80,7 +80,6 @@ define_keyword_tokens! { "catch" => SyntaxKind::KW_CATCH => Catch; "catch_all" => SyntaxKind::KW_CATCH_ALL => CatchAll; "catch_all_panics" => SyntaxKind::KW_CATCH_ALL_PANICS => CatchAllPanics; - "instanceof" => SyntaxKind::KW_INSTANCEOF => Instanceof; "is" => SyntaxKind::KW_IS => Is; "spawn" => SyntaxKind::KW_SPAWN => Spawn; "with" => SyntaxKind::KW_WITH => With; @@ -252,7 +251,6 @@ pub enum BinaryOp { And(And), Pipe(Pipe), Caret(Caret), - Instanceof(Instanceof), LessLess(LessLess), GreaterGreater(GreaterGreater), Plus(Plus), @@ -289,7 +287,6 @@ impl BinaryOp { BinaryOp::OrOr(t) => t.span(), BinaryOp::Pipe(t) => t.span(), BinaryOp::Caret(t) => t.span(), - BinaryOp::Instanceof(t) => t.span(), BinaryOp::LessLess(t) => t.span(), BinaryOp::GreaterGreater(t) => t.span(), BinaryOp::Plus(t) => t.span(), @@ -339,9 +336,6 @@ impl FromCST for BinaryOp { SyntaxKind::OR_OR => Ok(BinaryOp::OrOr(OrOr::new_from_span(token.text_range()))), SyntaxKind::PIPE => Ok(BinaryOp::Pipe(Pipe::new_from_span(token.text_range()))), SyntaxKind::CARET => Ok(BinaryOp::Caret(Caret::new_from_span(token.text_range()))), - SyntaxKind::KW_INSTANCEOF => Ok(BinaryOp::Instanceof(Instanceof::new_from_span( - token.text_range(), - ))), SyntaxKind::LESS_LESS => Ok(BinaryOp::LessLess(LessLess::new_from_span( token.text_range(), ))), @@ -412,7 +406,6 @@ impl Printable for BinaryOp { BinaryOp::And(t) => printer.print_raw_token(t), BinaryOp::Pipe(t) => printer.print_raw_token(t), BinaryOp::Caret(t) => printer.print_raw_token(t), - BinaryOp::Instanceof(t) => printer.print_raw_token(t), BinaryOp::LessLess(t) => printer.print_raw_token(t), BinaryOp::GreaterGreater(t) => printer.print_raw_token(t), BinaryOp::Plus(t) => printer.print_raw_token(t), @@ -621,37 +614,10 @@ impl Word { } } -/// True for token kinds the parser accepts as identifiers in name positions. -/// -/// The lexer emits dedicated keyword kinds for these words, but the parser -/// keeps them valid as field, parameter, method, and member-access names -/// (e.g. a class field or parameter named `client`, or `x.implements(y)` on -/// the reflection `type` value). The CST therefore contains the keyword kind -/// where the strong AST expects a name, and [`Word::from_cst`] must accept it. -/// Mirrors `at_member_name` and `parse_parameter` in `baml_compiler_parser`. -#[must_use] -pub fn is_word_like(kind: SyntaxKind) -> bool { - matches!( - kind, - SyntaxKind::WORD - | SyntaxKind::KW_CLIENT - | SyntaxKind::KW_CLASS - | SyntaxKind::KW_ENUM - | SyntaxKind::KW_FUNCTION - | SyntaxKind::KW_IMPLEMENTS - | SyntaxKind::KW_IMPLEMENT - | SyntaxKind::KW_EXTENDS - | SyntaxKind::KW_REQUIRES - | SyntaxKind::KW_INTERFACE - | SyntaxKind::KW_SPAWN - | SyntaxKind::KW_AWAIT - ) -} - impl FromCST for Word { fn from_cst(elem: SyntaxElement) -> Result { let token = StrongAstError::assert_is_token(elem)?; - if !is_word_like(token.kind()) { + if token.kind() != SyntaxKind::WORD { return Err(StrongAstError::UnexpectedKind { expected: SyntaxKind::WORD, found: token.kind(), diff --git a/baml_language/crates/baml_fmt/src/ast/types.rs b/baml_language/crates/baml_fmt/src/ast/types.rs index 9498b08e783..f5fbf9d5d12 100644 --- a/baml_language/crates/baml_fmt/src/ast/types.rs +++ b/baml_language/crates/baml_fmt/src/ast/types.rs @@ -561,16 +561,7 @@ impl UnionTypeMember { let dot = t::Dot::from_cst(dot)?; let segment = it.expect_next("type path segment after `.`")?; let token = StrongAstError::assert_is_token(segment)?; - if !matches!( - token.kind(), - SyntaxKind::WORD - | SyntaxKind::KW_SPAWN - | SyntaxKind::KW_AWAIT - | SyntaxKind::KW_CLASS - | SyntaxKind::KW_ENUM - | SyntaxKind::KW_INTERFACE - | SyntaxKind::KW_FUNCTION - ) { + if token.kind() != SyntaxKind::WORD { return Err(StrongAstError::UnexpectedKindDesc { expected_desc: "type path segment".into(), found: token.kind(), @@ -1273,9 +1264,7 @@ impl FromCST for FunctionTypeParam { let mut it = SyntaxNodeIter::new(&node); - let name = if let Some(name) = - it.next_if(|e| matches!(e.kind(), SyntaxKind::WORD | SyntaxKind::KW_CLIENT)) - { + let name = if let Some(name) = it.next_if(|e| e.kind() == SyntaxKind::WORD) { let name = t::Word::new_from_span(name.text_range()); let question = it .next_if_kind(SyntaxKind::QUESTION) diff --git a/baml_language/crates/baml_fmt/src/lib.rs b/baml_language/crates/baml_fmt/src/lib.rs index 92926e93e23..533901eb371 100644 --- a/baml_language/crates/baml_fmt/src/lib.rs +++ b/baml_language/crates/baml_fmt/src/lib.rs @@ -700,8 +700,8 @@ mod object_spread_tests { mod contextual_keyword_identifier_tests { use super::*; - /// `client` lexes as `KW_CLIENT` but is a legal identifier everywhere the - /// checker accepts one. The formatter must not die on it (it used to: + /// `client` is a weak keyword: a legal identifier everywhere outside a + /// client declaration. The formatter must not die on it (it used to: /// "Expected token/node of kind WORD, but found `KW_CLIENT`"). #[test] fn test_client_as_identifier_formats() { @@ -1221,11 +1221,10 @@ mod linear_formatter_regression_tests { #[cfg(test)] mod contextual_keyword_name_tests { - //! Regression tests for contextual keywords used as names. The lexer - //! emits a dedicated keyword kind for `client` (`KW_CLIENT`), and the - //! parser accepts it as a class field, parameter, and member-access - //! name (BEP-049 §10 `ctx.client`). The formatter used to reject those - //! files with "Expected token/node of kind WORD, but found `KW_CLIENT`". + //! Regression tests for weak keywords used as names: `client` is a class + //! field, parameter, and member-access name (BEP-049 §10 `ctx.client`). + //! The formatter used to reject those files with "Expected token/node of + //! kind WORD, but found `KW_CLIENT`". use super::*; diff --git a/baml_language/crates/baml_ide/src/info.rs b/baml_language/crates/baml_ide/src/info.rs index 15cf547b919..75862c0be28 100644 --- a/baml_language/crates/baml_ide/src/info.rs +++ b/baml_language/crates/baml_ide/src/info.rs @@ -734,12 +734,7 @@ fn client_config_key_type_info(token: &SyntaxToken) -> Option { let key = config_item .children_with_tokens() .filter_map(rowan::NodeOrToken::into_token) - .find(|candidate| { - matches!( - candidate.kind(), - SyntaxKind::WORD | SyntaxKind::KW_RETRY_POLICY - ) - })?; + .find(|candidate| candidate.kind() == SyntaxKind::WORD)?; if key.text_range() != token.text_range() { return None; } diff --git a/baml_language/crates/baml_ide/src/rename.rs b/baml_language/crates/baml_ide/src/rename.rs index 5f633211351..b4c8d379b88 100644 --- a/baml_language/crates/baml_ide/src/rename.rs +++ b/baml_language/crates/baml_ide/src/rename.rs @@ -1094,7 +1094,7 @@ function read(p: P, n: Named, s: Shows) -> string throws never { #[test] fn the_replacement_must_be_one_identifier() { let test = at("enum Status", "Status"); - for bad in ["", "2legit", "has space", "with.dot", "class", "type"] { + for bad in ["", "2legit", "has space", "with.dot", "class", "self"] { let error = rename(&test.db, test.cursor.file, test.cursor.offset, bad) .expect_err("`{bad}` is not an identifier"); assert_eq!( diff --git a/baml_language/crates/baml_tests/baml_src/ns_compiler/ns_throws/ns_unknown_contracts/precision.baml b/baml_language/crates/baml_tests/baml_src/ns_compiler/ns_throws_clauses/ns_unknown_contracts/precision.baml similarity index 100% rename from baml_language/crates/baml_tests/baml_src/ns_compiler/ns_throws/ns_unknown_contracts/precision.baml rename to baml_language/crates/baml_tests/baml_src/ns_compiler/ns_throws_clauses/ns_unknown_contracts/precision.baml diff --git a/baml_language/crates/baml_tests/baml_src/ns_constructor_consistency/constructor_consistency.baml b/baml_language/crates/baml_tests/baml_src/ns_constructor_consistency/constructor_consistency.baml index a55bcd09dbc..675dd5b5310 100644 --- a/baml_language/crates/baml_tests/baml_src/ns_constructor_consistency/constructor_consistency.baml +++ b/baml_language/crates/baml_tests/baml_src/ns_constructor_consistency/constructor_consistency.baml @@ -1,8 +1,8 @@ test "reserved words are rejected as reflection constructor names" { - let class_name = reflect.Class.new("type", {}) catch (e) { + let class_name = reflect.Class.new("string", {}) catch (e) { reflect.errors.CompilationError => e.diagnostics[0].code + "|" + e.diagnostics[0].message, }; - let field_name = reflect.Class.new("ValidClass", { "test": string.type() }) catch (e) { + let field_name = reflect.Class.new("ValidClass", { "self": string.type() }) catch (e) { reflect.errors.CompilationError => e.diagnostics[0].code + "|" + e.diagnostics[0].message, }; let enum_name = reflect.Enum.new("class", []) catch (e) { @@ -34,7 +34,7 @@ test "reserved words are rejected as reflection constructor names" { }; assert.equal( class_result + "\n" + field_result + "\n" + enum_result + "\n" + variant_result, - "E0010|invalid class name `type`\nE0010|invalid field name `ValidClass.test`\nE0010|invalid enum name `class`\nE0010|invalid enum variant name `ValidEnum.function`", + "E0010|invalid class name `string`\nE0010|invalid field name `ValidClass.self`\nE0010|invalid enum name `class`\nE0010|invalid enum variant name `ValidEnum.function`", ) } diff --git a/baml_language/crates/baml_tests/baml_src/ns_defer/defer.baml b/baml_language/crates/baml_tests/baml_src/ns_defer_blocks/defer.baml similarity index 100% rename from baml_language/crates/baml_tests/baml_src/ns_defer/defer.baml rename to baml_language/crates/baml_tests/baml_src/ns_defer_blocks/defer.baml diff --git a/baml_language/crates/baml_tests/baml_src/ns_defer/defer_scopes.baml b/baml_language/crates/baml_tests/baml_src/ns_defer_blocks/defer_scopes.baml similarity index 100% rename from baml_language/crates/baml_tests/baml_src/ns_defer/defer_scopes.baml rename to baml_language/crates/baml_tests/baml_src/ns_defer_blocks/defer_scopes.baml diff --git a/baml_language/crates/baml_tests/baml_src/ns_fixtures/ns_catch_all_panics/catch_all_panics.baml b/baml_language/crates/baml_tests/baml_src/ns_fixtures/ns_catch_all_panics_clauses/catch_all_panics.baml similarity index 100% rename from baml_language/crates/baml_tests/baml_src/ns_fixtures/ns_catch_all_panics/catch_all_panics.baml rename to baml_language/crates/baml_tests/baml_src/ns_fixtures/ns_catch_all_panics_clauses/catch_all_panics.baml diff --git a/baml_language/crates/baml_tests/baml_src/ns_item_projections/item_projections.baml b/baml_language/crates/baml_tests/baml_src/ns_item_projections/item_projections.baml index 55a24dd9511..8ab8f2f8269 100644 --- a/baml_language/crates/baml_tests/baml_src/ns_item_projections/item_projections.baml +++ b/baml_language/crates/baml_tests/baml_src/ns_item_projections/item_projections.baml @@ -378,29 +378,29 @@ test "as_remains_usable_as_an_identifier" { assert.equal((as + 1).to_string(), "6") } -// An interface method may be named with a contextual keyword; every access -// spelling must reach it, the qualified one included. +// An interface method may be named with a weak keyword; every access spelling +// must reach it, the qualified one included. interface Keyworded { - function implements(self) -> int throws never - function extends(self) -> int throws never + function test(self) -> int throws never + function client(self) -> int throws never } class Keyed2 { implements Keyworded { - function implements(self) -> int throws never { + function test(self) -> int throws never { 1 } - function extends(self) -> int throws never { + function client(self) -> int throws never { 2 } } } test "keyword_named_members_are_reachable_through_the_qualifier" { let k = Keyed2 {} - assert.equal(k.implements(), 1) - assert.equal((Keyed2 as Keyworded).implements(k), 1) - assert.equal((Keyed2 as Keyworded).extends(k), 2) - assert.equal(Keyworded.extends(k), 2) + assert.equal(k.test(), 1) + assert.equal((Keyed2 as Keyworded).test(k), 1) + assert.equal((Keyed2 as Keyworded).client(k), 2) + assert.equal(Keyworded.client(k), 2) } // A `self`-less member reached through the TYPE carries the whole callee diff --git a/baml_language/crates/baml_tests/baml_src/ns_reserved_names/reserved_names.baml b/baml_language/crates/baml_tests/baml_src/ns_reserved_names/reserved_names.baml new file mode 100644 index 00000000000..0bc013ac470 --- /dev/null +++ b/baml_language/crates/baml_tests/baml_src/ns_reserved_names/reserved_names.baml @@ -0,0 +1,212 @@ +// Reserved names: keywords never name a declaration, and built-in type names +// never name an item or type parameter. Each source compiles as its own +// package, so every case reports exactly its own diagnostics. + +function reserved_names_first_error(source: string) -> string { + let result = reflect.Package.compile({ "main.baml": source }) catch (e) { + reflect.errors.CompilationError => e.diagnostics[0].code + "|" + e.diagnostics[0].message, + }; + if (result is string) { + result + } else { + "ok" + } +} + +function reserved_names_builtin_types() -> string[] { + [ + "int", + "bigint", + "float", + "string", + "bool", + "uint8array", + "image", + "audio", + "video", + "pdf", + "map", + "json", + "void", + "never", + "unknown", + ] +} + +function reserved_names_keywords() -> string[] { + [ + "class", + "enum", + "interface", + "implements", + "implement", + "extends", + "requires", + "function", + "if", + "else", + "for", + "while", + "let", + "in", + "break", + "continue", + "return", + "throw", + "match", + "catch", + "catch_all", + "catch_all_panics", + "throws", + "spawn", + "await", + "defer", + "is", + "const", + "true", + "false", + "null", + "self", + "root", + ] +} + +function reserved_names_weak_keywords() -> string[] { + [ + "client", + "generator", + "test", + "testset", + "retry_policy", + "template_string", + "type", + "as", + "with", + "instanceof", + ] +} + +test "built-in type names cannot name an item or type parameter" { + for (let name in reserved_names_builtin_types()) { + let reserved = "E0176|`" + name + "` is a built-in type name and cannot name "; + let label = ": `" + name + "` is reserved"; + assert.equal(reserved_names_first_error("class " + name + " { x: int }"), reserved + "a class" + label); + assert.equal(reserved_names_first_error("enum " + name + " { A }"), reserved + "an enum" + label); + assert.equal(reserved_names_first_error("interface " + name + " { }"), reserved + "an interface" + label); + assert.equal(reserved_names_first_error("type " + name + " = int"), reserved + "a type alias" + label); + assert.equal( + reserved_names_first_error("function " + name + "() -> int { 1 }"), + reserved + "a function" + label, + ); + assert.equal( + reserved_names_first_error("function f<" + name + ">(x: int) -> int { x }"), + reserved + "a type parameter" + label, + ); + assert.equal( + reserved_names_first_error("class Box<" + name + "> { x: int }"), + reserved + "a type parameter" + label, + ); + } +} + +test "built-in type names stay legal as members, parameters, and locals" { + for (let name in reserved_names_builtin_types()) { + assert.equal(reserved_names_first_error("class C { " + name + ": int }"), "ok"); + assert.equal( + reserved_names_first_error("class C { function " + name + "(self) -> int { 1 } }"), + "ok", + ); + assert.equal(reserved_names_first_error("enum E { " + name + " }"), "ok"); + assert.equal( + reserved_names_first_error("function f(" + name + ": int) -> int { " + name + " }"), + "ok", + ); + assert.equal( + reserved_names_first_error("function f() -> int { let " + name + " = 1; " + name + " }"), + "ok", + ); + } +} + +test "keywords cannot name a function or method" { + for (let name in reserved_names_keywords()) { + let reserved = "E0176|`" + name + "` is a keyword and cannot name "; + let label = ": `" + name + "` is reserved"; + assert.equal( + reserved_names_first_error("function " + name + "() -> int { 1 }"), + reserved + "a function" + label, + ); + assert.equal( + reserved_names_first_error("class C { function " + name + "(self) -> int { 1 } }"), + reserved + "a method" + label, + ); + assert.equal( + reserved_names_first_error("interface I { function " + name + "(self) -> int }"), + reserved + "a method" + label, + ); + } +} + +test "keywords cannot name any declaration" { + for (let name in reserved_names_keywords()) { + let reserved = "E0176|`" + name + "` is a keyword and cannot name "; + let label = ": `" + name + "` is reserved"; + assert.equal(reserved_names_first_error("class " + name + " { x: int }"), reserved + "a class" + label); + assert.equal(reserved_names_first_error("enum " + name + " { A }"), reserved + "an enum" + label); + assert.equal(reserved_names_first_error("interface " + name + " { }"), reserved + "an interface" + label); + assert.equal(reserved_names_first_error("type " + name + " = int"), reserved + "a type alias" + label); + assert.equal( + reserved_names_first_error("function f<" + name + ">(x: int) -> int { x }"), + reserved + "a type parameter" + label, + ); + assert.equal(reserved_names_first_error("class C { " + name + ": int }"), reserved + "a field" + label); + assert.equal(reserved_names_first_error("interface I { " + name + ": int }"), reserved + "a field" + label); + assert.equal(reserved_names_first_error("enum E { " + name + " }"), reserved + "an enum variant" + label); + assert.equal( + reserved_names_first_error("function f(" + name + ": int) -> int { 1 }"), + reserved + "a parameter" + label, + ); + assert.equal( + reserved_names_first_error("function f() -> int { let g = (" + name + ": int) -> int { 1 }; 1 }"), + reserved + "a parameter" + label, + ); + assert.equal( + reserved_names_first_error("function f() -> int { let " + name + " = 1; 1 }"), + reserved + "a binding" + label, + ); + assert.equal( + reserved_names_first_error("function f() -> int { 1 catch (" + name + ") { _ => 2 } }"), + reserved + "a binding" + label, + ); + } +} + +test "weak keywords are ordinary names outside their declarations" { + for (let name in reserved_names_weak_keywords()) { + assert.equal(reserved_names_first_error("class " + name + " { x: int }"), "ok"); + assert.equal(reserved_names_first_error("class C { " + name + ": int }"), "ok"); + assert.equal( + reserved_names_first_error("class C { x: int\n function " + name + "(self) -> int { self.x } }"), + "ok", + ); + assert.equal( + reserved_names_first_error( + "function f(" + name + ": int) -> int { let y = " + name + "; y }", + ), + "ok", + ); + assert.equal( + reserved_names_first_error("function g() -> int { 1 }\nfunction " + name + "() -> int { g() }"), + "ok", + ); + } +} + +test "keyword-prefixed names are ordinary identifiers" { + assert.equal( + reserved_names_first_error( + "class is_function { if_x: int }\nfunction class_name(self_ref: int) -> int { let root_y = self_ref; root_y }", + ), + "ok", + ) +} diff --git a/baml_language/crates/baml_tests/projects/diagnostic_errors/instanceof_removed/instanceof_removed.baml b/baml_language/crates/baml_tests/projects/diagnostic_errors/instanceof_removed/instanceof_removed.baml deleted file mode 100644 index 14a817a214f..00000000000 --- a/baml_language/crates/baml_tests/projects/diagnostic_errors/instanceof_removed/instanceof_removed.baml +++ /dev/null @@ -1,21 +0,0 @@ -// The `instanceof` operator has been removed in favor of `match`. -// Using it should produce error E0098 with a migration hint. - -class Cat { - name string -} - -class Dog { - breed string -} - -function check_instanceof(x: Cat | Dog) -> bool { - x instanceof Cat -} - -function check_instanceof_in_if(x: Cat | Dog) -> string { - if (x instanceof Dog) { - return "dog"; - } - "cat" -} diff --git a/baml_language/crates/baml_tests/projects/diagnostic_errors/root_is_reserved/main.baml b/baml_language/crates/baml_tests/projects/diagnostic_errors/root_is_a_keyword/main.baml similarity index 79% rename from baml_language/crates/baml_tests/projects/diagnostic_errors/root_is_reserved/main.baml rename to baml_language/crates/baml_tests/projects/diagnostic_errors/root_is_a_keyword/main.baml index e7e7c1f8105..4f825dfcbc8 100644 --- a/baml_language/crates/baml_tests/projects/diagnostic_errors/root_is_reserved/main.baml +++ b/baml_language/crates/baml_tests/projects/diagnostic_errors/root_is_a_keyword/main.baml @@ -1,8 +1,7 @@ -// `root` is the path to the package itself (TYPE_SYSTEM.md § Declarations, -// Packages, and Names) and names nothing else: no item, binding, or type -// parameter may be called `root`, so `root.X` always means this package's `X`. -// Every form that declares a name is refused, at the name. The other item -// kinds named `root` live one per namespace (`ns_enum/`, `ns_interface/`, +// `root` is a keyword: the path to the package itself (TYPE_SYSTEM.md +// § Declarations, Packages, and Names), so `root.X` always means this +// package's `X`. Every form that declares a name refuses it. The other item +// kinds named `root` live one per namespace (`ns_enum_item/`, `ns_interface_item/`, // `ns_alias/`), so each reports once. class root { diff --git a/baml_language/crates/baml_tests/projects/diagnostic_errors/root_is_reserved/ns_alias/item.baml b/baml_language/crates/baml_tests/projects/diagnostic_errors/root_is_a_keyword/ns_alias/item.baml similarity index 100% rename from baml_language/crates/baml_tests/projects/diagnostic_errors/root_is_reserved/ns_alias/item.baml rename to baml_language/crates/baml_tests/projects/diagnostic_errors/root_is_a_keyword/ns_alias/item.baml diff --git a/baml_language/crates/baml_tests/projects/diagnostic_errors/root_is_reserved/ns_enum/item.baml b/baml_language/crates/baml_tests/projects/diagnostic_errors/root_is_a_keyword/ns_enum_item/item.baml similarity index 100% rename from baml_language/crates/baml_tests/projects/diagnostic_errors/root_is_reserved/ns_enum/item.baml rename to baml_language/crates/baml_tests/projects/diagnostic_errors/root_is_a_keyword/ns_enum_item/item.baml diff --git a/baml_language/crates/baml_tests/projects/diagnostic_errors/root_is_reserved/ns_interface/item.baml b/baml_language/crates/baml_tests/projects/diagnostic_errors/root_is_a_keyword/ns_interface_item/item.baml similarity index 100% rename from baml_language/crates/baml_tests/projects/diagnostic_errors/root_is_reserved/ns_interface/item.baml rename to baml_language/crates/baml_tests/projects/diagnostic_errors/root_is_a_keyword/ns_interface_item/item.baml diff --git a/baml_language/crates/baml_tests/projects/diagnostic_errors/root_namespace_is_reserved/main.baml b/baml_language/crates/baml_tests/projects/diagnostic_errors/root_namespace_is_reserved/main.baml index 6b9f2b7f68f..82bf5793d3b 100644 --- a/baml_language/crates/baml_tests/projects/diagnostic_errors/root_namespace_is_reserved/main.baml +++ b/baml_language/crates/baml_tests/projects/diagnostic_errors/root_namespace_is_reserved/main.baml @@ -1,5 +1,5 @@ -// A directory `ns_root/` would make `root.root.X` a path: the namespace is -// refused like every other name spelled `root`. +// A namespace takes its name from its directory, so `ns_root/` would name a +// namespace with the keyword `root`: refused like any other keyword name. function main() -> int { 1 diff --git a/baml_language/crates/baml_tests/snapshots/broken_syntax/is_in_type_position/baml_tests__broken_syntax__is_in_type_position__05_diagnostics.snap b/baml_language/crates/baml_tests/snapshots/broken_syntax/is_in_type_position/baml_tests__broken_syntax__is_in_type_position__05_diagnostics.snap index bf46f5b4fca..4000bf6c027 100644 --- a/baml_language/crates/baml_tests/snapshots/broken_syntax/is_in_type_position/baml_tests__broken_syntax__is_in_type_position__05_diagnostics.snap +++ b/baml_language/crates/baml_tests/snapshots/broken_syntax/is_in_type_position/baml_tests__broken_syntax__is_in_type_position__05_diagnostics.snap @@ -70,7 +70,7 @@ source: crates/baml_tests/src/generated_tests.rs ╭─[main.baml:6:43] 6 │ function bad_param(v: v is int) -> bool { true } · ──┬─ - · ╰── expected `top-level declaration`, found `identifier` + · ╰── expected `top-level declaration`, found `true` ╰──── [parse] E0010 @@ -123,7 +123,7 @@ source: crates/baml_tests/src/generated_tests.rs ╭─[main.baml:9:37] 9 │ function bad_return() -> v is int { true } · ──┬─ - · ╰── expected `top-level declaration`, found `identifier` + · ╰── expected `top-level declaration`, found `true` ╰──── [parse] E0010 diff --git a/baml_language/crates/baml_tests/snapshots/broken_syntax/parser_strings/baml_tests__broken_syntax__parser_strings__05_diagnostics.snap b/baml_language/crates/baml_tests/snapshots/broken_syntax/parser_strings/baml_tests__broken_syntax__parser_strings__05_diagnostics.snap index 87eda449ef4..64395484439 100644 --- a/baml_language/crates/baml_tests/snapshots/broken_syntax/parser_strings/baml_tests__broken_syntax__parser_strings__05_diagnostics.snap +++ b/baml_language/crates/baml_tests/snapshots/broken_syntax/parser_strings/baml_tests__broken_syntax__parser_strings__05_diagnostics.snap @@ -405,14 +405,13 @@ source: crates/baml_tests/src/generated_tests.rs · ╰── expected `top-level declaration`, found `'{'` ╰──── - [hir] E0010 + [parse] E0010 - × `client` config blocks are removed; declare a client value instead: `client MyClient = openai.ResponsesClient.new(model = "...");` (compose reliability with ai.Retry / ai.Fallback / - │ ai.RoundRobin) + × unexpected token ╭─[unicode_strings.baml:26:3] 26 │ client: GPT4 · ───┬── - · ╰── replace with `client Name = ;` + · ╰── expected `top-level declaration`, found `identifier` ╰──── [parse] E0010 @@ -421,7 +420,7 @@ source: crates/baml_tests/src/generated_tests.rs ╭─[unicode_strings.baml:26:9] 26 │ client: GPT4 · ┬ - · ╰── expected `client name`, found `':'` + · ╰── expected `top-level declaration`, found `':'` ╰──── [parse] E0010 diff --git a/baml_language/crates/baml_tests/snapshots/diagnostic_errors/instanceof_removed/baml_tests__diagnostic_errors__instanceof_removed__05_diagnostics.snap b/baml_language/crates/baml_tests/snapshots/diagnostic_errors/instanceof_removed/baml_tests__diagnostic_errors__instanceof_removed__05_diagnostics.snap deleted file mode 100644 index 13f7b168558..00000000000 --- a/baml_language/crates/baml_tests/snapshots/diagnostic_errors/instanceof_removed/baml_tests__diagnostic_errors__instanceof_removed__05_diagnostics.snap +++ /dev/null @@ -1,21 +0,0 @@ ---- -source: crates/baml_tests/src/generated_tests.rs ---- -=== COMPILER2 DIAGNOSTICS === - [hir] E0098 - - × `instanceof` is no longer supported. Use a `match` expression for type checking instead. - ╭─[instanceof_removed.baml:13:7] - 13 │ x instanceof Cat - · ─────┬──── - · ╰── use `match` instead - ╰──── - - [hir] E0098 - - × `instanceof` is no longer supported. Use a `match` expression for type checking instead. - ╭─[instanceof_removed.baml:17:11] - 17 │ if (x instanceof Dog) { - · ─────┬──── - · ╰── use `match` instead - ╰──── diff --git a/baml_language/crates/baml_tests/snapshots/diagnostic_errors/instanceof_removed/baml_tests__diagnostic_errors__instanceof_removed__10_formatter__instanceof_removed.snap b/baml_language/crates/baml_tests/snapshots/diagnostic_errors/instanceof_removed/baml_tests__diagnostic_errors__instanceof_removed__10_formatter__instanceof_removed.snap deleted file mode 100644 index de2dede2ac8..00000000000 --- a/baml_language/crates/baml_tests/snapshots/diagnostic_errors/instanceof_removed/baml_tests__diagnostic_errors__instanceof_removed__10_formatter__instanceof_removed.snap +++ /dev/null @@ -1,24 +0,0 @@ ---- -source: crates/baml_tests/src/generated_tests.rs ---- -// The `instanceof` operator has been removed in favor of `match`. -// Using it should produce error E0098 with a migration hint. - -class Cat { - name: string, -} - -class Dog { - breed: string, -} - -function check_instanceof(x: Cat | Dog) -> bool { - x instanceof Cat -} - -function check_instanceof_in_if(x: Cat | Dog) -> string { - if (x instanceof Dog) { - return "dog"; - } - "cat" -} diff --git a/baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_is_a_keyword/baml_tests__diagnostic_errors__root_is_a_keyword__05_diagnostics.snap b/baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_is_a_keyword/baml_tests__diagnostic_errors__root_is_a_keyword__05_diagnostics.snap new file mode 100644 index 00000000000..17e6b6d28c4 --- /dev/null +++ b/baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_is_a_keyword/baml_tests__diagnostic_errors__root_is_a_keyword__05_diagnostics.snap @@ -0,0 +1,206 @@ +--- +source: crates/baml_tests/src/generated_tests.rs +--- +=== COMPILER2 DIAGNOSTICS === + [hir] E0176 + + × `root` is a keyword and cannot name a class + ╭─[main.baml:7:7] + 7 │ class root { + · ──┬─ + · ╰── `root` is reserved + ╰──── + + [hir] E0176 + + × `root` is a keyword and cannot name a function + ╭─[main.baml:11:10] + 11 │ function root() -> int { + · ──┬─ + · ╰── `root` is reserved + ╰──── + + [validation] E0011 + + × name `root` defined 2 times as: class, function + ╭─[main.baml:7:7] + 7 │ class root { + · ──┬─ + · ╰── first defined as class here + ╰──── + ╭─[main.baml:11:10] + 11 │ function root() -> int { + · ──┬─ + · ╰── duplicate function definition + ╰──── + + [hir] E0176 + + × `root` is a keyword and cannot name a type parameter + ╭─[main.baml:15:18] + 15 │ function generic(value: root) -> root { + · ──┬─ + · ╰── `root` is reserved + ╰──── + + [hir] E0176 + + × `root` is a keyword and cannot name a type parameter + ╭─[main.baml:19:14] + 19 │ class Holder { + · ──┬─ + · ╰── `root` is reserved + ╰──── + + [hir] E0176 + + × `root` is a keyword and cannot name a type parameter + ╭─[main.baml:23:17] + 23 │ interface Bound { + · ──┬─ + · ╰── `root` is reserved + ╰──── + + [hir] E0176 + + × `root` is a keyword and cannot name a type parameter + ╭─[main.baml:28:23] + 28 │ function required(self, value: root) -> int throws never + · ──┬─ + · ╰── `root` is reserved + ╰──── + + [hir] E0176 + + × `root` is a keyword and cannot name a parameter + ╭─[main.baml:29:35] + 29 │ function required_param(self, root: int) -> int throws never + · ──┬─ + · ╰── `root` is reserved + ╰──── + + [hir] E0176 + + × `root` is a keyword and cannot name a type parameter + ╭─[main.baml:34:21] + 34 │ function method(self, value: root) -> root { + · ──┬─ + · ╰── `root` is reserved + ╰──── + + [hir] E0176 + + × `root` is a keyword and cannot name a type parameter + ╭─[main.baml:39:12] + 39 │ implements Bound for Holder { + · ──┬─ + · ╰── `root` is reserved + ╰──── + + [hir] E0176 + + × `root` is a keyword and cannot name a parameter + ╭─[main.baml:45:20] + 45 │ function parameter(root: string) -> string { + · ──┬─ + · ╰── `root` is reserved + ╰──── + + [hir] E0176 + + × `root` is a keyword and cannot name a binding + ╭─[main.baml:50:5] + 50 │ let root = 1 + · ────┬─── + · ╰── `root` is reserved + ╰──── + + [hir] E0176 + + × `root` is a keyword and cannot name a parameter + ╭─[main.baml:55:14] + 55 │ let f = (root: int) => { root } + · ──┬─ + · ╰── `root` is reserved + ╰──── + + [hir] E0176 + + × `root` is a keyword and cannot name a binding + ╭─[main.baml:60:10] + 60 │ for (let root in items) { + · ────┬─── + · ╰── `root` is reserved + ╰──── + + [hir] E0176 + + × `root` is a keyword and cannot name a binding + ╭─[main.baml:69:14] + 69 │ } catch (root) { + · ──┬─ + · ╰── `root` is reserved + ╰──── + + [hir] E0176 + + × `root` is a keyword and cannot name a binding + ╭─[main.baml:76:9] + 76 │ let root: int => root, + · ──────┬────── + · ╰── `root` is reserved + ╰──── + + [hir] E0176 + + × `root` is a keyword and cannot name a binding + ╭─[main.baml:82:30] + 82 │ let Holder { value: let root } = holder + · ────┬─── + · ╰── `root` is reserved + ╰──── + + [hir] E0176 + + × `root` is a keyword and cannot name a binding + ╭─[main.baml:88:10] + 88 │ [let root, ..] => root, + · ────┬─── + · ╰── `root` is reserved + ╰──── + + [hir] E0176 + + × `root` is a keyword and cannot name a type binding + ╭─[main.baml:94:14] + 94 │ let type root = reflect.Type.of(); + · ──┬─ + · ╰── `root` is reserved + ╰──── + + [hir] E0176 + + × `root` is a keyword and cannot name a type alias + ╭─[ns_alias/item.baml:1:6] + 1 │ type root = int + · ──┬─ + · ╰── `root` is reserved + ╰──── + + [hir] E0176 + + × `root` is a keyword and cannot name an enum + ╭─[ns_enum_item/item.baml:1:6] + 1 │ enum root { + · ──┬─ + · ╰── `root` is reserved + ╰──── + + [hir] E0176 + + × `root` is a keyword and cannot name an interface + ╭─[ns_interface_item/item.baml:1:11] + 1 │ interface root { + · ──┬─ + · ╰── `root` is reserved + ╰──── diff --git a/baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_is_reserved/baml_tests__diagnostic_errors__root_is_reserved__10_formatter__main.snap b/baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_is_a_keyword/baml_tests__diagnostic_errors__root_is_a_keyword__10_formatter__main.snap similarity index 80% rename from baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_is_reserved/baml_tests__diagnostic_errors__root_is_reserved__10_formatter__main.snap rename to baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_is_a_keyword/baml_tests__diagnostic_errors__root_is_a_keyword__10_formatter__main.snap index ceedf6c8804..5e86e08d461 100644 --- a/baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_is_reserved/baml_tests__diagnostic_errors__root_is_reserved__10_formatter__main.snap +++ b/baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_is_a_keyword/baml_tests__diagnostic_errors__root_is_a_keyword__10_formatter__main.snap @@ -1,11 +1,10 @@ --- source: crates/baml_tests/src/generated_tests.rs --- -// `root` is the path to the package itself (TYPE_SYSTEM.md § Declarations, -// Packages, and Names) and names nothing else: no item, binding, or type -// parameter may be called `root`, so `root.X` always means this package's `X`. -// Every form that declares a name is refused, at the name. The other item -// kinds named `root` live one per namespace (`ns_enum/`, `ns_interface/`, +// `root` is a keyword: the path to the package itself (TYPE_SYSTEM.md +// § Declarations, Packages, and Names), so `root.X` always means this +// package's `X`. Every form that declares a name refuses it. The other item +// kinds named `root` live one per namespace (`ns_enum_item/`, `ns_interface_item/`, // `ns_alias/`), so each reports once. class root { diff --git a/baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_is_reserved/baml_tests__diagnostic_errors__root_is_reserved__10_formatter__ns_alias_item.snap b/baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_is_a_keyword/baml_tests__diagnostic_errors__root_is_a_keyword__10_formatter__ns_alias_item.snap similarity index 100% rename from baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_is_reserved/baml_tests__diagnostic_errors__root_is_reserved__10_formatter__ns_alias_item.snap rename to baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_is_a_keyword/baml_tests__diagnostic_errors__root_is_a_keyword__10_formatter__ns_alias_item.snap diff --git a/baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_is_reserved/baml_tests__diagnostic_errors__root_is_reserved__10_formatter__ns_enum_item.snap b/baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_is_a_keyword/baml_tests__diagnostic_errors__root_is_a_keyword__10_formatter__ns_enum_item_item.snap similarity index 100% rename from baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_is_reserved/baml_tests__diagnostic_errors__root_is_reserved__10_formatter__ns_enum_item.snap rename to baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_is_a_keyword/baml_tests__diagnostic_errors__root_is_a_keyword__10_formatter__ns_enum_item_item.snap diff --git a/baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_is_reserved/baml_tests__diagnostic_errors__root_is_reserved__10_formatter__ns_interface_item.snap b/baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_is_a_keyword/baml_tests__diagnostic_errors__root_is_a_keyword__10_formatter__ns_interface_item_item.snap similarity index 100% rename from baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_is_reserved/baml_tests__diagnostic_errors__root_is_reserved__10_formatter__ns_interface_item.snap rename to baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_is_a_keyword/baml_tests__diagnostic_errors__root_is_a_keyword__10_formatter__ns_interface_item_item.snap diff --git a/baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_is_reserved/baml_tests__diagnostic_errors__root_is_reserved__05_diagnostics.snap b/baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_is_reserved/baml_tests__diagnostic_errors__root_is_reserved__05_diagnostics.snap deleted file mode 100644 index 818a91eb194..00000000000 --- a/baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_is_reserved/baml_tests__diagnostic_errors__root_is_reserved__05_diagnostics.snap +++ /dev/null @@ -1,206 +0,0 @@ ---- -source: crates/baml_tests/src/generated_tests.rs ---- -=== COMPILER2 DIAGNOSTICS === - [hir] E0176 - - × `root` is the path to this package and cannot name a binding, an item, or a type parameter - ╭─[main.baml:8:7] - 8 │ class root { - · ──┬─ - · ╰── `root` is this package - ╰──── - - [hir] E0176 - - × `root` is the path to this package and cannot name a binding, an item, or a type parameter - ╭─[main.baml:12:10] - 12 │ function root() -> int { - · ──┬─ - · ╰── `root` is this package - ╰──── - - [validation] E0011 - - × name `root` defined 2 times as: class, function - ╭─[main.baml:8:7] - 8 │ class root { - · ──┬─ - · ╰── first defined as class here - ╰──── - ╭─[main.baml:12:10] - 12 │ function root() -> int { - · ──┬─ - · ╰── duplicate function definition - ╰──── - - [hir] E0176 - - × `root` is the path to this package and cannot name a binding, an item, or a type parameter - ╭─[main.baml:16:18] - 16 │ function generic(value: root) -> root { - · ──┬─ - · ╰── `root` is this package - ╰──── - - [hir] E0176 - - × `root` is the path to this package and cannot name a binding, an item, or a type parameter - ╭─[main.baml:20:14] - 20 │ class Holder { - · ──┬─ - · ╰── `root` is this package - ╰──── - - [hir] E0176 - - × `root` is the path to this package and cannot name a binding, an item, or a type parameter - ╭─[main.baml:24:17] - 24 │ interface Bound { - · ──┬─ - · ╰── `root` is this package - ╰──── - - [hir] E0176 - - × `root` is the path to this package and cannot name a binding, an item, or a type parameter - ╭─[main.baml:29:23] - 29 │ function required(self, value: root) -> int throws never - · ──┬─ - · ╰── `root` is this package - ╰──── - - [hir] E0176 - - × `root` is the path to this package and cannot name a binding, an item, or a type parameter - ╭─[main.baml:30:35] - 30 │ function required_param(self, root: int) -> int throws never - · ──┬─ - · ╰── `root` is this package - ╰──── - - [hir] E0176 - - × `root` is the path to this package and cannot name a binding, an item, or a type parameter - ╭─[main.baml:35:21] - 35 │ function method(self, value: root) -> root { - · ──┬─ - · ╰── `root` is this package - ╰──── - - [hir] E0176 - - × `root` is the path to this package and cannot name a binding, an item, or a type parameter - ╭─[main.baml:40:12] - 40 │ implements Bound for Holder { - · ──┬─ - · ╰── `root` is this package - ╰──── - - [hir] E0176 - - × `root` is the path to this package and cannot name a binding, an item, or a type parameter - ╭─[main.baml:46:20] - 46 │ function parameter(root: string) -> string { - · ──┬─ - · ╰── `root` is this package - ╰──── - - [hir] E0176 - - × `root` is the path to this package and cannot name a binding, an item, or a type parameter - ╭─[main.baml:51:5] - 51 │ let root = 1 - · ────┬─── - · ╰── `root` is this package - ╰──── - - [hir] E0176 - - × `root` is the path to this package and cannot name a binding, an item, or a type parameter - ╭─[main.baml:56:14] - 56 │ let f = (root: int) => { root } - · ──┬─ - · ╰── `root` is this package - ╰──── - - [hir] E0176 - - × `root` is the path to this package and cannot name a binding, an item, or a type parameter - ╭─[main.baml:61:10] - 61 │ for (let root in items) { - · ────┬─── - · ╰── `root` is this package - ╰──── - - [hir] E0176 - - × `root` is the path to this package and cannot name a binding, an item, or a type parameter - ╭─[main.baml:70:14] - 70 │ } catch (root) { - · ──┬─ - · ╰── `root` is this package - ╰──── - - [hir] E0176 - - × `root` is the path to this package and cannot name a binding, an item, or a type parameter - ╭─[main.baml:77:9] - 77 │ let root: int => root, - · ──────┬────── - · ╰── `root` is this package - ╰──── - - [hir] E0176 - - × `root` is the path to this package and cannot name a binding, an item, or a type parameter - ╭─[main.baml:83:30] - 83 │ let Holder { value: let root } = holder - · ────┬─── - · ╰── `root` is this package - ╰──── - - [hir] E0176 - - × `root` is the path to this package and cannot name a binding, an item, or a type parameter - ╭─[main.baml:89:10] - 89 │ [let root, ..] => root, - · ────┬─── - · ╰── `root` is this package - ╰──── - - [hir] E0176 - - × `root` is the path to this package and cannot name a binding, an item, or a type parameter - ╭─[main.baml:95:14] - 95 │ let type root = reflect.Type.of(); - · ──┬─ - · ╰── `root` is this package - ╰──── - - [hir] E0176 - - × `root` is the path to this package and cannot name a binding, an item, or a type parameter - ╭─[ns_alias/item.baml:1:6] - 1 │ type root = int - · ──┬─ - · ╰── `root` is this package - ╰──── - - [hir] E0176 - - × `root` is the path to this package and cannot name a binding, an item, or a type parameter - ╭─[ns_enum/item.baml:1:6] - 1 │ enum root { - · ──┬─ - · ╰── `root` is this package - ╰──── - - [hir] E0176 - - × `root` is the path to this package and cannot name a binding, an item, or a type parameter - ╭─[ns_interface/item.baml:1:11] - 1 │ interface root { - · ──┬─ - · ╰── `root` is this package - ╰──── diff --git a/baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_namespace_is_reserved/baml_tests__diagnostic_errors__root_namespace_is_reserved__05_diagnostics.snap b/baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_namespace_is_reserved/baml_tests__diagnostic_errors__root_namespace_is_reserved__05_diagnostics.snap index 4b053ca5c08..ef5e19d3603 100644 --- a/baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_namespace_is_reserved/baml_tests__diagnostic_errors__root_namespace_is_reserved__05_diagnostics.snap +++ b/baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_namespace_is_reserved/baml_tests__diagnostic_errors__root_namespace_is_reserved__05_diagnostics.snap @@ -4,7 +4,7 @@ source: crates/baml_tests/src/generated_tests.rs === COMPILER2 DIAGNOSTICS === [hir] E0176 - × `root` is the path to this package and cannot name a namespace: rename the directory `ns_root/` + × `root` is a keyword and cannot name a namespace: rename the directory `ns_root/` ╭─[items.baml:1:1] 1 │ class Thing { · ▲ diff --git a/baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_namespace_is_reserved/baml_tests__diagnostic_errors__root_namespace_is_reserved__10_formatter__main.snap b/baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_namespace_is_reserved/baml_tests__diagnostic_errors__root_namespace_is_reserved__10_formatter__main.snap index a4f2a1b7014..fe4d01e9171 100644 --- a/baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_namespace_is_reserved/baml_tests__diagnostic_errors__root_namespace_is_reserved__10_formatter__main.snap +++ b/baml_language/crates/baml_tests/snapshots/diagnostic_errors/root_namespace_is_reserved/baml_tests__diagnostic_errors__root_namespace_is_reserved__10_formatter__main.snap @@ -1,8 +1,8 @@ --- source: crates/baml_tests/src/generated_tests.rs --- -// A directory `ns_root/` would make `root.root.X` a path: the namespace is -// refused like every other name spelled `root`. +// A namespace takes its name from its directory, so `ns_root/` would name a +// namespace with the keyword `root`: refused like any other keyword name. function main() -> int { 1 diff --git a/baml_language/crates/baml_tests/src/compiler2_tir/phase8_exceptions.rs b/baml_language/crates/baml_tests/src/compiler2_tir/phase8_exceptions.rs index 9fe6c1a26c8..2d6f35d5f5d 100644 --- a/baml_language/crates/baml_tests/src/compiler2_tir/phase8_exceptions.rs +++ b/baml_language/crates/baml_tests/src/compiler2_tir/phase8_exceptions.rs @@ -1,6 +1,6 @@ //! Phase 8 tests: catch/throw/throws + match parity in compiler2 TIR. -// Native ns_exceptions, ns_lambdas, and ns_defer suites cover successful panic +// Native ns_exceptions, ns_lambdas, and ns_defer_blocks suites cover successful panic // field access, inferred throwing lambdas, and throwing defer bodies. use super::support::{make_db, render_tir}; use crate::engine::TestDbExt; diff --git a/baml_language/crates/baml_type/Cargo.toml b/baml_language/crates/baml_type/Cargo.toml index b327627baea..88445d54c86 100644 --- a/baml_language/crates/baml_type/Cargo.toml +++ b/baml_language/crates/baml_type/Cargo.toml @@ -6,6 +6,7 @@ rust-version.workspace = true [dependencies] baml_base = { workspace = true } +baml_compiler_lexer = { workspace = true } baml_type_macros = { workspace = true } bitflags = { workspace = true } borsh = { workspace = true } diff --git a/baml_language/crates/baml_type/src/lib.rs b/baml_language/crates/baml_type/src/lib.rs index 55a97d83bb1..d8b94a345cd 100644 --- a/baml_language/crates/baml_type/src/lib.rs +++ b/baml_language/crates/baml_type/src/lib.rs @@ -47,6 +47,7 @@ mod param; pub mod pattern_overlap; mod primitive; mod realized_ty; +pub mod reserved; mod runtime_ty; pub mod simplify_sap; mod structural_interface; diff --git a/baml_language/crates/baml_type/src/reserved.rs b/baml_language/crates/baml_type/src/reserved.rs new file mode 100644 index 00000000000..93ac99549b1 --- /dev/null +++ b/baml_language/crates/baml_type/src/reserved.rs @@ -0,0 +1,29 @@ +//! The names no user declaration may take. +//! +//! Keywords come from the lexer and built-in type names from the compiler +//! alias table, so neither list is written down twice. Callers exempt the +//! language packages: the stdlib declares `t.implements(u)`, the +//! `baml.spawn` namespace, and the `json` type alias itself. + +/// Why a name is reserved. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ReservedName { + /// A lexer keyword (`if`, `self`, `root`, ...). + Keyword, + /// A name the type lowering reads as a built-in type (`string`, `map`, + /// ...), so a declaration of it could never be named. + BuiltinType, +} + +impl ReservedName { + /// Why `name` cannot name a declaration in the type or item namespace. + pub fn of(name: &str) -> Option { + if baml_compiler_lexer::is_keyword(name) { + Some(ReservedName::Keyword) + } else if crate::compiler_aliases::by_spelling(name).is_some() { + Some(ReservedName::BuiltinType) + } else { + None + } + } +} diff --git a/baml_language/crates/bex_engine/src/telemetry/failure_tests.rs b/baml_language/crates/bex_engine/src/telemetry/failure_tests.rs index 69d8232ef9f..ab7b1cc83ce 100644 --- a/baml_language/crates/bex_engine/src/telemetry/failure_tests.rs +++ b/baml_language/crates/bex_engine/src/telemetry/failure_tests.rs @@ -9,7 +9,9 @@ fn nz(n: usize) -> NonZeroUsize { NonZeroUsize::new(n).unwrap() } +/// `root` holds the delivery's shared CAS; it must outlive the engine. fn engine_with_writer( + root: &std::path::Path, source: &str, write: impl FnMut(&SealedFile) -> Result<(), btel_file::LocalDeliveryError> + Send + 'static, ) -> Arc { @@ -34,7 +36,7 @@ fn engine_with_writer( |control| { let failure = control; let writer = btel_file::LocalDelivery::with_test_writer( - PathBuf::new(), + root.to_path_buf(), btel_file::LocalDeliveryConfig::default(), write, move |error| failure.disable(btel_processor::RuntimeError(error.to_string())), @@ -74,7 +76,9 @@ async fn disk_error_with_full_file_queue_and_chunk_pool_does_not_fail_applicatio let error = btel_file::LocalDeliveryError(std::io::Error::from_raw_os_error(errno).to_string()); let expected = error.to_string(); + let root = tempfile::tempdir().unwrap(); let engine = engine_with_writer( + root.path(), r#" function leaf(n: int) -> int { n + 1 } function main() -> int { @@ -168,7 +172,9 @@ async fn cancelling_shutdown_does_not_reopen_a_closed_transport() { let mut first = true; // A live manual owner also exercises terminal deferred publication without // reserving another producer from this deliberately tiny worker pool. + let root = tempfile::tempdir().unwrap(); let engine = engine_with_writer( + root.path(), r#"function main() -> int { let span = trace.start_span("pending"); 1 }"#, move |_| { if std::mem::take(&mut first) { diff --git a/baml_language/crates/bex_vm/src/package_baml/spawn.rs b/baml_language/crates/bex_vm/src/package_baml/spawn.rs index a981cb70db9..21736471d46 100644 --- a/baml_language/crates/bex_vm/src/package_baml/spawn.rs +++ b/baml_language/crates/bex_vm/src/package_baml/spawn.rs @@ -229,7 +229,7 @@ pub fn spawn_launch(vm: &BexVm, plan: Value) -> Result = Vec::new(); for (export, value) in types { let export = export.to_string(); - if !super::type_kinds::is_baml_identifier(&export) { + if !super::type_kinds::is_type_name(&export) { let diagnostic = runtime_type::invalid_identifier(InvalidIdentifierKind::ExportedType, &export) .with_phase(DiagnosticPhase::Hir); diff --git a/baml_language/crates/bex_vm/src/package_reflect/runtime_class_builder.rs b/baml_language/crates/bex_vm/src/package_reflect/runtime_class_builder.rs index 1d7132d232d..1093f99609f 100644 --- a/baml_language/crates/bex_vm/src/package_reflect/runtime_class_builder.rs +++ b/baml_language/crates/bex_vm/src/package_reflect/runtime_class_builder.rs @@ -28,9 +28,9 @@ use super::{ BamlClassClassBuilder, BamlClassClassPendingType, PackageReflectImpl, type_kinds::{ ReflectedTypeRow, WitnessField, alloc_compilation_error, alloc_with_meta, - compiler_diagnostic, is_baml_identifier, prepare_class_witnesses, reflected_class_fields, - reflected_type_row, register_class_witnesses, string_map_rows, validate_class_witnesses, - with_meta_row, + compiler_diagnostic, is_baml_identifier, is_type_name, prepare_class_witnesses, + reflected_class_fields, reflected_type_row, register_class_witnesses, string_map_rows, + validate_class_witnesses, with_meta_row, }, }; use crate::{BexVm, errors::VmRustFnError}; @@ -642,7 +642,7 @@ fn prepare_group( let mut prepared = IndexMap::new(); for node in group.values() { - if !is_baml_identifier(&node.name) { + if !is_type_name(&node.name) { diagnostics.push( runtime_type::invalid_identifier(InvalidIdentifierKind::Class, &node.name) .with_phase(DiagnosticPhase::Hir), diff --git a/baml_language/crates/bex_vm/src/package_reflect/type_kinds.rs b/baml_language/crates/bex_vm/src/package_reflect/type_kinds.rs index 466050cf272..c030cb9ba03 100644 --- a/baml_language/crates/bex_vm/src/package_reflect/type_kinds.rs +++ b/baml_language/crates/bex_vm/src/package_reflect/type_kinds.rs @@ -467,7 +467,7 @@ fn mint_class( witnesses: Vec>, ) -> Result { let mut diagnostics = Vec::new(); - if !is_baml_identifier(class_name) { + if !is_type_name(class_name) { diagnostics.push( runtime_type::invalid_identifier(InvalidIdentifierKind::Class, class_name) .with_phase(DiagnosticPhase::Hir), @@ -975,7 +975,7 @@ impl BamlClassEnum for PackageReflectImpl { ) -> Result { let enum_name = name.as_str(); let mut diagnostics = Vec::new(); - if !is_baml_identifier(enum_name) { + if !is_type_name(enum_name) { diagnostics.push( runtime_type::invalid_identifier(InvalidIdentifierKind::Enum, enum_name) .with_phase(DiagnosticPhase::Hir), @@ -1862,6 +1862,12 @@ pub(super) fn is_baml_identifier(value: &str) -> bool { runtime_type::is_baml_identifier(value) } +/// Whether a runtime type may take the name `value`: an identifier a source +/// declaration could use ([`baml_type::reserved::ReservedName::of`]). +pub(super) fn is_type_name(value: &str) -> bool { + is_baml_identifier(value) && baml_type::reserved::ReservedName::of(value).is_none() +} + pub(crate) fn compiler_diagnostic(id: DiagnosticId, message: String) -> Diagnostic { Diagnostic::error(id, message).with_phase(DiagnosticPhase::Hir) } diff --git a/baml_language/sdk_tests/crates/cpp/type_shapes/customizable/tests/test_void.cc b/baml_language/sdk_tests/crates/cpp/type_shapes/customizable/tests/test_void.cc index 4e80d80fd2b..e28a68bff19 100644 --- a/baml_language/sdk_tests/crates/cpp/type_shapes/customizable/tests/test_void.cc +++ b/baml_language/sdk_tests/crates/cpp/type_shapes/customizable/tests/test_void.cc @@ -4,6 +4,6 @@ #include BAML_TEST(void_no_op) { - baml_sdk::void_::no_op(); // returns void; completing without throwing is the + baml_sdk::unit::no_op(); // returns void; completing without throwing is the // assertion } diff --git a/baml_language/sdk_tests/crates/go/type_shapes/customizable/go_codegen_edges_test.go b/baml_language/sdk_tests/crates/go/type_shapes/customizable/go_codegen_edges_test.go index 7b166b92b37..84cc619bdc2 100644 --- a/baml_language/sdk_tests/crates/go/type_shapes/customizable/go_codegen_edges_test.go +++ b/baml_language/sdk_tests/crates/go/type_shapes/customizable/go_codegen_edges_test.go @@ -42,7 +42,7 @@ var ( _ func(context.Context, bool, ...baml_go.CallOption) (bool, error) = baml_sdk.LiteralsRoundTripLiteralTrue _ func(context.Context, bool, ...baml_go.CallOption) (bool, error) = baml_sdk.LiteralsRoundTripLiteralFalse - _ func(context.Context, ...baml_go.CallOption) error = baml_sdk.VoidNoOp + _ func(context.Context, ...baml_go.CallOption) error = baml_sdk.UnitNoOp _ func(context.Context, string, ...baml_go.CallOption) (string, error) = baml_sdk.GoCodegenLeftEcho _ func(context.Context, string, ...baml_go.CallOption) (string, error) = baml_sdk.GoCodegenRightEcho diff --git a/baml_language/sdk_tests/crates/go/type_shapes/customizable/roundtrip_tests/test_void_test.go b/baml_language/sdk_tests/crates/go/type_shapes/customizable/roundtrip_tests/test_void_test.go index 0371519eaf9..a8a53282a38 100644 --- a/baml_language/sdk_tests/crates/go/type_shapes/customizable/roundtrip_tests/test_void_test.go +++ b/baml_language/sdk_tests/crates/go/type_shapes/customizable/roundtrip_tests/test_void_test.go @@ -9,7 +9,7 @@ import ( // Direct port of python_pydantic2/type_shapes/roundtrip_tests/test_void.py. func Test_no_op(t *testing.T) { - if err := baml_sdk.VoidNoOp(context.Background()); err != nil { + if err := baml_sdk.UnitNoOp(context.Background()); err != nil { t.Fatal(err) } } diff --git a/baml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestVoid.java b/baml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestVoid.java index c623ff78d02..d2fd4d1e94f 100644 --- a/baml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestVoid.java +++ b/baml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestVoid.java @@ -21,6 +21,6 @@ void test_void_no_op() { // beyond the doc and needs confirmation. // java-port note: BAML `-> void` maps to Java `void` (Python gets None); // the strongest expressible assertion is that the call completes. - org.junit.jupiter.api.Assertions.assertDoesNotThrow(() -> baml_sdk.void$.Fns.no_op()); + org.junit.jupiter.api.Assertions.assertDoesNotThrow(() -> baml_sdk.unit.Fns.no_op()); } } diff --git a/baml_language/sdk_tests/crates/python_pydantic2/type_shapes/customizable/roundtrip_tests/test_void.py b/baml_language/sdk_tests/crates/python_pydantic2/type_shapes/customizable/roundtrip_tests/test_void.py index 7cf86a42e17..e10abf3e9ec 100644 --- a/baml_language/sdk_tests/crates/python_pydantic2/type_shapes/customizable/roundtrip_tests/test_void.py +++ b/baml_language/sdk_tests/crates/python_pydantic2/type_shapes/customizable/roundtrip_tests/test_void.py @@ -1,7 +1,7 @@ """Roundtrip coverage for `baml_sdk.void` — `void` return lowers to `None`.""" import baml_sdk # noqa: F401 — initializes the BAML runtime -from baml_sdk.void import no_op +from baml_sdk.unit import no_op def test_void_no_op(): diff --git a/baml_language/sdk_tests/crates/rust/type_shapes/customizable/roundtrip_tests/test_void.rs b/baml_language/sdk_tests/crates/rust/type_shapes/customizable/roundtrip_tests/test_void.rs index 559c25c4de2..06b26d23906 100644 --- a/baml_language/sdk_tests/crates/rust/type_shapes/customizable/roundtrip_tests/test_void.rs +++ b/baml_language/sdk_tests/crates/rust/type_shapes/customizable/roundtrip_tests/test_void.rs @@ -1,6 +1,6 @@ //! Roundtrip coverage for `baml_sdk::void` — `void` return lowers to `()`. -use baml_sdk::void::no_op; +use baml_sdk::unit::no_op; #[test] fn test_void_no_op() { diff --git a/baml_language/sdk_tests/crates/swift/type_shapes/customizable/roundtrip_tests/TestVoid.swift b/baml_language/sdk_tests/crates/swift/type_shapes/customizable/roundtrip_tests/TestVoid.swift index 94b68c44084..c4aec2076a4 100644 --- a/baml_language/sdk_tests/crates/swift/type_shapes/customizable/roundtrip_tests/TestVoid.swift +++ b/baml_language/sdk_tests/crates/swift/type_shapes/customizable/roundtrip_tests/TestVoid.swift @@ -5,6 +5,6 @@ import Baml final class TestVoid: XCTestCase { func test_void_no_op() throws { - try Baml.void.no_op() + try Baml.unit.no_op() } } diff --git a/baml_language/sdk_tests/crates/typescript/type_shapes/customizable/roundtrip_void.test.ts b/baml_language/sdk_tests/crates/typescript/type_shapes/customizable/roundtrip_void.test.ts index a29dac46faf..cc04a1cce42 100644 --- a/baml_language/sdk_tests/crates/typescript/type_shapes/customizable/roundtrip_void.test.ts +++ b/baml_language/sdk_tests/crates/typescript/type_shapes/customizable/roundtrip_void.test.ts @@ -1,7 +1,7 @@ // Roundtrip coverage for baml_sdk/void — ported from test_void.py. import "./baml_sdk/index.js"; import { describe, it, expect } from "vitest"; -import { no_op } from "./baml_sdk/void/index.js"; +import { no_op } from "./baml_sdk/unit/index.js"; describe("roundtrip void", () => { it("void_no_op", () => expect(no_op()).toBeNull()); diff --git a/baml_language/sdk_tests/fixtures/function_calls/baml_src/ns_raises_test/types.baml b/baml_language/sdk_tests/fixtures/function_calls/baml_src/ns_raises_test/types.baml index 82e0a191c29..8c44c29ccc6 100644 --- a/baml_language/sdk_tests/fixtures/function_calls/baml_src/ns_raises_test/types.baml +++ b/baml_language/sdk_tests/fixtures/function_calls/baml_src/ns_raises_test/types.baml @@ -37,7 +37,7 @@ function PureLen(s: string) -> int { /// Loads documents, with failure modes on its methods. class DocLoader { - root string + base_dir string /// Load the document at `path`. function load(self, path: string) -> string throws ParseError { diff --git a/baml_language/sdk_tests/fixtures/type_shapes/baml_src/ns_void/types.baml b/baml_language/sdk_tests/fixtures/type_shapes/baml_src/ns_unit/types.baml similarity index 100% rename from baml_language/sdk_tests/fixtures/type_shapes/baml_src/ns_void/types.baml rename to baml_language/sdk_tests/fixtures/type_shapes/baml_src/ns_unit/types.baml diff --git a/baml_language/sdks/typescript/bridge_typescript/dist/proto/baml_cffi.js b/baml_language/sdks/typescript/bridge_typescript/dist/proto/baml_cffi.js index 7a5ff3e797f..d3fb7b3ea4f 100644 --- a/baml_language/sdks/typescript/bridge_typescript/dist/proto/baml_cffi.js +++ b/baml_language/sdks/typescript/bridge_typescript/dist/proto/baml_cffi.js @@ -306,7 +306,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ InboundValue.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -896,7 +896,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ InboundListValue.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -1144,7 +1144,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ InboundMapValue.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -1448,7 +1448,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ InboundMapEntry.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -1771,7 +1771,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ InboundClassValue.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -2028,7 +2028,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ InboundEnumValue.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -2290,7 +2290,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyArg.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -2625,7 +2625,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ CallFunctionArgs.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -3050,7 +3050,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ InvocationOptions.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -3410,7 +3410,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ TraceSelection.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -3769,7 +3769,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ TraceOptions.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -4227,7 +4227,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ TraceMetadataValue.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -4556,7 +4556,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ CallAck.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -4844,7 +4844,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlHandle.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -5444,7 +5444,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTy.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -6224,7 +6224,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyDef.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -6590,7 +6590,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlWitnessDef.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -6923,7 +6923,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlWitnessFieldLink.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -7219,7 +7219,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyMetadata.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -7571,7 +7571,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlClassDef.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -7893,7 +7893,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlClassFieldDef.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -8191,7 +8191,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlEnumDef.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -8490,7 +8490,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlEnumVariantDef.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -8773,7 +8773,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyPrimitive.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -9062,7 +9062,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyClass.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -9334,7 +9334,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyTypeAlias.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -9593,7 +9593,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyEnum.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -9820,7 +9820,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyList.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -10063,7 +10063,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyMap.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -10313,7 +10313,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyOptional.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -10547,7 +10547,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyUnion.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -10782,7 +10782,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyUnknown.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -11046,7 +11046,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyLiteral.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -11383,7 +11383,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyMedia.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -11675,7 +11675,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyInterface.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -11977,7 +11977,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyAssociatedBinding.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -12233,7 +12233,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyEnumVariant.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -12520,7 +12520,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyFunctionParam.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -12843,7 +12843,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyFunction.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -13163,7 +13163,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyFuture.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -13402,7 +13402,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyRustType.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -13597,7 +13597,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyMetaType.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -13792,7 +13792,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyResource.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -13987,7 +13987,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyPromptAst.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -14204,7 +14204,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyTypeVar.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -14466,7 +14466,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyAssociatedTypeProjection.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -14717,7 +14717,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyNever.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -14959,7 +14959,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlOutboundResult.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -15257,7 +15257,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlOutboundError.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -15551,7 +15551,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlOutboundPanic.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -16052,7 +16052,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlOutboundValue.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -16698,7 +16698,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlOutboundHandle.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -17059,7 +17059,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlValueNull.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -17278,7 +17278,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlValueList.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -17553,7 +17553,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlOutboundMapEntry.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -17822,7 +17822,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlValueMap.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -18130,7 +18130,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlValueClass.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -18443,7 +18443,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlValueEnum.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -18770,7 +18770,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlValueUnionVariant.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -19171,7 +19171,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlValueMedia.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -19523,7 +19523,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlValueMediaFileContent.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -19799,7 +19799,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlValuePromptAst.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -20106,7 +20106,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlValuePromptAstMessage.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -20365,7 +20365,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlValuePromptAstMultiple.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -20647,7 +20647,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlValuePromptAstSimple.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -20928,7 +20928,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlValuePromptAstSimpleMultiple.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -21176,7 +21176,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlToHostCall.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -21497,7 +21497,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ HostInvocation.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -21898,7 +21898,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlToHostArg.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -22213,7 +22213,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlLiteralValue.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** diff --git a/baml_language/sdks/typescript/bridge_typescript/typescript_src/proto/baml_cffi.js b/baml_language/sdks/typescript/bridge_typescript/typescript_src/proto/baml_cffi.js index 7a5ff3e797f..d3fb7b3ea4f 100644 --- a/baml_language/sdks/typescript/bridge_typescript/typescript_src/proto/baml_cffi.js +++ b/baml_language/sdks/typescript/bridge_typescript/typescript_src/proto/baml_cffi.js @@ -306,7 +306,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ InboundValue.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -896,7 +896,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ InboundListValue.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -1144,7 +1144,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ InboundMapValue.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -1448,7 +1448,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ InboundMapEntry.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -1771,7 +1771,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ InboundClassValue.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -2028,7 +2028,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ InboundEnumValue.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -2290,7 +2290,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyArg.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -2625,7 +2625,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ CallFunctionArgs.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -3050,7 +3050,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ InvocationOptions.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -3410,7 +3410,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ TraceSelection.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -3769,7 +3769,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ TraceOptions.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -4227,7 +4227,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ TraceMetadataValue.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -4556,7 +4556,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ CallAck.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -4844,7 +4844,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlHandle.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -5444,7 +5444,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTy.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -6224,7 +6224,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyDef.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -6590,7 +6590,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlWitnessDef.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -6923,7 +6923,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlWitnessFieldLink.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -7219,7 +7219,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyMetadata.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -7571,7 +7571,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlClassDef.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -7893,7 +7893,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlClassFieldDef.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -8191,7 +8191,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlEnumDef.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -8490,7 +8490,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlEnumVariantDef.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -8773,7 +8773,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyPrimitive.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -9062,7 +9062,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyClass.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -9334,7 +9334,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyTypeAlias.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -9593,7 +9593,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyEnum.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -9820,7 +9820,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyList.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -10063,7 +10063,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyMap.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -10313,7 +10313,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyOptional.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -10547,7 +10547,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyUnion.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -10782,7 +10782,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyUnknown.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -11046,7 +11046,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyLiteral.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -11383,7 +11383,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyMedia.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -11675,7 +11675,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyInterface.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -11977,7 +11977,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyAssociatedBinding.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -12233,7 +12233,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyEnumVariant.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -12520,7 +12520,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyFunctionParam.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -12843,7 +12843,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyFunction.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -13163,7 +13163,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyFuture.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -13402,7 +13402,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyRustType.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -13597,7 +13597,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyMetaType.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -13792,7 +13792,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyResource.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -13987,7 +13987,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyPromptAst.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -14204,7 +14204,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyTypeVar.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -14466,7 +14466,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyAssociatedTypeProjection.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -14717,7 +14717,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlTyNever.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -14959,7 +14959,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlOutboundResult.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -15257,7 +15257,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlOutboundError.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -15551,7 +15551,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlOutboundPanic.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -16052,7 +16052,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlOutboundValue.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -16698,7 +16698,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlOutboundHandle.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -17059,7 +17059,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlValueNull.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -17278,7 +17278,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlValueList.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -17553,7 +17553,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlOutboundMapEntry.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -17822,7 +17822,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlValueMap.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -18130,7 +18130,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlValueClass.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -18443,7 +18443,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlValueEnum.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -18770,7 +18770,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlValueUnionVariant.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -19171,7 +19171,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlValueMedia.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -19523,7 +19523,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlValueMediaFileContent.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -19799,7 +19799,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlValuePromptAst.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -20106,7 +20106,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlValuePromptAstMessage.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -20365,7 +20365,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlValuePromptAstMultiple.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -20647,7 +20647,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlValuePromptAstSimple.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -20928,7 +20928,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlValuePromptAstSimpleMultiple.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -21176,7 +21176,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlToHostCall.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -21497,7 +21497,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ HostInvocation.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -21898,7 +21898,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlToHostArg.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** @@ -22213,7 +22213,7 @@ export const baml_bridge = $root.baml_bridge = (() => { * @returns {$protobuf.Writer} Writer */ BamlLiteralValue.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); + return this.encode(message, writer && writer.len ? writer.fork() : writer).ldelim(); }; /** diff --git a/baml_language/tools/type_quiz/ns_algebra/ty.baml b/baml_language/tools/type_quiz/ns_algebra/ty.baml index 8a8b3300a33..cca01d6f627 100644 --- a/baml_language/tools/type_quiz/ns_algebra/ty.baml +++ b/baml_language/tools/type_quiz/ns_algebra/ty.baml @@ -482,7 +482,7 @@ class AliasDecl { /// Declare a recursive alias. `body` receives the alias's own type so it /// can refer back to it: - /// `AliasDecl.recursive("Rec", (self) -> { Union.new([Prim.int(), Array.new(self)]) })`. + /// `AliasDecl.recursive("Rec", (rec) -> { Union.new([Prim.int(), Array.new(rec)]) })`. function recursive(name: string, body: (Ty) -> Ty throws never) -> AliasDecl throws never { let decl = AliasDecl { name, body: Never {} }; decl.body = body(Alias { decl }); diff --git a/baml_language/tools/type_quiz/ns_bank/facts.baml b/baml_language/tools/type_quiz/ns_bank/facts.baml index d818e4b6ea4..0a07408e795 100644 --- a/baml_language/tools/type_quiz/ns_bank/facts.baml +++ b/baml_language/tools/type_quiz/ns_bank/facts.baml @@ -581,8 +581,8 @@ function alias_is_transparent(seed: root.engine.Seed) -> Derived throws never { /// `type A = leaf | A[]` under a seeded name. function list_alias(name: string, leaf: root.algebra.Ty) -> root.algebra.AliasDecl throws never { - root.algebra.AliasDecl.recursive(name, (self) -> { - root.algebra.Union.new([leaf, root.algebra.Array.new(self)]) + root.algebra.AliasDecl.recursive(name, (rec) -> { + root.algebra.Union.new([leaf, root.algebra.Array.new(rec)]) }) } @@ -620,11 +620,11 @@ function unfolds_once(seed: root.engine.Seed) -> Derived throws never { function unguarded_member_is_vacuous(seed: root.engine.Seed) -> Derived throws never { let names = root.engine.pick_distinct(seed, type_names(), 2); - let with_member = root.algebra.AliasDecl.recursive(names.value[0], (self) -> { - root.algebra.Union.new([self, root.algebra.Array.new(self)]) + let with_member = root.algebra.AliasDecl.recursive(names.value[0], (rec) -> { + root.algebra.Union.new([rec, root.algebra.Array.new(rec)]) }); - let plain = root.algebra.AliasDecl.recursive(names.value[1], (self) -> { - root.algebra.Array.new(self) + let plain = root.algebra.AliasDecl.recursive(names.value[1], (rec) -> { + root.algebra.Array.new(rec) }); derived( root.algebra.pair( @@ -723,11 +723,11 @@ function unfolded_with_wrong_leaf(seed: root.engine.Seed) -> Derived throws neve function vacuous_beside_guarded(seed: root.engine.Seed) -> Derived throws never { let l = pick_leaves(seed, 1); let names = root.engine.pick_distinct(l.next, type_names(), 2); - let with_leaf = root.algebra.AliasDecl.recursive(names.value[0], (self) -> { - root.algebra.Union.new([self, root.algebra.Array.new(l.value[0])]) + let with_leaf = root.algebra.AliasDecl.recursive(names.value[0], (rec) -> { + root.algebra.Union.new([rec, root.algebra.Array.new(l.value[0])]) }); - let plain = root.algebra.AliasDecl.recursive(names.value[1], (self) -> { - root.algebra.Array.new(self) + let plain = root.algebra.AliasDecl.recursive(names.value[1], (rec) -> { + root.algebra.Array.new(rec) }); derived( root.algebra.pair( diff --git a/baml_language/tools/type_quiz/ns_conformance/algebra.baml b/baml_language/tools/type_quiz/ns_conformance/algebra.baml index 8884bb13355..4547163a399 100644 --- a/baml_language/tools/type_quiz/ns_conformance/algebra.baml +++ b/baml_language/tools/type_quiz/ns_conformance/algebra.baml @@ -49,8 +49,8 @@ function lorem_decl() -> root.algebra.IfaceDecl throws never { } function rec_decl() -> root.algebra.AliasDecl throws never { - root.algebra.AliasDecl.recursive("Rec", (self) -> { - root.algebra.Union.new([root.algebra.Prim.int(), root.algebra.Array.new(self)]) + root.algebra.AliasDecl.recursive("Rec", (rec) -> { + root.algebra.Union.new([root.algebra.Prim.int(), root.algebra.Array.new(rec)]) }) } diff --git a/tools/stdlib-matrix/baml_src/models.baml b/tools/stdlib-matrix/baml_src/models.baml index f2442d8c0c0..b29f5c1dbc1 100644 --- a/tools/stdlib-matrix/baml_src/models.baml +++ b/tools/stdlib-matrix/baml_src/models.baml @@ -124,15 +124,25 @@ class ExportImplMethod { from_default: bool?, } -/// A top-level impl record. `interface` is a declarable field name, but the -/// parser rejects it in struct-literal position, so this class can only be -/// built by JSON decoding — which is how it always arrives (tests build -/// fixtures as `json` literals decoded with `baml.json.to` for the same reason). +/// A top-level impl record. The field is `iface` in BAML and `interface` on +/// the wire, since `interface` is a keyword; the override below reads that key +/// the same way `ExportSignature` reads `throws`. class ExportImpl { id: string, - interface: string, + iface: string, for_ty: TyRef, methods: ExportImplMethod[], + + implements baml.FromJson { + function from_json(j: baml.json.json) -> Self throws baml.json.DecodeError { + ExportImpl { + id: baml.json.to(baml.json.field(j, "id")), + iface: baml.json.to(baml.json.field(j, "interface")), + for_ty: baml.json.to(baml.json.field(j, "for_ty")), + methods: baml.json.to(baml.json.field(j, "methods")), + } + } + } } class PackageExportDoc { diff --git a/tools/stdlib-matrix/baml_src/symbols.baml b/tools/stdlib-matrix/baml_src/symbols.baml index ad74ddd5dac..b4d6156673f 100644 --- a/tools/stdlib-matrix/baml_src/symbols.baml +++ b/tools/stdlib-matrix/baml_src/symbols.baml @@ -387,7 +387,7 @@ function extract_symbols(doc: PackageExportDoc) -> BamlSymbol[] { } for (let impl_record in doc.impls) { - let step = ImplStep { base: impl_record.for_ty.display, iface: impl_record.interface.split(".") }; + let step = ImplStep { base: impl_record.for_ty.display, iface: impl_record.iface.split(".") }; for (let method in impl_record.methods) { if (method.name.includes("$")) { continue; @@ -399,7 +399,7 @@ function extract_symbols(doc: PackageExportDoc) -> BamlSymbol[] { id: method.id, declared_by: method.declared_by, symbol: impl_path, - display: `(${impl_record.for_ty.display} as ${impl_record.interface}).${method.name}`, + display: `(${impl_record.for_ty.display} as ${impl_record.iface}).${method.name}`, kind: "function", origin: "impl_method", generics: [], diff --git a/typescript2/pkg-grammar-treesitter/grammar.js b/typescript2/pkg-grammar-treesitter/grammar.js index b9dd6113e8e..6b71a35efa0 100644 --- a/typescript2/pkg-grammar-treesitter/grammar.js +++ b/typescript2/pkg-grammar-treesitter/grammar.js @@ -32,7 +32,7 @@ const PREC = { BIT_XOR: 6, // ^ BIT_AND: 7, // & EQUALITY: 8, // == != - COMPARE: 9, // < > <= >= instanceof is + COMPARE: 9, // < > <= >= is SHIFT: 10, // << >> ADD: 11, // + - MUL: 12, // * / % @@ -1085,7 +1085,6 @@ module.exports = grammar({ ['>', PREC.COMPARE], ['<=', PREC.COMPARE], ['>=', PREC.COMPARE], - ['instanceof', PREC.COMPARE], ['<<', PREC.SHIFT], ['>>', PREC.SHIFT], ['+', PREC.ADD], diff --git a/typescript2/pkg-grammar-treesitter/queries/highlights.scm b/typescript2/pkg-grammar-treesitter/queries/highlights.scm index 7d3a8967455..a7507424011 100644 --- a/typescript2/pkg-grammar-treesitter/queries/highlights.scm +++ b/typescript2/pkg-grammar-treesitter/queries/highlights.scm @@ -94,7 +94,6 @@ [ "is" - "instanceof" ] @keyword.operator ; ---------------------------------------------------------------------------