1use std::collections::HashMap;
24
25use crate::ir::*;
26use crate::known::{self, ParamKind, PrefixOp};
27use crate::load::{Container, Crate};
28
29const MAX_DEPTH: usize = 32;
31const MAX_INSTANCES: usize = 20_000;
32
33#[derive(Clone, Debug)]
41struct VbVal {
42 root: String,
44 key: Key,
45 certainty: Certainty,
46}
47
48#[derive(Clone, Default)]
56struct Env {
57 vb: HashMap<String, VbVal>,
58 closures: HashMap<String, syn::ExprClosure>,
59}
60
61impl Env {
62 fn bind_param(&mut self, name: String, value: Option<VbVal>) {
67 match value {
68 Some(val) => {
69 self.vb.insert(name, val);
70 }
71 None => {
72 self.vb.remove(&name);
73 }
74 }
75 }
76}
77
78pub struct Extractor<'a> {
79 krate: &'a Crate,
80 known_candle_constructors: bool,
81 out: Structure,
82 defs: HashMap<(String, String), ModuleDefId>,
83 sites: HashMap<(ModuleDefId, usize, usize, usize, String), ParamSiteId>,
84 stack: Vec<(String, String)>,
85 truncated: bool,
86}
87
88#[derive(Clone)]
89struct Ctx {
90 def: ModuleDefId,
91 instance: ModuleInstanceId,
92 file: usize,
95 conditional: Option<String>,
96 repeat: Option<Repeat>,
97 depth: usize,
98}
99
100impl Ctx {
101 fn certainty(&self) -> Certainty {
102 match &self.conditional {
103 Some(reason) => Certainty::Conditional(reason.clone()),
104 None => Certainty::Certain,
105 }
106 }
107
108 fn in_branch(&self, reason: impl Into<String>) -> Self {
109 let mut next = self.clone();
110 if next.conditional.is_none() {
112 next.conditional = Some(reason.into());
113 }
114 next
115 }
116}
117
118impl<'a> Extractor<'a> {
119 pub fn new(krate: &'a Crate) -> Self {
120 Self {
121 krate,
122 known_candle_constructors: true,
126 out: Structure::default(),
127 defs: HashMap::new(),
128 sites: HashMap::new(),
129 stack: Vec::new(),
130 truncated: false,
131 }
132 }
133
134 pub fn for_candle_version(krate: &'a Crate, version: Option<&str>) -> Self {
138 let mut extractor = Self::new(krate);
139 extractor.known_candle_constructors =
140 version.is_some_and(crate::op_semantics::is_audited_candle_version);
141 extractor
142 }
143
144 pub fn run(mut self, root_type: &str, ctor: Option<&str>) -> anyhow::Result<Structure> {
145 let ctor_name = match ctor {
146 Some(name) => name.to_string(),
147 None => self.find_entry_ctor(root_type)?,
148 };
149
150 let candidates: Vec<_> = self
151 .krate
152 .method_candidates(root_type, &ctor_name)
153 .into_iter()
154 .filter(|func| func.trait_name.is_none())
155 .collect();
156 let func = match candidates.as_slice() {
157 [func] => *func,
158 [] => {
159 return Err(anyhow::anyhow!(
160 "`{root_type}::{ctor_name}` not found in crate"
161 ))
162 }
163 _ => {
164 return Err(anyhow::anyhow!(
165 "`{root_type}::{ctor_name}` is ambiguous ({} definitions); use a \
166 module-qualified root and active Cargo cfg",
167 candidates.len()
168 ))
169 }
170 };
171
172 let def = self.def_id(root_type, &ctor_name, func.span);
173 let primary_root = func
174 .vb_params
175 .first()
176 .and_then(|i| func.params.get(*i))
177 .cloned()
178 .unwrap_or_else(|| "vb".to_string());
179
180 let root = self.out.add_instance(
181 def,
182 None,
183 None,
184 Key::default(),
185 primary_root,
186 false,
187 None,
188 func.span,
189 Certainty::Certain,
190 );
191 self.out.root = Some(root);
192
193 let mut env = Env::default();
195 for index in &func.vb_params {
196 if let Some(name) = func.params.get(*index) {
197 env.vb.insert(
198 name.clone(),
199 VbVal {
200 root: name.clone(),
201 key: Key::default(),
202 certainty: Certainty::Certain,
203 },
204 );
205 }
206 }
207
208 let ctx = Ctx {
209 def,
210 instance: root,
211 file: func.span.file,
212 conditional: None,
213 repeat: None,
214 depth: 0,
215 };
216 self.stack.push((root_type.to_string(), ctor_name));
217 let block = func.block.clone();
218 self.walk_block(&block, &mut env, &ctx);
219 self.stack.pop();
220
221 if self.truncated {
222 self.out.diagnose(
223 SrcSpan::UNKNOWN,
224 format!("analysis truncated at {MAX_INSTANCES} instances; output is incomplete"),
225 None,
226 );
227 }
228 self.out.dedupe_params();
229 self.out.derive_prefixes();
230 Ok(self.out)
231 }
232
233 fn find_entry_ctor(&self, root_type: &str) -> anyhow::Result<String> {
234 for preferred in ["new", "load"] {
237 let candidates: Vec<_> = self
238 .krate
239 .method_candidates(root_type, preferred)
240 .into_iter()
241 .filter(|func| func.trait_name.is_none() && !func.vb_params.is_empty())
242 .collect();
243 match candidates.len() {
244 0 => {}
245 1 => return Ok(preferred.to_string()),
246 count => {
247 return Err(anyhow::anyhow!(
248 "`{root_type}::{preferred}` is ambiguous ({count} active-or-cfg-gated \
249 definitions); select a module-qualified root and Cargo configuration"
250 ))
251 }
252 }
253 }
254 let mut candidates: Vec<String> = self
255 .krate
256 .all_methods()
257 .filter(|func| {
258 let owner_matches = if root_type.contains("::") {
259 func.qualified_type_name == root_type
260 } else {
261 func.type_name == root_type
262 };
263 owner_matches && func.trait_name.is_none() && !func.vb_params.is_empty()
264 })
265 .map(|func| func.fn_name.clone())
266 .collect();
267 candidates.sort();
268 candidates.dedup();
269 candidates.first().cloned().ok_or_else(|| {
270 anyhow::anyhow!(
271 "no constructor taking a VarBuilder found on `{root_type}`; \
272 pass --ctor to choose one explicitly"
273 )
274 })
275 }
276
277 fn def_id(&mut self, type_name: &str, ctor: &str, span: SrcSpan) -> ModuleDefId {
278 let key = (type_name.to_string(), ctor.to_string());
279 if let Some(id) = self.defs.get(&key) {
280 return *id;
281 }
282 let id = self
283 .out
284 .add_def(type_name.to_string(), Some(ctor.to_string()), span);
285 self.defs.insert(key, id);
286 id
287 }
288
289 fn walk_block(&mut self, block: &syn::Block, env: &mut Env, ctx: &Ctx) {
292 for stmt in &block.stmts {
293 self.walk_stmt(stmt, env, ctx);
294 }
295 }
296
297 fn walk_stmt(&mut self, stmt: &syn::Stmt, env: &mut Env, ctx: &Ctx) {
298 match stmt {
299 syn::Stmt::Local(local) => {
300 let Some(init) = &local.init else { return };
301 if let syn::Expr::Closure(closure) = unwrap_expr(&init.expr) {
305 if let Some(name) = binding_name(&local.pat) {
306 env.closures.insert(name, closure.clone());
307 return;
308 }
309 }
310 if let Some(val) = self.eval_vb(&init.expr, env, ctx) {
311 if let Some(name) = binding_name(&local.pat) {
312 env.vb.insert(name, val);
313 return;
314 }
315 }
316 self.walk_expr(&init.expr, env, ctx);
317 }
318 syn::Stmt::Expr(expr, _) => self.walk_expr(expr, env, ctx),
319 syn::Stmt::Item(_) => {}
320 syn::Stmt::Macro(m) => {
321 if macro_cannot_register_params(&m.mac.path) {
322 return;
323 }
324 self.out.diagnose(
325 crate::load::span_of(ctx.file, m.mac.path.segments[0].ident.span()),
326 format!(
327 "macro `{}!` not expanded; any parameters it registers are invisible",
328 crate::load::type_text(&m.mac.path)
329 ),
330 None,
331 );
332 }
333 }
334 }
335
336 fn walk_expr(&mut self, expr: &syn::Expr, env: &mut Env, ctx: &Ctx) {
339 if self.out.instances.len() > MAX_INSTANCES {
340 self.truncated = true;
341 return;
342 }
343 if self.try_local_closure(expr, env, ctx) {
346 return;
347 }
348 if self.try_param_site(expr, env, ctx) {
349 return;
350 }
351 if self.try_free_function(expr, env, ctx) {
352 return;
353 }
354 if self.try_submodule(expr, env, ctx, None) {
355 return;
356 }
357
358 match unwrap_expr(expr) {
359 syn::Expr::Struct(s) => self.walk_struct_literal(s, env, ctx),
360 syn::Expr::ForLoop(f) => {
361 let mut inner = ctx.clone();
362 inner.repeat = Some(Repeat {
363 var: binding_name(&f.pat).unwrap_or_else(|| "_".to_string()),
364 bound: crate::load::type_text(&f.expr),
365 });
366 let mut scoped = env.clone();
367 self.walk_block(&f.body, &mut scoped, &inner);
368 }
369 syn::Expr::While(w) => {
370 self.walk_expr(&w.cond, env, ctx);
371 let mut inner = ctx.clone();
372 inner.repeat = Some(Repeat {
373 var: "_".to_string(),
374 bound: format!("while {}", crate::load::type_text(&w.cond)),
375 });
376 let mut scoped = env.clone();
377 self.walk_block(&w.body, &mut scoped, &inner);
378 }
379 syn::Expr::Loop(l) => {
380 let mut inner = ctx.clone();
381 inner.repeat = Some(Repeat {
382 var: "_".to_string(),
383 bound: "loop".to_string(),
384 });
385 let mut scoped = env.clone();
386 self.walk_block(&l.body, &mut scoped, &inner);
387 }
388 syn::Expr::If(i) => {
389 let branch = ctx.in_branch(format!("if {}", crate::load::type_text(&i.cond)));
390 let mut scoped = env.clone();
391 self.walk_block(&i.then_branch, &mut scoped, &branch);
392 if let Some((_, alt)) = &i.else_branch {
393 let mut scoped = env.clone();
394 self.walk_expr(alt, &mut scoped, &branch);
395 }
396 }
397 syn::Expr::Match(m) => {
398 let branch = ctx.in_branch(format!("match {}", crate::load::type_text(&m.expr)));
399 for arm in &m.arms {
400 let mut scoped = env.clone();
401 self.walk_expr(&arm.body, &mut scoped, &branch);
402 }
403 }
404 syn::Expr::Closure(c) => {
405 let mut scoped = env.clone();
408 for param in &c.inputs {
409 if let Some(name) = binding_name(param) {
410 scoped.bind_param(name, None);
411 }
412 }
413 self.walk_expr(&c.body, &mut scoped, ctx);
414 }
415 syn::Expr::MethodCall(mc) => {
416 let mut inner = ctx.clone();
419 if matches!(mc.method.to_string().as_str(), "then" | "then_some") {
423 inner = inner.in_branch(format!(
424 "only when {}",
425 crate::load::type_text(&mc.receiver)
426 ));
427 }
428 if mc.method == "map"
432 && inner.repeat.is_none()
433 && looks_like_iteration(&mc.receiver)
434 {
435 inner.repeat = Some(Repeat {
436 var: "_".to_string(),
437 bound: crate::load::type_text(&mc.receiver),
438 });
439 }
440 let receiver_vb = self.eval_vb(&mc.receiver, env, &inner);
445 self.walk_expr(&mc.receiver, env, &inner);
446 for arg in &mc.args {
447 if let syn::Expr::Closure(closure) = unwrap_expr(arg) {
448 let mut scoped = env.clone();
449 for (index, param) in closure.inputs.iter().enumerate() {
450 if let Some(name) = binding_name(param) {
451 let value = if index == 0 {
452 receiver_vb.clone()
453 } else {
454 None
455 };
456 scoped.bind_param(name, value);
457 }
458 }
459 self.walk_expr(&closure.body, &mut scoped, &inner);
460 } else {
461 self.walk_expr(arg, env, &inner);
462 }
463 }
464 }
465 other => self.walk_children(other, env, ctx),
466 }
467 }
468
469 fn try_local_closure(&mut self, expr: &syn::Expr, env: &mut Env, ctx: &Ctx) -> bool {
471 let syn::Expr::Call(call) = unwrap_expr(expr) else {
472 return false;
473 };
474 let Some(path) = call_path(&call.func) else {
475 return false;
476 };
477 if path.len() != 1 {
478 return false;
479 }
480 let Some(closure) = env.closures.get(&path[0]).cloned() else {
481 return false;
482 };
483
484 let mut scoped = env.clone();
485 scoped.closures.remove(&path[0]);
487 for (param, arg) in closure.inputs.iter().zip(call.args.iter()) {
488 if let Some(name) = binding_name(param) {
489 let value = self.eval_vb(arg, env, ctx);
490 scoped.bind_param(name, value);
491 }
492 }
493 for param in closure.inputs.iter().skip(call.args.len()) {
495 if let Some(name) = binding_name(param) {
496 scoped.bind_param(name, None);
497 }
498 }
499
500 self.walk_expr(&closure.body, &mut scoped, ctx);
501 true
502 }
503
504 fn walk_struct_literal(&mut self, s: &syn::ExprStruct, env: &mut Env, ctx: &Ctx) {
507 let type_name = s.path.segments.last().map(|seg| seg.ident.to_string());
508 let own_type = self.out.def(ctx.def).name.clone();
509
510 let group = match &type_name {
511 Some(name)
514 if name != "Self"
515 && *name != own_type
516 && self.krate.struct_candidates(name).len() == 1 =>
517 {
518 let def = self.def_id(name, "<struct literal>", span_of_expr(ctx, &s.path));
519 let id = self.out.add_instance(
520 def,
521 Some(ctx.instance),
522 None,
523 Key::default(),
524 String::new(),
525 true,
526 ctx.repeat.clone(),
527 span_of_expr(ctx, &s.path),
528 ctx.certainty(),
529 );
530 Some((def, id))
531 }
532 _ => None,
533 };
534
535 let inner = match group {
536 Some((def, instance)) => Ctx {
537 def,
538 instance,
539 ..ctx.clone()
540 },
541 None => ctx.clone(),
542 };
543
544 for field in &s.fields {
545 let name = match &field.member {
546 syn::Member::Named(id) => Some(id.to_string()),
547 syn::Member::Unnamed(i) => Some(i.index.to_string()),
548 };
549 self.walk_field_expr(&field.expr, env, &inner, name);
550 }
551 }
552
553 fn walk_field_expr(
554 &mut self,
555 expr: &syn::Expr,
556 env: &mut Env,
557 ctx: &Ctx,
558 field: Option<String>,
559 ) {
560 let ctx = match &field {
562 Some(name) if self.field_is_option(ctx, name) => {
563 &ctx.in_branch(format!("Option field `{name}`"))
564 }
565 _ => ctx,
566 };
567
568 if self.try_local_closure(expr, env, ctx) {
571 return;
572 }
573 if self.try_param_site(expr, env, ctx) {
574 return;
575 }
576 if self.try_free_function(expr, env, ctx) {
577 return;
578 }
579 if self.try_submodule(expr, env, ctx, field) {
580 return;
581 }
582 self.walk_expr(expr, env, ctx);
583 }
584
585 fn field_is_option(&self, ctx: &Ctx, field: &str) -> bool {
586 let candidates = self.krate.struct_candidates(&self.out.def(ctx.def).name);
587 candidates
588 .first()
589 .filter(|_| candidates.len() == 1)
590 .and_then(|s| s.fields.iter().find(|f| f.name == field))
591 .map(|f| f.ty.container == Container::Option)
592 .unwrap_or(false)
593 }
594
595 fn walk_children(&mut self, expr: &syn::Expr, env: &mut Env, ctx: &Ctx) {
596 use syn::Expr as E;
597 match expr {
598 E::Call(c) => {
599 if c.args
600 .iter()
601 .any(|arg| self.eval_vb(arg, env, ctx).is_some())
602 {
603 let target = call_path(&c.func)
604 .map(|path| path.join("::"))
605 .unwrap_or_else(|| crate::load::type_text(&c.func));
606 self.out.diagnose(
607 span_of_expr(ctx, c),
608 format!(
609 "unresolved call `{target}` receives a VarBuilder; any parameters it \
610 registers are unknown"
611 ),
612 None,
613 );
614 }
615 for a in &c.args {
616 self.walk_expr(a, env, ctx);
617 }
618 }
619 E::Try(t) => self.walk_expr(&t.expr, env, ctx),
620 E::Reference(r) => self.walk_expr(&r.expr, env, ctx),
621 E::Paren(p) => self.walk_expr(&p.expr, env, ctx),
622 E::Group(g) => self.walk_expr(&g.expr, env, ctx),
623 E::Block(b) => {
624 let mut scoped = env.clone();
625 self.walk_block(&b.block, &mut scoped, ctx);
626 }
627 E::Unsafe(u) => {
628 let mut scoped = env.clone();
629 self.walk_block(&u.block, &mut scoped, ctx);
630 }
631 E::Tuple(t) => {
632 for e in &t.elems {
633 self.walk_expr(e, env, ctx);
634 }
635 }
636 E::Array(a) => {
637 for e in &a.elems {
638 self.walk_expr(e, env, ctx);
639 }
640 }
641 E::Return(r) => {
642 if let Some(e) = &r.expr {
643 self.walk_expr(e, env, ctx);
644 }
645 }
646 E::Let(l) => self.walk_expr(&l.expr, env, ctx),
647 E::Assign(a) => self.walk_expr(&a.right, env, ctx),
648 E::Binary(b) => {
649 self.walk_expr(&b.left, env, ctx);
650 self.walk_expr(&b.right, env, ctx);
651 }
652 _ => {}
653 }
654 }
655
656 fn try_param_site(&mut self, expr: &syn::Expr, env: &mut Env, ctx: &Ctx) -> bool {
659 let expr = unwrap_expr(expr);
660
661 if let syn::Expr::Call(call) = expr {
663 let Some(path) = call_path(&call.func) else {
664 return false;
665 };
666 let Some(func) = path.last() else {
667 return false;
668 };
669 if path.len() == 1 && env.closures.contains_key(func) {
672 return false;
673 }
674 let resolved_path = self.krate.resolve_unambiguous_import_path(&path);
675 let Some(ctor) = self
676 .known_candle_constructors
677 .then(|| known_constructor(resolved_path.as_slice(), func))
678 .flatten()
679 else {
680 return false;
681 };
682
683 let span = span_of_expr(ctx, expr);
684 let Some(vb) = self.resolve_vb_arg(&call.args, ctor.vb_arg, env, ctx) else {
685 self.out.diagnose(
686 span,
687 format!(
688 "`{}` call whose VarBuilder argument could not be resolved to a prefix",
689 path.join("::")
690 ),
691 None,
692 );
693 return true;
694 };
695
696 let rnn = rnn_config(func, &call.args);
697 for leaf in ctor.leaves {
698 if leaf.kind == ParamKind::Bias && rnn.biases == Some(false) {
701 continue;
702 }
703 let shape = constructor_leaf_shape(func, leaf.name, &call.args);
704 let unconditional = leaf.unconditional
706 || (leaf.kind == ParamKind::Bias && rnn.biases == Some(true));
707 let leaf_certainty = combine(
708 &vb.certainty,
709 &ctx.certainty(),
710 unconditional,
711 &format!(
712 "`{func}` registers `{}` only in some configurations",
713 leaf.name
714 ),
715 );
716 let named_by_config = leaf.config_named && !rnn.default_names;
719 let leaf_seg = if named_by_config {
720 self.out.diagnose(
721 span,
722 format!(
723 "`{func}` names `{}` from its config argument; unresolved \
724 `layer_idx`/`direction` leaves a tensor-name family",
725 leaf.name
726 ),
727 None,
728 );
729 KeySeg::Template {
730 text: config_named_family(leaf.name),
731 }
732 } else {
733 KeySeg::Literal(leaf.name.to_string())
734 };
735 let site = self.site_for(
736 ctx,
737 span,
738 leaf.name,
739 Acquisition::Constructor {
740 func: path.join("::"),
741 cite: ctor.cite,
742 },
743 Key::default().push(leaf_seg.clone()),
744 leaf.kind,
745 shape,
746 leaf_certainty.clone(),
747 );
748 let key = vb.key.push(leaf_seg);
749 self.out
750 .add_param(site, ctx.instance, key, vb.root.clone(), leaf_certainty);
751 }
752 return true;
753 }
754
755 if let syn::Expr::MethodCall(mc) = expr {
757 let method = mc.method.to_string();
758 let Some(name_arg) = known::raw_get_name_arg(&method) else {
759 return false;
760 };
761 let Some(vb) = self.eval_vb(&mc.receiver, env, ctx) else {
762 return false;
763 };
764 let span = span_of_expr(ctx, expr);
765 let Some(name_expr) = mc.args.iter().nth(name_arg) else {
766 return false;
767 };
768 let Some(name) = string_literal(name_expr) else {
769 self.out.diagnose(
770 span,
771 format!("`{method}` with a non-literal tensor name; key is unknown"),
772 Some(vb.key.clone()),
773 );
774 return true;
775 };
776
777 let shape = if name_arg > 0 {
778 mc.args.first().map(crate::load::type_text)
779 } else {
780 None
781 };
782 let leaf_certainty = combine(&vb.certainty, &ctx.certainty(), true, "");
783 let site = self.site_for(
784 ctx,
785 span,
786 &name,
787 Acquisition::RawGet { method },
788 Key::default().push_literal(&name),
789 ParamKind::Raw,
790 shape,
791 leaf_certainty.clone(),
792 );
793 let key = vb.key.push_literal(&name);
794 self.out
795 .add_param(site, ctx.instance, key, vb.root, leaf_certainty);
796 return true;
797 }
798
799 false
800 }
801
802 #[allow(clippy::too_many_arguments)]
803 fn site_for(
804 &mut self,
805 ctx: &Ctx,
806 span: SrcSpan,
807 leaf: &str,
808 acquisition: Acquisition,
809 relative_key: Key,
810 kind: ParamKind,
811 shape: Option<String>,
812 certainty: Certainty,
813 ) -> ParamSiteId {
814 let cache_key = (ctx.def, span.file, span.line, span.col, leaf.to_string());
817 if let Some(id) = self.sites.get(&cache_key) {
818 return *id;
819 }
820 let id = self.out.add_site(
821 ctx.def,
822 acquisition,
823 relative_key,
824 kind,
825 shape,
826 span,
827 certainty,
828 );
829 self.sites.insert(cache_key, id);
830 id
831 }
832
833 fn try_free_function(&mut self, expr: &syn::Expr, env: &mut Env, ctx: &Ctx) -> bool {
842 let syn::Expr::Call(call) = unwrap_expr(expr) else {
843 return false;
844 };
845 let Some(path) = call_path(&call.func) else {
846 return false;
847 };
848 let Some(name) = path.last().cloned() else {
849 return false;
850 };
851 let lookup_name = normalize_qualified_path(&path);
852 let candidates = self.krate.function_candidates(&lookup_name);
853 let func = match candidates.as_slice() {
854 [func] => *func,
855 [] => return false,
856 _ => {
857 if candidates.iter().any(|func| !func.vb_params.is_empty()) {
858 self.out.diagnose(
859 span_of_expr(ctx, expr),
860 format!(
861 "free function `{lookup_name}` is ambiguous ({} definitions); \
862 parameters cannot be attributed safely",
863 candidates.len()
864 ),
865 None,
866 );
867 return true;
868 }
869 return false;
870 }
871 };
872 if func.vb_params.is_empty() {
873 return false;
874 }
875
876 let span = span_of_expr(ctx, expr);
877 let stack_key = ("<free>".to_string(), name.clone());
878 if ctx.depth >= MAX_DEPTH {
879 self.out.diagnose(
880 span,
881 format!("recursion depth {MAX_DEPTH} exceeded at free function `{name}`"),
882 None,
883 );
884 return true;
885 }
886 if self.stack.contains(&stack_key) {
887 self.out.diagnose(
888 span,
889 format!("cycle through free function `{name}`; body not expanded"),
890 None,
891 );
892 return true;
893 }
894
895 let mut helper_env = Env::default();
896 let mut resolved = 0usize;
897 for index in &func.vb_params {
898 let Some(param) = func.params.get(*index) else {
899 continue;
900 };
901 let Some(arg) = call.args.iter().nth(*index) else {
902 self.out.diagnose(
903 span,
904 format!("free function `{name}` is missing VarBuilder argument `{param}`"),
905 None,
906 );
907 continue;
908 };
909 match self.eval_vb(arg, env, ctx) {
910 Some(value) => {
911 helper_env.vb.insert(param.clone(), value);
912 resolved += 1;
913 }
914 None => self.out.diagnose(
915 span,
916 format!(
917 "free function `{name}` argument `{param}` is a VarBuilder whose prefix \
918 could not be resolved"
919 ),
920 None,
921 ),
922 }
923 }
924 if resolved == 0 {
925 return true;
926 }
927
928 let helper_ctx = Ctx {
929 file: func.span.file,
930 depth: ctx.depth + 1,
931 ..ctx.clone()
932 };
933 self.stack.push(stack_key);
934 let block = func.block.clone();
935 self.walk_block(&block, &mut helper_env, &helper_ctx);
936 self.stack.pop();
937 true
938 }
939
940 fn try_submodule(
942 &mut self,
943 expr: &syn::Expr,
944 env: &mut Env,
945 ctx: &Ctx,
946 field: Option<String>,
947 ) -> bool {
948 let expr = unwrap_expr(expr);
949 let syn::Expr::Call(call) = expr else {
950 return false;
951 };
952 let Some(path) = call_path(&call.func) else {
953 return false;
954 };
955 if path.len() < 2 {
956 return false;
957 }
958 let ctor_name = path[path.len() - 1].clone();
959 let raw_type = path[..path.len() - 1].join("::");
960 let type_name = if raw_type == "Self" {
961 self.out.def(ctx.def).name.clone()
962 } else {
963 normalize_qualified_name(&raw_type)
964 };
965
966 let candidates: Vec<_> = self
967 .krate
968 .method_candidates(&type_name, &ctor_name)
969 .into_iter()
970 .filter(|func| func.trait_name.is_none())
971 .collect();
972 let func = match candidates.as_slice() {
973 [func] => *func,
974 [] => {
975 if call
976 .args
977 .iter()
978 .any(|arg| self.eval_vb(arg, env, ctx).is_some())
979 {
980 self.out.diagnose(
981 span_of_expr(ctx, expr),
982 format!(
983 "unresolved constructor `{type_name}::{ctor_name}` receives a \
984 VarBuilder; its parameters are unknown"
985 ),
986 None,
987 );
988 return true;
989 }
990 return false;
991 }
992 _ => {
993 self.out.diagnose(
994 span_of_expr(ctx, expr),
995 format!(
996 "constructor `{type_name}::{ctor_name}` is ambiguous ({} definitions); \
997 subtree not expanded",
998 candidates.len()
999 ),
1000 None,
1001 );
1002 return true;
1003 }
1004 };
1005 if func.vb_params.is_empty() {
1006 return false;
1007 }
1008
1009 let span = span_of_expr(ctx, expr);
1010
1011 let mut bindings: Vec<(String, VbVal)> = Vec::new();
1015 let mut primary: Option<VbVal> = None;
1016
1017 for index in &func.vb_params {
1018 let Some(arg) = call.args.iter().nth(*index) else {
1019 continue;
1020 };
1021 let Some(name) = func.params.get(*index) else {
1022 continue;
1023 };
1024 match self.eval_vb(arg, env, ctx) {
1025 Some(val) => {
1026 if primary.is_none() {
1030 primary = Some(val.clone());
1031 }
1032 bindings.push((name.clone(), val));
1033 }
1034 None => {
1035 self.out.diagnose(
1036 span,
1037 format!(
1038 "`{type_name}::{ctor_name}` argument `{name}` is a VarBuilder whose \
1039 prefix could not be resolved; parameters under it are missing"
1040 ),
1041 None,
1042 );
1043 }
1044 }
1045 }
1046
1047 let Some(primary) = primary else {
1048 self.out.diagnose(
1049 span,
1050 format!("`{type_name}::{ctor_name}` VarBuilder arguments not resolvable"),
1051 None,
1052 );
1053 return true;
1054 };
1055
1056 if ctx.depth >= MAX_DEPTH {
1057 self.out.diagnose(
1058 span,
1059 format!("recursion depth {MAX_DEPTH} exceeded at `{type_name}::{ctor_name}`"),
1060 Some(primary.key),
1061 );
1062 return true;
1063 }
1064 if self.stack.contains(&(type_name.clone(), ctor_name.clone())) {
1065 self.out.diagnose(
1066 span,
1067 format!("cycle through `{type_name}::{ctor_name}`; subtree not expanded"),
1068 Some(primary.key),
1069 );
1070 return true;
1071 }
1072
1073 let certainty = merge(ctx.certainty(), primary.certainty.clone());
1074 if raw_type == "Self" && field.is_none() {
1077 let mut helper_env = Env::default();
1078 for (name, val) in bindings {
1079 helper_env.vb.insert(name, val);
1080 }
1081 let helper_ctx = Ctx {
1082 file: func.span.file,
1083 conditional: match &certainty {
1084 Certainty::Conditional(reason) => Some(reason.clone()),
1085 _ => ctx.conditional.clone(),
1086 },
1087 depth: ctx.depth + 1,
1088 ..ctx.clone()
1089 };
1090 self.stack.push((type_name, ctor_name));
1091 let block = func.block.clone();
1092 self.walk_block(&block, &mut helper_env, &helper_ctx);
1093 self.stack.pop();
1094 return true;
1095 }
1096
1097 let child_def = self.def_id(&type_name, &ctor_name, func.span);
1098 let child = self.out.add_instance(
1099 child_def,
1100 Some(ctx.instance),
1101 field,
1102 primary.key.clone(),
1103 primary.root.clone(),
1104 false,
1105 ctx.repeat.clone(),
1106 span,
1107 certainty.clone(),
1108 );
1109
1110 let mut child_env = Env::default();
1111 for (name, val) in bindings {
1112 child_env.vb.insert(name, val);
1113 }
1114 let child_ctx = Ctx {
1115 def: child_def,
1116 instance: child,
1117 file: func.span.file,
1118 conditional: match &certainty {
1119 Certainty::Conditional(reason) => Some(reason.clone()),
1120 _ => None,
1121 },
1122 repeat: None,
1125 depth: ctx.depth + 1,
1126 };
1127
1128 self.stack.push((type_name, ctor_name));
1129 let block = func.block.clone();
1130 self.walk_block(&block, &mut child_env, &child_ctx);
1131 self.stack.pop();
1132 true
1133 }
1134
1135 fn resolve_vb_arg(
1138 &mut self,
1139 args: &syn::punctuated::Punctuated<syn::Expr, syn::Token![,]>,
1140 index: usize,
1141 env: &Env,
1142 ctx: &Ctx,
1143 ) -> Option<VbVal> {
1144 args.iter()
1145 .nth(index)
1146 .and_then(|arg| self.eval_vb(arg, env, ctx))
1147 }
1148
1149 fn eval_vb(&mut self, expr: &syn::Expr, env: &Env, ctx: &Ctx) -> Option<VbVal> {
1150 let expr = unwrap_expr(expr);
1151 match expr {
1152 syn::Expr::Path(p) => {
1153 let name = p.path.segments.last()?.ident.to_string();
1154 env.vb.get(&name).cloned()
1155 }
1156 syn::Expr::MethodCall(mc) => {
1157 let method = mc.method.to_string();
1158
1159 if method == "then" || method == "then_some" {
1162 let arg = mc.args.first()?;
1163 let inner = match unwrap_expr(arg) {
1164 syn::Expr::Closure(c) => c.body.as_ref(),
1165 other => other,
1166 };
1167 let val = self.eval_vb(inner, env, ctx)?;
1168 let reason = format!("only when {}", crate::load::type_text(&mc.receiver));
1169 return Some(VbVal {
1170 certainty: merge(val.certainty, Certainty::Conditional(reason)),
1171 ..val
1172 });
1173 }
1174
1175 if method == "map" || method == "and_then" {
1179 let base = self.eval_vb(&mc.receiver, env, ctx)?;
1180 let arg = mc.args.first()?;
1181 let syn::Expr::Closure(closure) = unwrap_expr(arg) else {
1182 return None;
1183 };
1184 let param = closure.inputs.first().and_then(binding_name)?;
1185 let mut scoped = env.clone();
1186 scoped.vb.insert(param, base.clone());
1187 let val = self.eval_vb(&closure.body, &scoped, ctx)?;
1188 return Some(VbVal {
1189 certainty: merge(val.certainty, base.certainty),
1190 ..val
1191 });
1192 }
1193
1194 if matches!(
1197 method.as_str(),
1198 "as_ref" | "as_mut" | "clone" | "to_owned" | "to_dtype"
1199 ) {
1200 return self.eval_vb(&mc.receiver, env, ctx);
1201 }
1202
1203 let op = known::prefix_method(&method)?;
1204 let base = self.eval_vb(&mc.receiver, env, ctx)?;
1205 match op {
1206 PrefixOp::Root => Some(VbVal {
1207 key: Key::default(),
1208 ..base
1209 }),
1210 PrefixOp::Push | PrefixOp::Replace => {
1211 let arg = mc.args.first()?;
1212 let (segs, seg_certainty) = self.eval_prefix_arg(arg, ctx);
1213 let start = if op == PrefixOp::Replace {
1214 Key::default()
1215 } else {
1216 base.key
1217 };
1218 Some(VbVal {
1219 root: base.root,
1220 key: start.extend(&segs),
1221 certainty: merge(base.certainty, seg_certainty),
1222 })
1223 }
1224 }
1225 }
1226 syn::Expr::Call(c) => {
1227 let path = call_path(&c.func)?;
1228 if path.last().map(String::as_str) == Some("Some") && c.args.len() == 1 {
1229 return self.eval_vb(&c.args[0], env, ctx);
1230 }
1231 None
1232 }
1233 _ => None,
1234 }
1235 }
1236
1237 fn eval_prefix_arg(&mut self, expr: &syn::Expr, ctx: &Ctx) -> (Vec<KeySeg>, Certainty) {
1238 let expr = unwrap_expr(expr);
1239
1240 if let Some(text) = string_literal(expr) {
1241 return (literal_segs(&text), Certainty::Certain);
1242 }
1243
1244 if let syn::Expr::Macro(m) = expr {
1246 if m.mac
1247 .path
1248 .segments
1249 .last()
1250 .map(|s| s.ident.to_string())
1251 .as_deref()
1252 == Some("format")
1253 {
1254 if let Some(segs) = format_segs(&m.mac.tokens.to_string()) {
1255 return (segs, Certainty::Certain);
1256 }
1257 }
1258 }
1259
1260 let text = crate::load::type_text(expr);
1263 self.out.diagnose(
1264 span_of_expr(ctx, expr),
1265 format!("prefix argument `{text}` is not a literal or `format!`; segment is dynamic"),
1266 None,
1267 );
1268 (vec![KeySeg::Dynamic { expr: text }], Certainty::Certain)
1269 }
1270}
1271
1272fn combine(a: &Certainty, b: &Certainty, unconditional: bool, reason: &str) -> Certainty {
1275 let merged = merge(a.clone(), b.clone());
1276 if unconditional {
1277 merged
1278 } else {
1279 match merged {
1280 Certainty::Certain => Certainty::Conditional(reason.to_string()),
1281 other => other,
1282 }
1283 }
1284}
1285
1286fn merge(a: Certainty, b: Certainty) -> Certainty {
1289 match (&a, &b) {
1290 (Certainty::Unknown(_), _) => a,
1291 (_, Certainty::Unknown(_)) => b,
1292 (Certainty::Conditional(_), _) => a,
1293 (_, Certainty::Conditional(_)) => b,
1294 _ => Certainty::Certain,
1295 }
1296}
1297
1298fn literal_segs(text: &str) -> Vec<KeySeg> {
1299 text.split('.')
1300 .filter(|p| !p.is_empty())
1301 .map(|p| KeySeg::Literal(p.to_string()))
1302 .collect()
1303}
1304
1305fn format_segs(tokens: &str) -> Option<Vec<KeySeg>> {
1311 let tokens = tokens.trim();
1312 let rest = tokens.strip_prefix('"')?;
1313 let end = find_unescaped_quote(rest)?;
1314 let template = &rest[..end];
1315 let args: Vec<String> = rest[end + 1..]
1316 .trim_start()
1317 .trim_start_matches(',')
1318 .split(',')
1319 .map(|a| a.trim().to_string())
1320 .filter(|a| !a.is_empty())
1321 .collect();
1322
1323 let mut positional = args.into_iter();
1324 let mut segs = Vec::new();
1325 for part in template.split('.').filter(|p| !p.is_empty()) {
1326 if let Some(inner) = brace_content(part) {
1327 let expr = if inner.is_empty() {
1328 positional.next().unwrap_or_else(|| "_".to_string())
1329 } else {
1330 inner.split(':').next().unwrap_or(inner).to_string()
1332 };
1333 segs.push(KeySeg::Dynamic { expr });
1334 } else if part.contains('{') {
1335 segs.push(KeySeg::Template {
1338 text: part.to_string(),
1339 });
1340 } else {
1341 segs.push(KeySeg::Literal(part.to_string()));
1342 }
1343 }
1344 Some(segs)
1345}
1346
1347fn brace_content(part: &str) -> Option<&str> {
1348 let inner = part.strip_prefix('{')?.strip_suffix('}')?;
1349 (!inner.contains('{')).then_some(inner)
1350}
1351
1352fn find_unescaped_quote(s: &str) -> Option<usize> {
1353 let bytes = s.as_bytes();
1354 let mut i = 0;
1355 while i < bytes.len() {
1356 match bytes[i] {
1357 b'\\' => i += 2,
1358 b'"' => return Some(i),
1359 _ => i += 1,
1360 }
1361 }
1362 None
1363}
1364
1365fn macro_cannot_register_params(path: &syn::Path) -> bool {
1371 let Some(name) = path
1372 .segments
1373 .last()
1374 .map(|segment| segment.ident.to_string())
1375 else {
1376 return false;
1377 };
1378 matches!(
1379 name.as_str(),
1380 "assert"
1381 | "assert_eq"
1382 | "assert_ne"
1383 | "debug_assert"
1384 | "debug_assert_eq"
1385 | "debug_assert_ne"
1386 | "bail"
1387 | "ensure"
1388 | "panic"
1389 | "todo"
1390 | "unimplemented"
1391 | "unreachable"
1392 | "print"
1393 | "println"
1394 | "eprint"
1395 | "eprintln"
1396 | "dbg"
1397 )
1398}
1399
1400fn string_literal(expr: &syn::Expr) -> Option<String> {
1401 match unwrap_expr(expr) {
1402 syn::Expr::Lit(lit) => match &lit.lit {
1403 syn::Lit::Str(s) => Some(s.value()),
1404 _ => None,
1405 },
1406 _ => None,
1407 }
1408}
1409
1410fn unwrap_expr(expr: &syn::Expr) -> &syn::Expr {
1412 match expr {
1413 syn::Expr::Try(t) => unwrap_expr(&t.expr),
1414 syn::Expr::Reference(r) => unwrap_expr(&r.expr),
1415 syn::Expr::Paren(p) => unwrap_expr(&p.expr),
1416 syn::Expr::Group(g) => unwrap_expr(&g.expr),
1417 other => other,
1418 }
1419}
1420
1421fn call_path(expr: &syn::Expr) -> Option<Vec<String>> {
1423 match unwrap_expr(expr) {
1424 syn::Expr::Path(p) => Some(
1425 p.path
1426 .segments
1427 .iter()
1428 .map(|s| s.ident.to_string())
1429 .collect(),
1430 ),
1431 _ => None,
1432 }
1433}
1434
1435fn known_constructor(path: &[String], func: &str) -> Option<&'static crate::known::Constructor> {
1441 let namespaced = path.len() >= 2
1442 && matches!(
1443 path.first().map(String::as_str),
1444 Some("candle_nn" | "nn" | "candle")
1445 );
1446 namespaced.then(|| known::lookup(func)).flatten()
1447}
1448
1449#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
1456struct RnnConfig {
1457 biases: Option<bool>,
1459 default_names: bool,
1461}
1462
1463fn rnn_config(
1464 func: &str,
1465 args: &syn::punctuated::Punctuated<syn::Expr, syn::Token![,]>,
1466) -> RnnConfig {
1467 if !matches!(func, "lstm" | "gru") {
1468 return RnnConfig::default();
1469 }
1470 let Some(text) = args.iter().nth(2).map(crate::load::type_text) else {
1472 return RnnConfig::default();
1473 };
1474 let text = text.replace(' ', "");
1475 match text.rsplit("::").next().unwrap_or(&text) {
1478 "default()" => RnnConfig {
1479 biases: Some(true),
1480 default_names: true,
1481 },
1482 "default_no_bias()" => RnnConfig {
1483 biases: Some(false),
1484 default_names: true,
1485 },
1486 _ => RnnConfig::default(),
1487 }
1488}
1489
1490fn config_named_family(leaf: &str) -> String {
1495 match leaf.strip_suffix("l0") {
1496 Some(stem) => format!("{stem}l{{layer_idx}}{{direction}}"),
1497 None => leaf.to_string(),
1498 }
1499}
1500
1501fn constructor_leaf_shape(
1502 func: &str,
1503 leaf: &str,
1504 args: &syn::punctuated::Punctuated<syn::Expr, syn::Token![,]>,
1505) -> Option<String> {
1506 let arg = |index: usize| args.iter().nth(index).map(crate::load::type_text);
1507 let pair =
1508 |left: Option<String>, right: Option<String>| Some(format!("({}, {})", left?, right?));
1509 match (func, leaf) {
1510 ("linear" | "linear_no_bias" | "linear_b", "weight") => pair(arg(1), arg(0)),
1511 ("linear" | "linear_b", "bias") => arg(1),
1512 ("embedding", "weight") => pair(arg(0), arg(1)),
1513 (
1514 "layer_norm" | "layer_norm_no_bias" | "rms_norm" | "batch_norm" | "group_norm",
1515 "weight" | "bias" | "running_mean" | "running_var",
1516 ) => {
1517 if func == "group_norm" {
1518 arg(1)
1519 } else {
1520 arg(0)
1521 }
1522 }
1523 ("prelu", "weight") => Some(format!("({}.unwrap_or(1),)", arg(0)?)),
1524 ("lstm" | "gru", leaf) if leaf.starts_with("weight_") || leaf.starts_with("bias_") => {
1527 let gates = if func == "lstm" { 4 } else { 3 };
1528 let rows = format!("{gates} * {}", arg(1)?);
1529 match (leaf.starts_with("weight_"), leaf.contains("_ih_")) {
1530 (true, true) => Some(format!("({rows}, {})", arg(0)?)),
1531 (true, false) => Some(format!("({rows}, {})", arg(1)?)),
1532 (false, _) => Some(format!("({rows},)")),
1533 }
1534 }
1535 ("conv1d" | "conv1d_no_bias", "weight") => Some(format!(
1536 "({}, {} / groups({}), {})",
1537 arg(1)?,
1538 arg(0)?,
1539 arg(3)?,
1540 arg(2)?
1541 )),
1542 ("conv2d" | "conv2d_no_bias", "weight") => Some(format!(
1543 "({}, {} / groups({}), {}, {})",
1544 arg(1)?,
1545 arg(0)?,
1546 arg(3)?,
1547 arg(2)?,
1548 arg(2)?
1549 )),
1550 ("conv_transpose1d" | "conv_transpose1d_no_bias", "weight") => Some(format!(
1551 "({}, {} / groups({}), {})",
1552 arg(0)?,
1553 arg(1)?,
1554 arg(3)?,
1555 arg(2)?
1556 )),
1557 ("conv_transpose2d" | "conv_transpose2d_no_bias", "weight") => Some(format!(
1558 "({}, {}, {}, {})",
1559 arg(0)?,
1560 arg(1)?,
1561 arg(2)?,
1562 arg(2)?
1563 )),
1564 (
1565 "conv1d"
1566 | "conv1d_no_bias"
1567 | "conv2d"
1568 | "conv2d_no_bias"
1569 | "conv_transpose1d"
1570 | "conv_transpose1d_no_bias"
1571 | "conv_transpose2d"
1572 | "conv_transpose2d_no_bias",
1573 "bias",
1574 ) => arg(1),
1575 _ => None,
1576 }
1577}
1578
1579fn normalize_qualified_path(path: &[String]) -> String {
1580 let mut path = path;
1581 while matches!(path.first().map(String::as_str), Some("crate" | "self")) {
1582 path = &path[1..];
1583 }
1584 path.join("::")
1585}
1586
1587fn normalize_qualified_name(name: &str) -> String {
1588 normalize_qualified_path(
1589 &name
1590 .split("::")
1591 .map(ToString::to_string)
1592 .collect::<Vec<_>>(),
1593 )
1594}
1595
1596fn looks_like_iteration(expr: &syn::Expr) -> bool {
1599 match unwrap_expr(expr) {
1600 syn::Expr::Range(_) => true,
1601 syn::Expr::MethodCall(mc) => matches!(
1602 mc.method.to_string().as_str(),
1603 "iter"
1604 | "into_iter"
1605 | "iter_mut"
1606 | "enumerate"
1607 | "zip"
1608 | "take"
1609 | "skip"
1610 | "rev"
1611 | "filter"
1612 | "chain"
1613 | "step_by"
1614 | "windows"
1615 | "chunks"
1616 ),
1617 _ => false,
1618 }
1619}
1620
1621fn binding_name(pat: &syn::Pat) -> Option<String> {
1622 match pat {
1623 syn::Pat::Ident(id) => Some(id.ident.to_string()),
1624 syn::Pat::Type(t) => binding_name(&t.pat),
1625 _ => None,
1626 }
1627}
1628
1629fn span_of_expr<T: syn::spanned::Spanned>(ctx: &Ctx, node: &T) -> SrcSpan {
1631 let start = node.span().start();
1632 SrcSpan {
1633 file: ctx.file,
1634 line: start.line,
1635 col: start.column,
1636 }
1637}