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}