Skip to main content

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