resolve.rs
41.8 kB · rust · 1196 lines
1use crate::model::*;2use crate::parse::{self, Files, Item, Tree};3use quote::ToTokens;4use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};56pub struct Built {7 pub manifest: Manifest,8 pub collisions: Vec<String>,9 pub macro_body_fns: usize,10}1112type Key = (String, usize);1314#[derive(Clone, PartialEq, Eq, Hash, Debug)]15enum Target {16 Module(String),17 Item(String, usize),18 External(Vec<String>),19}2021pub fn build(files: &Files, version: &str) -> Result<Built> {22 let tree = parse::parse(files)?;23 let reach = reach(&tree);24 let mut builder = Builder {25 tree: &tree,26 reach,27 cross: HashMap::new(),28 paths: HashMap::new(),29 defaults: HashMap::new(),30 };31 builder.classify_types()?;32 let manifest = builder.manifest(version);33 let collisions = collisions(&manifest);34 Ok(Built {35 manifest,36 collisions,37 macro_body_fns: tree.macro_body_fns,38 })39}4041// LOOKUP4243fn parent(key: &str) -> String {44 match key.rfind("::") {45 Some(i) => key[..i].to_string(),46 None => String::new(),47 }48}4950fn join(module: &str, name: &str) -> String {51 if module.is_empty() {52 name.to_string()53 } else {54 format!("{module}::{name}")55 }56}5758fn item_name(item: &Item) -> Option<&str> {59 match item {60 Item::Fn(f) => Some(&f.name),61 Item::Struct(s) => Some(&s.name),62 Item::Enum(e) => Some(&e.name),63 Item::Alias(a) => Some(&a.name),64 Item::Const(c) => Some(&c.name),65 Item::Trait(t) => Some(&t.name),66 Item::Impl(_) => None,67 }68}6970type Stack = Vec<(String, String)>;7172fn lookup(tree: &Tree, module: &str, name: &str, origin: &str, stack: &mut Stack) -> Vec<Target> {73 let mut out = vec![];74 let Some(m) = tree.modules.get(module) else {75 return out;76 };77 let key = (module.to_string(), name.to_string());78 if stack.len() > 32 || stack.contains(&key) {79 return out;80 }81 stack.push(key);82 let inside =83 module.is_empty() || origin == module || origin.starts_with(&format!("{module}::"));84 if m.children.iter().any(|c| c == name) {85 let child = join(module, name);86 if inside || tree.modules.get(&child).is_some_and(|c| c.public) {87 out.push(Target::Module(child));88 }89 }90 for (i, item) in m.items.iter().enumerate() {91 if item_name(item) == Some(name) {92 out.push(Target::Item(module.to_string(), i));93 }94 }95 for u in m96 .uses97 .iter()98 .filter(|u| !u.glob && u.name == name && (u.public || inside))99 {100 out.extend(resolve_path(tree, module, &u.path, stack));101 }102 for u in m.uses.iter().filter(|u| u.glob && (u.public || inside)) {103 for target in resolve_path(tree, module, &u.path, stack) {104 if let Target::Module(p) = target {105 out.extend(lookup(tree, &p, name, origin, stack));106 }107 }108 }109 stack.pop();110 out.dedup();111 out112}113114fn resolve_path(tree: &Tree, origin: &str, segs: &[String], stack: &mut Stack) -> Vec<Target> {115 if segs.is_empty() {116 return vec![];117 }118 let mut rest = &segs[1..];119 let mut current = match segs[0].as_str() {120 "crate" => vec![Target::Module(String::new())],121 "self" => vec![Target::Module(origin.to_string())],122 "super" => {123 let mut at = parent(origin);124 while rest.first().map(String::as_str) == Some("super") {125 at = parent(&at);126 rest = &rest[1..];127 }128 vec![Target::Module(at)]129 }130 first => {131 let found = lookup(tree, origin, first, origin, stack);132 if found.is_empty() {133 return vec![Target::External(segs.to_vec())];134 }135 found136 }137 };138 for (i, seg) in rest.iter().enumerate() {139 let last = i + 1 == rest.len();140 let mut next = vec![];141 for target in current {142 match target {143 Target::Module(p) => next.extend(lookup(tree, &p, seg, origin, stack)),144 Target::External(mut e) => {145 e.push(seg.clone());146 next.push(Target::External(e));147 }148 Target::Item(..) => {}149 }150 }151 if !last {152 next.retain(|t| !matches!(t, Target::Item(..)));153 }154 current = next;155 }156 current157}158159fn lookup_from(tree: &Tree, module: &str, name: &str) -> Vec<Target> {160 lookup(tree, module, name, module, &mut Stack::new())161}162163fn resolve_from(tree: &Tree, module: &str, segs: &[String]) -> Vec<Target> {164 resolve_path(tree, module, segs, &mut Stack::new())165}166167// REACH168169struct Reach {170 modules: HashMap<String, Vec<Vec<String>>>,171 items: HashMap<Key, Vec<Vec<String>>>,172}173174fn public_names(tree: &Tree, module: &str) -> Vec<(String, Target)> {175 let mut out = vec![];176 let Some(m) = tree.modules.get(module) else {177 return out;178 };179 for (i, item) in m.items.iter().enumerate() {180 if let Some(name) = item_name(item) {181 out.push((name.to_string(), Target::Item(module.to_string(), i)));182 }183 }184 for c in &m.children {185 let key = join(module, c);186 if tree.modules.get(&key).is_some_and(|child| child.public) {187 out.push((c.clone(), Target::Module(key)));188 }189 }190 for u in m.uses.iter().filter(|u| u.public) {191 if u.glob {192 for target in resolve_from(tree, module, &u.path) {193 if let Target::Module(p) = target {194 out.extend(public_names(tree, &p));195 }196 }197 } else {198 for target in resolve_from(tree, module, &u.path) {199 out.push((u.name.clone(), target));200 }201 }202 }203 out204}205206fn reach(tree: &Tree) -> Reach {207 let mut reach = Reach {208 modules: HashMap::new(),209 items: HashMap::new(),210 };211 let mut seen = HashSet::new();212 let mut queue = VecDeque::from([(String::new(), Vec::<String>::new())]);213 while let Some((module, path)) = queue.pop_front() {214 if path.len() > 12 || !seen.insert((module.clone(), path.clone())) {215 continue;216 }217 reach218 .modules219 .entry(module.clone())220 .or_default()221 .push(path.clone());222 for (name, target) in public_names(tree, &module) {223 let mut at = path.clone();224 at.push(name);225 match target {226 Target::Item(m, i) => reach.items.entry((m, i)).or_default().push(at),227 Target::Module(m) if tree.modules.get(&m).is_some_and(|x| x.public) => {228 queue.push_back((m, at))229 }230 Target::Module(_) | Target::External(_) => {}231 }232 }233 }234 reach235}236237fn shortest(candidates: &[Vec<String>], canonical: &str) -> Option<String> {238 candidates239 .iter()240 .map(|c| c.join("::"))241 .min_by_key(|p| (p.matches("::").count(), p != canonical, p.clone()))242}243244// BUILDER245246struct Scope<'a> {247 module: &'a str,248 generics: Vec<String>,249 self_ty: Option<Ty>,250}251252struct Owner {253 path: Option<String>,254 module: String,255 cross: TypeCross,256 dim: Option<u8>,257 ty: Ty,258}259260struct Builder<'a> {261 tree: &'a Tree,262 reach: Reach,263 cross: HashMap<Key, TypeCross>,264 paths: HashMap<Key, String>,265 defaults: HashMap<Key, String>,266}267268impl Builder<'_> {269 fn module(&self, key: &str) -> &parse::Module {270 &self.tree.modules[key]271 }272273 fn public_path(&self, key: &Key, name: &str) -> Option<String> {274 let canonical = join(&key.0, name);275 self.reach276 .items277 .get(key)278 .and_then(|c| shortest(c, &canonical))279 }280281 fn module_path(&self, key: &str) -> Option<String> {282 self.reach.modules.get(key).and_then(|c| shortest(c, key))283 }284285 fn nearest_public_module(&self, key: &str) -> String {286 let mut at = key.to_string();287 loop {288 if let Some(p) = self.module_path(&at) {289 return p;290 }291 if at.is_empty() {292 return at;293 }294 at = parent(&at);295 }296 }297298 fn impl_owner(&self, module: &str, imp: &parse::Impl) -> Option<(Key, Option<u8>)> {299 self.type_key(module, &imp.self_ty)300 }301302 fn type_key(&self, module: &str, ty: &syn::Type) -> Option<(Key, Option<u8>)> {303 let syn::Type::Path(p) = ty else {304 return None;305 };306 let segs: Vec<String> = p307 .path308 .segments309 .iter()310 .map(|s| s.ident.to_string())311 .collect();312 let dim = p.path.segments.last().and_then(|s| dim_of(&s.arguments));313 let targets = if segs.len() == 1 {314 lookup_from(self.tree, module, &segs[0])315 } else {316 resolve_from(self.tree, module, &segs)317 };318 targets.into_iter().find_map(|t| match t {319 Target::Item(m, i)320 if matches!(self.module(&m).items[i], Item::Struct(_) | Item::Enum(_)) =>321 {322 Some(((m, i), dim))323 }324 _ => None,325 })326 }327328 fn trait_of(&self, module: &str, path: &[String]) -> Option<(Key, &parse::Trait)> {329 let targets = if path.len() == 1 {330 lookup_from(self.tree, module, &path[0])331 } else {332 resolve_from(self.tree, module, path)333 };334 targets.into_iter().find_map(|t| match t {335 Target::Item(m, i) => match &self.module(&m).items[i] {336 Item::Trait(tr) => Some(((m, i), tr)),337 _ => None,338 },339 _ => None,340 })341 }342343 fn classify_types(&mut self) -> Result<()> {344 let mut with_methods = HashSet::new();345 let mut hand_seen: HashMap<String, usize> = HashMap::new();346 for (mk, m) in &self.tree.modules {347 for item in &m.items {348 let Item::Impl(imp) = item else { continue };349 let Some((key, _)) = self.impl_owner(mk, imp) else {350 continue;351 };352 if imp353 .trait_path354 .as_ref()355 .and_then(|p| p.last())356 .is_some_and(|t| t == "Default")357 {358 self.defaults359 .insert(key, format!("{}:{}", m.file, imp.line));360 continue;361 }362 let fns = match &imp.trait_path {363 Some(path) => match self.trait_of(mk, path) {364 Some((_, tr)) => &tr.fns,365 None => continue,366 },367 None => &imp.fns,368 };369 if fns.iter().any(|f| f.self_kind.is_some() && !f.generated) {370 with_methods.insert(key);371 }372 }373 }374 for (mk, m) in &self.tree.modules {375 for (i, item) in m.items.iter().enumerate() {376 let key = (mk.clone(), i);377 let derived = match item {378 Item::Struct(s) => s.derives.iter().any(|d| d == "Default").then_some(s.line),379 Item::Enum(e) => e.derives.iter().any(|d| d == "Default").then_some(e.line),380 _ => None,381 };382 if let Some(line) = derived {383 self.defaults384 .insert(key.clone(), format!("{}:{line}", m.file));385 }386 let (name, cross) = match item {387 Item::Struct(s) => {388 let cross = if HAND.contains(&s.name.as_str()) {389 *hand_seen.entry(s.name.clone()).or_default() += 1;390 TypeCross::Hand {391 name: s.name.clone(),392 }393 } else if with_methods.contains(&key)394 || s.fields.iter().any(|f| f.serde_skip)395 {396 TypeCross::Class397 } else if serde_both(&s.derives) {398 TypeCross::Plain399 } else {400 TypeCross::Uncrossable {401 reason: "no serde derives and no methods".into(),402 }403 };404 (&s.name, cross)405 }406 Item::Enum(e) => {407 let unit = e.variants.iter().all(|v| v.fields.is_empty());408 let cross = if unit && serde_both(&e.derives) {409 TypeCross::Enum {410 named: e.named,411 words: e.variants.iter().filter_map(|v| v.word.clone()).collect(),412 }413 } else if with_methods.contains(&key)414 || e.variants415 .iter()416 .flat_map(|v| &v.fields)417 .any(|f| f.serde_skip)418 {419 TypeCross::Class420 } else if serde_both(&e.derives) {421 TypeCross::Plain422 } else {423 TypeCross::Uncrossable {424 reason: "no serde derives and no methods".into(),425 }426 };427 (&e.name, cross)428 }429 Item::Trait(_)430 | Item::Fn(_)431 | Item::Alias(_)432 | Item::Const(_)433 | Item::Impl(_) => continue,434 };435 if let Some(p) = self.public_path(&key, name) {436 self.paths.insert(key.clone(), p);437 }438 self.cross.insert(key, cross);439 }440 }441 for (name, count) in hand_seen {442 if count > 1 {443 return Err(format!("hand type {name} is defined {count} times"));444 }445 }446 Ok(())447 }448449 // TYPES450451 fn ty(&self, scope: &Scope, t: &syn::Type) -> Ty {452 let text = t.to_token_stream().to_string();453 match t {454 syn::Type::Path(p) => self.path_ty(scope, p, &text),455 syn::Type::Reference(r) => {456 let lifetime = r.lifetime.as_ref().map(|l| format!("'{}", l.ident));457 let mutable = r.mutability.is_some();458 match (&*r.elem, &lifetime) {459 (syn::Type::Slice(s), None) => Ty::Slice {460 mutable,461 item: Box::new(self.ty(scope, &s.elem)),462 },463 (syn::Type::Path(p), None)464 if !mutable && p.qself.is_none() && p.path.is_ident("str") =>465 {466 Ty::Str467 }468 _ => Ty::Ref {469 mutable,470 lifetime,471 item: Box::new(self.ty(scope, &r.elem)),472 },473 }474 }475 syn::Type::Slice(s) => Ty::Slice {476 mutable: false,477 item: Box::new(self.ty(scope, &s.elem)),478 },479 syn::Type::Array(a) => match &a.len {480 syn::Expr::Lit(syn::ExprLit {481 lit: syn::Lit::Int(n),482 ..483 }) => Ty::Array {484 item: Box::new(self.ty(scope, &a.elem)),485 len: n.base10_parse().unwrap_or(0),486 },487 _ => Ty::Unknown { text },488 },489 syn::Type::Tuple(t) if t.elems.is_empty() => Ty::Unit,490 syn::Type::Tuple(t) => Ty::Tuple {491 items: t.elems.iter().map(|e| self.ty(scope, e)).collect(),492 },493 syn::Type::Paren(p) => self.ty(scope, &p.elem),494 syn::Type::ImplTrait(_) => Ty::Unknown {495 text: format!("impl Trait argument {text}"),496 },497 _ => Ty::Unknown { text },498 }499 }500501 fn path_ty(&self, scope: &Scope, p: &syn::TypePath, text: &str) -> Ty {502 let unknown = || Ty::Unknown {503 text: text.to_string(),504 };505 if p.qself.is_some() {506 return unknown();507 }508 let segs: Vec<String> = p509 .path510 .segments511 .iter()512 .map(|s| s.ident.to_string())513 .collect();514 let last = p.path.segments.last().expect("a path has a segment");515 let args: Vec<&syn::Type> = match &last.arguments {516 syn::PathArguments::AngleBracketed(a) => a517 .args518 .iter()519 .filter_map(|g| match g {520 syn::GenericArgument::Type(t) => Some(t),521 _ => None,522 })523 .collect(),524 _ => vec![],525 };526 let arg = |i: usize| args.get(i).map(|t| Box::new(self.ty(scope, t)));527 if segs.len() == 1 {528 let name = segs[0].as_str();529 if name == "Self" {530 return scope.self_ty.clone().unwrap_or_else(unknown);531 }532 if scope.generics.iter().any(|g| g == name) {533 return Ty::Unknown {534 text: format!("generic {name}"),535 };536 }537 match name {538 "bool" | "u8" | "u16" | "u32" | "u64" | "usize" | "i8" | "i16" | "i32" | "i64"539 | "f32" | "f64" | "char" => {540 return Ty::Scalar {541 name: name.to_string(),542 }543 }544 "u128" => return Ty::U128,545 "i128" => return Ty::I128,546 "String" => return Ty::String,547 "str" => return Ty::Str,548 "Vec" => return arg(0).map_or_else(unknown, |item| Ty::Vec { item }),549 "Option" => return arg(0).map_or_else(unknown, |item| Ty::Option { item }),550 "Result" => {551 let crate_error = args.len() == 1552 || (args.len() == 2553 && matches!(self.ty(scope, args[1]), Ty::Opaque { path } if path.rsplit("::").next() == Some("Error")));554 return match (crate_error, arg(0)) {555 (true, Some(item)) => Ty::Result { item },556 _ => unknown(),557 };558 }559 _ => {}560 }561 }562 let targets = if segs.len() == 1 {563 lookup_from(self.tree, scope.module, &segs[0])564 } else {565 resolve_from(self.tree, scope.module, &segs)566 };567 for target in targets {568 match target {569 Target::Item(m, i) => return self.item_ty(&(m, i), dim_of(&last.arguments), text),570 Target::External(e) => {571 return match e.join("::").as_str() {572 "serde_json::Value" | "serde_json::Map" => Ty::Json,573 "std::collections::HashMap" | "std::collections::BTreeMap"574 if args.len() == 2 =>575 {576 Ty::Map {577 key: arg(0).expect("a key"),578 value: arg(1).expect("a value"),579 }580 }581 "std::collections::BTreeSet" if args.len() == 1 => Ty::Set {582 item: arg(0).expect("an item"),583 },584 _ => unknown(),585 }586 }587 Target::Module(_) => {}588 }589 }590 if segs.len() == 1 {591 return Ty::Unknown {592 text: format!("unknown type {text}"),593 };594 }595 unknown()596 }597598 fn item_ty(&self, key: &Key, dim: Option<u8>, text: &str) -> Ty {599 let item = &self.module(&key.0).items[key.1];600 if let Item::Alias(a) = item {601 if a.generic {602 return Ty::Unknown {603 text: format!("generic alias {text}"),604 };605 }606 let scope = Scope {607 module: &key.0,608 generics: vec![],609 self_ty: None,610 };611 return self.ty(&scope, &a.ty);612 }613 let Some(cross) = self.cross.get(key) else {614 return Ty::Unknown {615 text: text.to_string(),616 };617 };618 let name = item_name(item).unwrap_or_default();619 let Some(path) = self.paths.get(key).cloned() else {620 return Ty::Unknown {621 text: format!("private type {name}"),622 };623 };624 match cross {625 TypeCross::Hand { name } if name == "Code" => Ty::Code,626 TypeCross::Hand { name } => Ty::Hand {627 name: name.clone(),628 dim,629 },630 TypeCross::Class => Ty::Class { path, dim },631 TypeCross::Plain => Ty::Plain { path },632 TypeCross::Enum { .. } => Ty::Enum { path },633 TypeCross::Uncrossable { .. } => Ty::Opaque { path },634 }635 }636637 fn owner(&self, module: &str, imp: &parse::Impl) -> Option<Owner> {638 let (key, dim) = self.impl_owner(module, imp)?;639 let cross = self.cross[&key].clone();640 let path = self.paths.get(&key).cloned();641 let ty = self.item_ty(&key, dim, "Self");642 let target = match cross {643 TypeCross::Hand { .. } => self.nearest_public_module(&key.0),644 _ => path.as_deref().map(parent).unwrap_or_else(|| key.0.clone()),645 };646 Some(Owner {647 path,648 module: target,649 cross,650 dim,651 ty,652 })653 }654655 // ENTRIES656657 fn manifest(&self, version: &str) -> Manifest {658 let mut types = vec![];659 let mut consts = vec![];660 let mut functions = vec![];661 for (mk, m) in &self.tree.modules {662 let scope = Scope {663 module: mk,664 generics: vec![],665 self_ty: None,666 };667 for (i, item) in m.items.iter().enumerate() {668 let key = (mk.clone(), i);669 match item {670 Item::Struct(s) => {671 let Some(path) = self.paths.get(&key) else {672 continue;673 };674 types.push(Type {675 path: path.clone(),676 name: s.name.clone(),677 defined_at: format!("{}:{}", m.file, s.line),678 docs: s.docs.clone(),679 kind: TypeKind::Struct,680 cross: self.cross[&key].clone(),681 derives: s.derives.clone(),682 serde: s.serde.clone(),683 const_generic: s.const_generic,684 fields: self.fields(&scope, &s.fields),685 variants: vec![],686 alias: None,687 });688 functions.extend(self.default_of(&key, path, s.const_generic));689 }690 Item::Enum(e) => {691 let Some(path) = self.paths.get(&key) else {692 continue;693 };694 types.push(Type {695 path: path.clone(),696 name: e.name.clone(),697 defined_at: format!("{}:{}", m.file, e.line),698 docs: e.docs.clone(),699 kind: TypeKind::Enum,700 cross: self.cross[&key].clone(),701 derives: e.derives.clone(),702 serde: e.serde.clone(),703 const_generic: false,704 fields: vec![],705 variants: e706 .variants707 .iter()708 .map(|v| Variant {709 name: v.name.clone(),710 word: v.word.clone(),711 docs: v.docs.clone(),712 serde: v.serde.clone(),713 fields: self.fields(&scope, &v.fields),714 })715 .collect(),716 alias: None,717 });718 functions.extend(self.default_of(&key, path, false));719 }720 Item::Alias(a) => {721 let Some(path) = self.public_path(&key, &a.name) else {722 continue;723 };724 let ty = self.item_ty(&key, None, &a.name);725 let cross = match &ty {726 Ty::Hand { name, .. } => TypeCross::Hand { name: name.clone() },727 Ty::Class { .. } => TypeCross::Class,728 Ty::Enum { .. } => TypeCross::Enum {729 named: false,730 words: vec![],731 },732 _ => match uncrossable(&ty) {733 Some(reason) => TypeCross::Uncrossable { reason },734 None => TypeCross::Plain,735 },736 };737 functions.extend(self.alias_default(mk, a, &path, &ty));738 types.push(Type {739 path,740 name: a.name.clone(),741 defined_at: format!("{}:{}", m.file, a.line),742 docs: a.docs.clone(),743 kind: TypeKind::Alias,744 cross,745 derives: vec![],746 serde: vec![],747 const_generic: false,748 fields: vec![],749 variants: vec![],750 alias: Some(ty),751 });752 }753 Item::Const(c) => {754 let Some(path) = self.public_path(&key, &c.name) else {755 continue;756 };757 let ty = self.ty(&scope, &c.ty);758 let cross = match uncrossable(&ty)759 .or_else(|| borrow(&ty).map(|b| format!("{b} const")))760 {761 Some(reason) => Cross::Skip { reason },762 None => Cross::Ok,763 };764 consts.push(Const {765 path,766 name: c.name.clone(),767 defined_at: format!("{}:{}", m.file, c.line),768 docs: c.docs.clone(),769 ty,770 cross,771 });772 }773 Item::Fn(f) => {774 let path = self.public_path(&key, &f.name);775 functions.push(self.function(776 mk,777 &m.file,778 f,779 path,780 None,781 join(mk, &f.name),782 ));783 }784 Item::Trait(_) => {}785 Item::Impl(imp) if imp.trait_path.is_some() => {786 let path = imp.trait_path.as_deref().unwrap_or_default();787 let Some((tkey, tr)) = self.trait_of(mk, path) else {788 continue;789 };790 let Some(via) = self.public_path(&tkey, &tr.name) else {791 continue;792 };793 let Some(owner) = self.owner(mk, imp) else {794 continue;795 };796 let Some(owner_path) = owner.path.clone() else {797 continue;798 };799 let file = self.module(&tkey.0).file.clone();800 for f in &tr.fns {801 let path = match owner.cross {802 TypeCross::Hand { .. } => join(&owner.module, &f.name),803 _ => format!("{owner_path}::{}", f.name),804 };805 let mut entry = self.function(806 &tkey.0,807 &file,808 f,809 Some(path.clone()),810 Some((&owner, imp)),811 path,812 );813 entry.module = owner.module.clone();814 entry.source = Source::Trait;815 entry.via = Some(via.clone());816 functions.push(entry);817 }818 }819 Item::Impl(imp) => {820 let Some(owner) = self.owner(mk, imp) else {821 let text = imp.self_ty.to_token_stream().to_string().replace(' ', "");822 for f in &imp.fns {823 let fallback = format!("{}::{}", join(mk, &text), f.name);824 functions.push(self.function(mk, &m.file, f, None, None, fallback));825 }826 continue;827 };828 for f in &imp.fns {829 let path = owner.path.as_ref().map(|p| match owner.cross {830 TypeCross::Hand { .. } => join(&owner.module, &f.name),831 _ => format!("{p}::{}", f.name),832 });833 let fallback = format!(834 "{}::{}",835 owner.path.clone().unwrap_or_else(|| join(mk, "?")),836 f.name837 );838 let mut entry =839 self.function(mk, &m.file, f, path, Some((&owner, imp)), fallback);840 if entry.cross != Cross::Private {841 entry.module = owner.module.clone();842 }843 functions.push(entry);844 }845 }846 }847 }848 }849 types.sort_by(|a, b| a.path.cmp(&b.path));850 consts.sort_by(|a, b| a.path.cmp(&b.path));851 functions.sort_by(|a, b| {852 (&a.path, &a.dims, &a.defined_at).cmp(&(&b.path, &b.dims, &b.defined_at))853 });854 let mut modules: Vec<Module> = self855 .tree856 .modules857 .iter()858 .filter_map(|(mk, m)| {859 let path = self.module_path(mk)?;860 let items = functions861 .iter()862 .filter(|f| f.cross != Cross::Private && f.module == path)863 .count()864 + types.iter().filter(|t| parent(&t.path) == path).count()865 + consts.iter().filter(|c| parent(&c.path) == path).count();866 Some(Module {867 path,868 file: m.file.clone(),869 docs: m.docs.clone(),870 items,871 })872 })873 .collect();874 modules.sort_by(|a, b| a.path.cmp(&b.path));875 modules.dedup_by(|a, b| a.path == b.path);876 Manifest {877 krate: "mrlyrs".into(),878 version: version.into(),879 modules,880 types,881 consts,882 functions,883 }884 }885886 fn default_of(&self, key: &Key, owner: &str, generic: bool) -> Option<Function> {887 let at = self.defaults.get(key)?;888 let path = owner.to_string();889 let ret = match self.cross.get(key)? {890 TypeCross::Class => Ty::Class { path, dim: None },891 TypeCross::Plain => Ty::Plain { path },892 TypeCross::Enum { .. } => Ty::Enum { path },893 TypeCross::Hand { .. } | TypeCross::Uncrossable { .. } => return None,894 };895 (!generic).then(|| default_fn(owner, at, ret))896 }897898 fn alias_default(899 &self,900 module: &str,901 alias: &parse::Alias,902 owner: &str,903 ty: &Ty,904 ) -> Option<Function> {905 if alias.generic || !matches!(ty, Ty::Plain { .. } | Ty::Class { .. } | Ty::Enum { .. }) {906 return None;907 }908 let (target, _) = self.type_key(module, &alias.ty)?;909 let at = self.defaults.get(&target)?;910 let generic =911 matches!(&self.module(&target.0).items[target.1], Item::Struct(s) if s.const_generic);912 generic.then(|| default_fn(owner, at, ty.clone()))913 }914915 fn fields(&self, scope: &Scope, fields: &[parse::Field]) -> Vec<Field> {916 fields917 .iter()918 .map(|f| Field {919 name: f.name.clone(),920 public: f.public,921 docs: f.docs.clone(),922 serde: f.serde.clone(),923 serde_skip: f.serde_skip,924 ty: self.ty(scope, &f.ty),925 })926 .collect()927 }928929 fn function(930 &self,931 module: &str,932 file: &str,933 f: &parse::Fn,934 path: Option<String>,935 owner: Option<(&Owner, &parse::Impl)>,936 fallback: String,937 ) -> Function {938 let mut generics: Vec<String> = f.type_params.iter().map(|(n, _)| n.clone()).collect();939 let mut dims = vec![];940 if let Some((o, imp)) = owner {941 generics.extend(imp.type_params.iter().cloned());942 match o.dim {943 Some(d) => dims.push(d),944 None if !imp.const_params.is_empty() => dims.extend([2, 3]),945 None => {}946 }947 }948 if dims.is_empty() && !f.const_params.is_empty() {949 dims.extend([2, 3]);950 }951 let scope = Scope {952 module,953 generics,954 self_ty: owner.map(|(o, _)| o.ty.clone()),955 };956 let params: Vec<Param> = f957 .params958 .iter()959 .map(|(name, ty)| Param {960 name: name.clone(),961 ty: self.ty(&scope, ty),962 })963 .collect();964 let ret = f.ret.as_ref().map_or(Ty::Unit, |t| self.ty(&scope, t));965 let private = path.is_none();966 let path = path.unwrap_or(fallback);967 let mut entry = Function {968 path: path.clone(),969 name: f.name.clone(),970 module: parent(&path),971 defined_at: format!("{file}:{}", f.line),972 docs: f.docs.clone(),973 owner: owner.and_then(|(o, _)| o.path.clone()),974 self_kind: f.self_kind,975 params,976 ret,977 dims,978 via: None,979 source: if f.generated {980 Source::NamedEnum981 } else {982 Source::Written983 },984 constant: f.constant,985 cross: Cross::Private,986 };987 if !private {988 entry.cross = self.classify(&entry, f);989 }990 entry991 }992993 fn classify(&self, entry: &Function, f: &parse::Fn) -> Cross {994 let skip = |reason: String| Cross::Skip { reason };995 if let Some((name, bounds)) = f.type_params.first() {996 if bounds.contains("Fn") || f.where_text.contains("Fn") {997 return skip(format!("closure parameter {name}"));998 }999 if entry.module == "core::error" {1000 return skip("error constructor".into());1001 }1002 return skip(format!("generic over {name}"));1003 }1004 for p in &entry.params {1005 if let Some(reason) = uncrossable(&p.ty) {1006 return skip(format!("{}: {reason}", p.name));1007 }1008 if let Ty::Ref {1009 mutable: true,1010 item,1011 ..1012 } = &p.ty1013 {1014 if !matches!(**item, Ty::Hand { .. } | Ty::Class { .. }) {1015 return skip(format!("{}: mutates plain data in place", p.name));1016 }1017 }1018 if let Ty::Slice { mutable: true, .. } = &p.ty {1019 return skip(format!("{}: mutable slice argument", p.name));1020 }1021 }1022 if let Some(reason) = uncrossable(&entry.ret) {1023 return skip(format!("returns {reason}"));1024 }1025 match borrow(&entry.ret) {1026 Some(_) if !f.generated && static_str(&entry.ret) => {}1027 Some(borrow) => return skip(format!("{borrow} return")),1028 None => {}1029 }1030 let unit = entry1031 .module1032 .split("::")1033 .next()1034 .unwrap_or_default()1035 .to_string();1036 let mut foreign = None;1037 let mut see = |t: &Ty| {1038 if let Ty::Class { path, .. } = t {1039 let home = path.split("::").next().unwrap_or_default();1040 if home != unit && foreign.is_none() {1041 foreign = Some(format!("class {path} lives in unit {home}"));1042 }1043 }1044 };1045 entry.params.iter().for_each(|p| p.ty.walk(&mut see));1046 entry.ret.walk(&mut see);1047 if let Some(reason) = foreign {1048 return skip(reason);1049 }1050 Cross::Ok1051 }1052}10531054fn default_fn(owner: &str, defined_at: &str, ret: Ty) -> Function {1055 let name = owner.rsplit("::").next().unwrap_or(owner);1056 Function {1057 path: format!("{owner}::default"),1058 name: "default".into(),1059 module: parent(owner),1060 defined_at: defined_at.to_string(),1061 docs: vec![format!("Returns the default {name}.")],1062 owner: Some(owner.to_string()),1063 self_kind: None,1064 params: vec![],1065 ret,1066 dims: vec![],1067 via: None,1068 source: Source::Default,1069 constant: false,1070 cross: Cross::Ok,1071 }1072}10731074fn borrow(ty: &Ty) -> Option<&'static str> {1075 if ty.any(&|t| matches!(t, Ty::Ref { lifetime: Some(l), .. } if l == "'static")) {1076 Some("&'static")1077 } else if ty.any(&|t| {1078 matches!(1079 t,1080 Ty::Ref {1081 lifetime: Some(_),1082 ..1083 }1084 )1085 }) {1086 Some("lifetime-bearing")1087 } else if ty.any(&|t| {1088 matches!(1089 t,1090 Ty::Ref { mutable: true, .. } | Ty::Slice { mutable: true, .. }1091 )1092 }) {1093 Some("mutable borrow")1094 } else {1095 None1096 }1097}10981099fn static_str(ty: &Ty) -> bool {1100 matches!(1101 ty,1102 Ty::Ref { mutable: false, lifetime: Some(l), item }1103 if l == "'static" && matches!(**item, Ty::Str)1104 )1105}11061107fn dim_of(args: &syn::PathArguments) -> Option<u8> {1108 let syn::PathArguments::AngleBracketed(a) = args else {1109 return None;1110 };1111 match a.args.first()? {1112 syn::GenericArgument::Const(syn::Expr::Lit(syn::ExprLit {1113 lit: syn::Lit::Int(n),1114 ..1115 })) => n.base10_parse().ok(),1116 _ => None,1117 }1118}11191120fn serde_both(derives: &[String]) -> bool {1121 derives.iter().any(|d| d == "Serialize") && derives.iter().any(|d| d == "Deserialize")1122}11231124pub fn uncrossable(ty: &Ty) -> Option<String> {1125 let mut reason = None;1126 ty.walk(&mut |t| {1127 if reason.is_some() {1128 return;1129 }1130 reason = match t {1131 Ty::Unknown { text } => Some(text.clone()),1132 Ty::Opaque { path } => Some(format!("uncrossable type {path}")),1133 Ty::Unit => None,1134 Ty::Map { key, .. } if !matches!(**key, Ty::Scalar { .. } | Ty::String | Ty::Str) => {1135 Some("map keyed by a non-scalar".into())1136 }1137 _ => None,1138 };1139 if let Ty::Unknown { text } = t {1140 if text.contains("Iterator") {1141 reason = Some("iterator return".into());1142 }1143 }1144 });1145 reason1146}11471148fn collisions(m: &Manifest) -> Vec<String> {1149 let mut out = vec![];1150 let mut free: BTreeMap<(String, String), Vec<&Function>> = BTreeMap::new();1151 for f in m.functions.iter().filter(|f| f.cross != Cross::Private) {1152 free.entry((f.module.clone(), f.path.clone()))1153 .or_default()1154 .push(f);1155 }1156 for ((_, path), entries) in free {1157 if entries.len() < 2 {1158 continue;1159 }1160 let dims: Vec<&u8> = entries.iter().flat_map(|f| f.dims.iter()).collect();1161 let distinct: HashSet<&u8> = dims.iter().copied().collect();1162 if !dims.is_empty()1163 && dims.len() == distinct.len()1164 && entries.iter().all(|f| !f.dims.is_empty())1165 {1166 continue;1167 }1168 let at: Vec<&str> = entries.iter().map(|f| f.defined_at.as_str()).collect();1169 out.push(format!("collision: {path} at {}", at.join(" and ")));1170 }1171 let homes: HashSet<&str> = m1172 .modules1173 .iter()1174 .filter(|x| x.items > 0)1175 .map(|x| x.path.as_str())1176 .collect();1177 for f in m.functions.iter().filter(|f| f.cross != Cross::Private) {1178 if homes.contains(f.path.as_str()) {1179 out.push(format!(1180 "collision: module {} and fn {} at {}",1181 f.path, f.path, f.defined_at1182 ));1183 }1184 }1185 for t in &m.types {1186 if homes.contains(t.path.as_str()) {1187 out.push(format!(1188 "collision: module {} and type {} at {}",1189 t.path, t.path, t.defined_at1190 ));1191 }1192 }1193 out.sort();1194 out.dedup();1195 out1196}