harn_parser/ast.rs
1use harn_lexer::{Span, StringSegment};
2
3/// A node wrapped with source location information.
4#[derive(Debug, Clone, PartialEq)]
5pub struct Spanned<T> {
6 pub node: T,
7 pub span: Span,
8}
9
10impl<T> Spanned<T> {
11 pub fn new(node: T, span: Span) -> Self {
12 Self { node, span }
13 }
14
15 pub fn dummy(node: T) -> Self {
16 Self {
17 node,
18 span: Span::dummy(),
19 }
20 }
21}
22
23/// A spanned AST node — the primary unit throughout the compiler.
24pub type SNode = Spanned<Node>;
25
26/// Helper to wrap a node with a span.
27pub fn spanned(node: Node, span: Span) -> SNode {
28 SNode::new(node, span)
29}
30
31/// If `node` is an `AttributedDecl`, returns `(attrs, inner)`; otherwise
32/// returns an empty attribute slice and the node itself. Use at the top
33/// of any consumer that processes top-level statements so attributes
34/// flow through transparently.
35pub fn peel_attributes(node: &SNode) -> (&[Attribute], &SNode) {
36 match &node.node {
37 Node::AttributedDecl { attributes, inner } => (attributes.as_slice(), inner.as_ref()),
38 _ => (&[], node),
39 }
40}
41
42/// A single argument to an attribute. Positional args have `name = None`;
43/// named args use `name: Some("key")`. Values are restricted to
44/// compile-time metadata expressions by the parser (literal scalars,
45/// identifiers, lists, dicts, and call-shaped sentinels).
46#[derive(Debug, Clone, PartialEq)]
47pub struct AttributeArg {
48 pub name: Option<String>,
49 pub value: SNode,
50 pub span: Span,
51}
52
53/// An attribute attached to a declaration: `@deprecated(since: "0.8")`.
54#[derive(Debug, Clone, PartialEq)]
55pub struct Attribute {
56 pub name: String,
57 pub args: Vec<AttributeArg>,
58 pub span: Span,
59}
60
61impl Attribute {
62 /// Find a named argument by key.
63 pub fn named_arg(&self, key: &str) -> Option<&SNode> {
64 self.args
65 .iter()
66 .find(|a| a.name.as_deref() == Some(key))
67 .map(|a| &a.value)
68 }
69
70 /// First positional argument, if any.
71 pub fn positional(&self, idx: usize) -> Option<&SNode> {
72 self.args
73 .iter()
74 .filter(|a| a.name.is_none())
75 .nth(idx)
76 .map(|a| &a.value)
77 }
78
79 /// Convenience: extract a string-literal arg by name.
80 pub fn string_arg(&self, key: &str) -> Option<String> {
81 match self.named_arg(key).map(|n| &n.node) {
82 Some(Node::StringLiteral(s)) => Some(s.clone()),
83 Some(Node::RawStringLiteral(s)) => Some(s.clone()),
84 _ => None,
85 }
86 }
87}
88
89/// AST nodes for the Harn language.
90#[derive(Debug, Clone, PartialEq)]
91pub enum Node {
92 /// A declaration carrying one or more attributes (`@attr`). The inner
93 /// node is always one of: FnDecl, ToolDecl, Pipeline, StructDecl,
94 /// EnumDecl, TypeDecl, InterfaceDecl, ImplBlock.
95 AttributedDecl {
96 attributes: Vec<Attribute>,
97 inner: Box<SNode>,
98 },
99 Pipeline {
100 name: String,
101 params: Vec<String>,
102 return_type: Option<TypeExpr>,
103 body: Vec<SNode>,
104 extends: Option<String>,
105 is_pub: bool,
106 },
107 LetBinding {
108 pattern: BindingPattern,
109 type_ann: Option<TypeExpr>,
110 value: Box<SNode>,
111 },
112 VarBinding {
113 pattern: BindingPattern,
114 type_ann: Option<TypeExpr>,
115 value: Box<SNode>,
116 },
117 OverrideDecl {
118 name: String,
119 params: Vec<String>,
120 body: Vec<SNode>,
121 },
122 ImportDecl {
123 path: String,
124 /// When true, the wildcard import is a re-export: every public symbol
125 /// from the target module becomes part of this module's public surface.
126 is_pub: bool,
127 },
128 /// Selective import: import { foo, bar } from "module"
129 SelectiveImport {
130 names: Vec<String>,
131 path: String,
132 /// When true, the listed names are re-exported as part of this
133 /// module's public surface.
134 is_pub: bool,
135 },
136 EnumDecl {
137 name: String,
138 type_params: Vec<TypeParam>,
139 variants: Vec<EnumVariant>,
140 is_pub: bool,
141 },
142 StructDecl {
143 name: String,
144 type_params: Vec<TypeParam>,
145 fields: Vec<StructField>,
146 is_pub: bool,
147 },
148 InterfaceDecl {
149 name: String,
150 type_params: Vec<TypeParam>,
151 associated_types: Vec<(String, Option<TypeExpr>)>,
152 methods: Vec<InterfaceMethod>,
153 },
154 /// Impl block: impl TypeName { fn method(self, ...) { ... } ... }
155 ImplBlock {
156 type_name: String,
157 methods: Vec<SNode>,
158 },
159
160 IfElse {
161 condition: Box<SNode>,
162 then_body: Vec<SNode>,
163 else_body: Option<Vec<SNode>>,
164 },
165 ForIn {
166 pattern: BindingPattern,
167 iterable: Box<SNode>,
168 body: Vec<SNode>,
169 },
170 MatchExpr {
171 value: Box<SNode>,
172 arms: Vec<MatchArm>,
173 },
174 WhileLoop {
175 condition: Box<SNode>,
176 body: Vec<SNode>,
177 },
178 Retry {
179 count: Box<SNode>,
180 body: Vec<SNode>,
181 },
182 /// Scoped cost-aware LLM routing block:
183 /// `cost_route { key: value ... body }`.
184 ///
185 /// Options are inherited by nested `llm_call` invocations unless a
186 /// call explicitly overrides the same option.
187 CostRoute {
188 options: Vec<(String, SNode)>,
189 body: Vec<SNode>,
190 },
191 ReturnStmt {
192 value: Option<Box<SNode>>,
193 },
194 TryCatch {
195 body: Vec<SNode>,
196 has_catch: bool,
197 error_var: Option<String>,
198 error_type: Option<TypeExpr>,
199 catch_body: Vec<SNode>,
200 finally_body: Option<Vec<SNode>>,
201 },
202 /// Try expression: try { body } — returns Result.Ok(value), an existing Result,
203 /// or Result.Err(error).
204 TryExpr {
205 body: Vec<SNode>,
206 },
207 FnDecl {
208 name: String,
209 type_params: Vec<TypeParam>,
210 params: Vec<TypedParam>,
211 return_type: Option<TypeExpr>,
212 where_clauses: Vec<WhereClause>,
213 body: Vec<SNode>,
214 is_pub: bool,
215 is_stream: bool,
216 },
217 ToolDecl {
218 name: String,
219 description: Option<String>,
220 params: Vec<TypedParam>,
221 return_type: Option<TypeExpr>,
222 body: Vec<SNode>,
223 is_pub: bool,
224 },
225 /// Top-level `skill NAME { ... }` declaration.
226 ///
227 /// Skills bundle metadata, tool references, MCP server lists, and
228 /// optional lifecycle hooks into a typed unit. Each body entry is a
229 /// `<field_name> <expression>` pair; the compiler lowers the decl to
230 /// `skill_define(skill_registry(), NAME, { field: expr, ... })` and
231 /// binds the resulting registry dict to `NAME`.
232 SkillDecl {
233 name: String,
234 fields: Vec<(String, SNode)>,
235 is_pub: bool,
236 },
237 /// Top-level `eval_pack NAME_OR_STRING { ... }` declaration.
238 ///
239 /// The compiler lowers fields into `eval_pack_manifest({ ... })` and
240 /// binds the normalized manifest to `binding_name`. Optional executable
241 /// body statements are only run when the declaration itself is executed
242 /// in script/block position; top-level pipeline preloading registers the
243 /// manifest data without running the body.
244 EvalPackDecl {
245 binding_name: String,
246 pack_id: String,
247 fields: Vec<(String, SNode)>,
248 body: Vec<SNode>,
249 summarize: Option<Vec<SNode>>,
250 is_pub: bool,
251 },
252 TypeDecl {
253 name: String,
254 type_params: Vec<TypeParam>,
255 type_expr: TypeExpr,
256 },
257 SpawnExpr {
258 body: Vec<SNode>,
259 },
260 /// Duration literal: 500ms, 5s, 30m, 2h, 1d, 1w
261 DurationLiteral(u64),
262 /// Range expression: `start to end` (inclusive) or `start to end exclusive` (half-open)
263 RangeExpr {
264 start: Box<SNode>,
265 end: Box<SNode>,
266 inclusive: bool,
267 },
268 /// Guard clause: guard condition else { body }
269 GuardStmt {
270 condition: Box<SNode>,
271 else_body: Vec<SNode>,
272 },
273 RequireStmt {
274 condition: Box<SNode>,
275 message: Option<Box<SNode>>,
276 },
277 /// Defer statement: defer { body } — runs body at scope exit.
278 DeferStmt {
279 body: Vec<SNode>,
280 },
281 /// Deadline block: deadline DURATION { body }
282 DeadlineBlock {
283 duration: Box<SNode>,
284 body: Vec<SNode>,
285 },
286 /// Yield expression: yields control to host, optionally with a value.
287 YieldExpr {
288 value: Option<Box<SNode>>,
289 },
290 /// Emit expression: emits one value from a `gen fn` stream.
291 EmitExpr {
292 value: Box<SNode>,
293 },
294 /// Mutex block: mutual exclusion for concurrent access.
295 MutexBlock {
296 body: Vec<SNode>,
297 },
298 /// Break out of a loop.
299 BreakStmt,
300 /// Continue to next loop iteration.
301 ContinueStmt,
302
303 /// First-class HITL primitive expression.
304 ///
305 /// Lexed as a reserved keyword (`request_approval`, `dual_control`,
306 /// `ask_user`, `escalate_to`), parsed at primary-expression position
307 /// as `keyword "(" args ")"`. Each arg is either positional
308 /// (`expr`) or named (`name: expr`).
309 ///
310 /// The compiler lowers this to a call to the matching async stdlib
311 /// builtin in `crates/harn-vm/src/stdlib/hitl.rs`, packaging the
312 /// named arguments into the existing options-dict shape. The
313 /// typechecker assigns each kind its canonical envelope return type.
314 HitlExpr {
315 kind: HitlKind,
316 args: Vec<HitlArg>,
317 },
318
319 Parallel {
320 mode: ParallelMode,
321 /// For Count mode: the count expression. For Each/Settle: the list expression.
322 expr: Box<SNode>,
323 variable: Option<String>,
324 body: Vec<SNode>,
325 /// Optional trailing `with { max_concurrent: N, ... }` option block.
326 /// A vec (rather than a dict) preserves source order for error
327 /// reporting and keeps parsing cheap. Only `max_concurrent` is
328 /// currently honored; unknown keys are rejected by the parser.
329 options: Vec<(String, SNode)>,
330 },
331
332 SelectExpr {
333 cases: Vec<SelectCase>,
334 timeout: Option<(Box<SNode>, Vec<SNode>)>,
335 default_body: Option<Vec<SNode>>,
336 },
337
338 FunctionCall {
339 name: String,
340 type_args: Vec<TypeExpr>,
341 args: Vec<SNode>,
342 },
343 MethodCall {
344 object: Box<SNode>,
345 method: String,
346 args: Vec<SNode>,
347 },
348 /// Optional method call: `obj?.method(args)` — returns nil if obj is nil.
349 OptionalMethodCall {
350 object: Box<SNode>,
351 method: String,
352 args: Vec<SNode>,
353 },
354 PropertyAccess {
355 object: Box<SNode>,
356 property: String,
357 },
358 /// Optional chaining: `obj?.property` — returns nil if obj is nil.
359 OptionalPropertyAccess {
360 object: Box<SNode>,
361 property: String,
362 },
363 SubscriptAccess {
364 object: Box<SNode>,
365 index: Box<SNode>,
366 },
367 /// Optional subscript: `obj?[index]` — returns nil if obj is nil.
368 OptionalSubscriptAccess {
369 object: Box<SNode>,
370 index: Box<SNode>,
371 },
372 SliceAccess {
373 object: Box<SNode>,
374 start: Option<Box<SNode>>,
375 end: Option<Box<SNode>>,
376 },
377 BinaryOp {
378 op: String,
379 left: Box<SNode>,
380 right: Box<SNode>,
381 },
382 UnaryOp {
383 op: String,
384 operand: Box<SNode>,
385 },
386 Ternary {
387 condition: Box<SNode>,
388 true_expr: Box<SNode>,
389 false_expr: Box<SNode>,
390 },
391 Assignment {
392 target: Box<SNode>,
393 value: Box<SNode>,
394 /// None = plain `=`, Some("+") = `+=`, etc.
395 op: Option<String>,
396 },
397 ThrowStmt {
398 value: Box<SNode>,
399 },
400
401 /// Enum variant construction: EnumName.Variant(args)
402 EnumConstruct {
403 enum_name: String,
404 variant: String,
405 args: Vec<SNode>,
406 },
407 /// Struct construction: StructName { field: value, ... }
408 StructConstruct {
409 struct_name: String,
410 fields: Vec<DictEntry>,
411 },
412
413 InterpolatedString(Vec<StringSegment>),
414 StringLiteral(String),
415 /// Raw string literal `r"..."` — no escape processing.
416 RawStringLiteral(String),
417 IntLiteral(i64),
418 FloatLiteral(f64),
419 BoolLiteral(bool),
420 NilLiteral,
421 Identifier(String),
422 ListLiteral(Vec<SNode>),
423 DictLiteral(Vec<DictEntry>),
424 /// Spread expression `...expr` inside list/dict literals.
425 Spread(Box<SNode>),
426 /// Try operator: expr? — unwraps Result.Ok or propagates Result.Err.
427 TryOperator {
428 operand: Box<SNode>,
429 },
430 /// Try-star operator: `try* EXPR` — evaluates EXPR; on throw, runs
431 /// pending finally blocks up to the enclosing catch and rethrows
432 /// the original value. On success, evaluates to EXPR's value.
433 /// Lowered per spec/HARN_SPEC.md as:
434 /// { let _r = try { EXPR }
435 /// guard is_ok(_r) else { throw unwrap_err(_r) }
436 /// unwrap(_r) }
437 TryStar {
438 operand: Box<SNode>,
439 },
440
441 /// Or-pattern in a `match` arm: `"ping" | "pong" -> body`. One or
442 /// more alternative patterns that share a single arm body. Only
443 /// legal inside a `MatchArm.pattern` slot.
444 OrPattern(Vec<SNode>),
445
446 Block(Vec<SNode>),
447 Closure {
448 params: Vec<TypedParam>,
449 body: Vec<SNode>,
450 /// When true, this closure was written as `fn(params) { body }`.
451 /// The formatter preserves this distinction.
452 fn_syntax: bool,
453 },
454}
455
456/// First-class human-in-the-loop primitive.
457///
458/// Each `HitlKind` is a reserved keyword expression with VM-enforced
459/// semantics: the names cannot be shadowed or rebound by user code,
460/// signatures are produced by the VM, and the audit log is recorded
461/// deterministically by the runtime.
462#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
463pub enum HitlKind {
464 /// `request_approval(action: ..., args: ..., quorum: ..., reviewers: ..., ...)`.
465 RequestApproval,
466 /// `dual_control(n: ..., m: ..., action: <closure>, approvers: ...)`.
467 DualControl,
468 /// `ask_user(prompt: ..., schema: ..., timeout: ..., default: ...)`.
469 AskUser,
470 /// `escalate_to(role: ..., reason: ...)`.
471 EscalateTo,
472}
473
474impl HitlKind {
475 /// Keyword surface form (matches the reserved keyword in the lexer
476 /// and the corresponding async builtin name in the VM).
477 pub fn as_keyword(self) -> &'static str {
478 match self {
479 HitlKind::RequestApproval => "request_approval",
480 HitlKind::DualControl => "dual_control",
481 HitlKind::AskUser => "ask_user",
482 HitlKind::EscalateTo => "escalate_to",
483 }
484 }
485}
486
487/// A single argument in a [`Node::HitlExpr`] call. `name` is `Some` when
488/// the caller used named-arg syntax (e.g. `quorum: 2`); positional
489/// arguments leave it as `None` and rely on the kind's parameter order.
490#[derive(Debug, Clone, PartialEq)]
491pub struct HitlArg {
492 pub name: Option<String>,
493 pub value: SNode,
494 pub span: Span,
495}
496
497/// Parallel execution mode.
498#[derive(Debug, Clone, Copy, PartialEq)]
499pub enum ParallelMode {
500 /// `parallel N { i -> ... }` — run N concurrent tasks.
501 Count,
502 /// `parallel each list { item -> ... }` — map over list concurrently.
503 Each,
504 /// `parallel each list { item -> ... } as stream` — emit as each task completes.
505 EachStream,
506 /// `parallel settle list { item -> ... }` — map with error collection.
507 Settle,
508}
509
510#[derive(Debug, Clone, PartialEq)]
511pub struct MatchArm {
512 pub pattern: SNode,
513 /// Optional guard: `pattern if condition -> { body }`.
514 pub guard: Option<Box<SNode>>,
515 pub body: Vec<SNode>,
516}
517
518#[derive(Debug, Clone, PartialEq)]
519pub struct SelectCase {
520 pub variable: String,
521 pub channel: Box<SNode>,
522 pub body: Vec<SNode>,
523}
524
525#[derive(Debug, Clone, PartialEq)]
526pub struct DictEntry {
527 pub key: SNode,
528 pub value: SNode,
529}
530
531/// An enum variant declaration.
532#[derive(Debug, Clone, PartialEq)]
533pub struct EnumVariant {
534 pub name: String,
535 pub fields: Vec<TypedParam>,
536}
537
538/// A struct field declaration.
539#[derive(Debug, Clone, PartialEq)]
540pub struct StructField {
541 pub name: String,
542 pub type_expr: Option<TypeExpr>,
543 pub optional: bool,
544}
545
546/// An interface method signature.
547#[derive(Debug, Clone, PartialEq)]
548pub struct InterfaceMethod {
549 pub name: String,
550 pub type_params: Vec<TypeParam>,
551 pub params: Vec<TypedParam>,
552 pub return_type: Option<TypeExpr>,
553}
554
555/// A type annotation (optional, for runtime checking).
556#[derive(Debug, Clone, PartialEq)]
557pub enum TypeExpr {
558 /// A named type: int, string, float, bool, nil, list, dict, closure,
559 /// or a user-defined type name.
560 Named(String),
561 /// A union type: `string | nil`, `int | float`.
562 Union(Vec<TypeExpr>),
563 /// An intersection type: `{x: int} & {y: int}`. The value must satisfy
564 /// every component simultaneously. Useful for layered context types
565 /// such as `fn use(ctx: BaseCtx & AuthCtx)`.
566 Intersection(Vec<TypeExpr>),
567 /// A dict shape type: `{name: string, age: int, active?: bool}`.
568 Shape(Vec<ShapeField>),
569 /// A list type: `list<int>`.
570 List(Box<TypeExpr>),
571 /// A dict type with key and value types: `dict<string, int>`.
572 DictType(Box<TypeExpr>, Box<TypeExpr>),
573 /// A lazy iterator type: `iter<int>`. Yields values of the inner type
574 /// via the combinator/sink protocol (`VmValue::Iter` at runtime).
575 Iter(Box<TypeExpr>),
576 /// A synchronous generator type: `Generator<int>`. Produced by a regular
577 /// `fn` body containing `yield`.
578 Generator(Box<TypeExpr>),
579 /// An asynchronous stream type: `Stream<int>`. Produced by `gen fn`.
580 Stream(Box<TypeExpr>),
581 /// A generic type application: `Option<int>`, `Result<string, int>`.
582 Applied { name: String, args: Vec<TypeExpr> },
583 /// A function type: `fn(int, string) -> bool`.
584 FnType {
585 params: Vec<TypeExpr>,
586 return_type: Box<TypeExpr>,
587 },
588 /// The bottom type: the type of expressions that never produce a value
589 /// (return, throw, break, continue).
590 Never,
591 /// A string-literal type: `"pass"`, `"fail"`. Assignable to `string`.
592 /// Used in unions to represent enum-like discriminated values.
593 LitString(String),
594 /// An int-literal type: `0`, `1`, `-1`. Assignable to `int`.
595 LitInt(i64),
596}
597
598/// A field in a dict shape type.
599#[derive(Debug, Clone, PartialEq)]
600pub struct ShapeField {
601 pub name: String,
602 pub type_expr: TypeExpr,
603 pub optional: bool,
604}
605
606/// A binding pattern for destructuring in let/var/for-in.
607#[derive(Debug, Clone, PartialEq)]
608pub enum BindingPattern {
609 /// Simple identifier: `let x = ...`
610 Identifier(String),
611 /// Dict destructuring: `let {name, age} = ...`
612 Dict(Vec<DictPatternField>),
613 /// List destructuring: `let [a, b] = ...`
614 List(Vec<ListPatternElement>),
615 /// Pair destructuring for `for (a, b) in iter { ... }`. The iter must
616 /// yield `VmValue::Pair` values. Not valid in let/var bindings.
617 Pair(String, String),
618}
619
620/// `_` is the discard binding name in `let`/`var`/destructuring positions.
621pub fn is_discard_name(name: &str) -> bool {
622 name == "_"
623}
624
625/// A field in a dict destructuring pattern.
626#[derive(Debug, Clone, PartialEq)]
627pub struct DictPatternField {
628 /// The dict key to extract.
629 pub key: String,
630 /// Renamed binding (if different from key), e.g. `{name: alias}`.
631 pub alias: Option<String>,
632 /// True for `...rest` (rest pattern).
633 pub is_rest: bool,
634 /// Default value if the key is missing (nil), e.g. `{name = "default"}`.
635 pub default_value: Option<Box<SNode>>,
636}
637
638/// An element in a list destructuring pattern.
639#[derive(Debug, Clone, PartialEq)]
640pub struct ListPatternElement {
641 /// The variable name to bind.
642 pub name: String,
643 /// True for `...rest` (rest pattern).
644 pub is_rest: bool,
645 /// Default value if the index is out of bounds (nil), e.g. `[a = 0]`.
646 pub default_value: Option<Box<SNode>>,
647}
648
649/// Declared variance of a generic type parameter.
650///
651/// - `Invariant` (default, no marker): the parameter appears in both
652/// input and output positions, or mutable state. `T<A>` and `T<B>`
653/// are unrelated unless `A == B`.
654/// - `Covariant` (`out T`): the parameter appears only in output
655/// positions (produced, not consumed). `T<Sub>` flows into
656/// `T<Super>`.
657/// - `Contravariant` (`in T`): the parameter appears only in input
658/// positions (consumed, not produced). `T<Super>` flows into
659/// `T<Sub>`.
660#[derive(Debug, Clone, Copy, PartialEq, Eq)]
661pub enum Variance {
662 Invariant,
663 Covariant,
664 Contravariant,
665}
666
667/// A generic type parameter on a function or pipeline declaration.
668#[derive(Debug, Clone, PartialEq)]
669pub struct TypeParam {
670 pub name: String,
671 pub variance: Variance,
672}
673
674impl TypeParam {
675 /// Construct an invariant type parameter (the default for
676 /// unannotated `<T>`).
677 pub fn invariant(name: impl Into<String>) -> Self {
678 Self {
679 name: name.into(),
680 variance: Variance::Invariant,
681 }
682 }
683}
684
685/// A where-clause constraint on a generic type parameter.
686#[derive(Debug, Clone, PartialEq)]
687pub struct WhereClause {
688 pub type_name: String,
689 pub bound: String,
690}
691
692/// A parameter with an optional type annotation and optional default value.
693#[derive(Debug, Clone, PartialEq)]
694pub struct TypedParam {
695 pub name: String,
696 pub type_expr: Option<TypeExpr>,
697 pub default_value: Option<Box<SNode>>,
698 /// If true, this is a rest parameter (`...name`) that collects remaining arguments.
699 pub rest: bool,
700}
701
702impl TypedParam {
703 /// Create an untyped parameter.
704 pub fn untyped(name: impl Into<String>) -> Self {
705 Self {
706 name: name.into(),
707 type_expr: None,
708 default_value: None,
709 rest: false,
710 }
711 }
712
713 /// Create a typed parameter.
714 pub fn typed(name: impl Into<String>, type_expr: TypeExpr) -> Self {
715 Self {
716 name: name.into(),
717 type_expr: Some(type_expr),
718 default_value: None,
719 rest: false,
720 }
721 }
722
723 /// Extract just the names from a list of typed params.
724 pub fn names(params: &[TypedParam]) -> Vec<String> {
725 params.iter().map(|p| p.name.clone()).collect()
726 }
727
728 /// Return the index of the first parameter with a default value, or None.
729 pub fn default_start(params: &[TypedParam]) -> Option<usize> {
730 params.iter().position(|p| p.default_value.is_some())
731 }
732}