resolve.rs
41.1 kB · rust · 1167 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 imp.trait_path.as_ref().and_then(|p| p.last()).is_some_and(|t| t == "Default") {353 self.defaults.insert(key, format!("{}:{}", m.file, imp.line));354 continue;355 }356 let fns = match &imp.trait_path {357 Some(path) => match self.trait_of(mk, path) {358 Some((_, tr)) => &tr.fns,359 None => continue,360 },361 None => &imp.fns,362 };363 if fns.iter().any(|f| f.self_kind.is_some() && !f.generated) {364 with_methods.insert(key);365 }366 }367 }368 for (mk, m) in &self.tree.modules {369 for (i, item) in m.items.iter().enumerate() {370 let key = (mk.clone(), i);371 let derived = match item {372 Item::Struct(s) => s.derives.iter().any(|d| d == "Default").then_some(s.line),373 Item::Enum(e) => e.derives.iter().any(|d| d == "Default").then_some(e.line),374 _ => None,375 };376 if let Some(line) = derived {377 self.defaults.insert(key.clone(), format!("{}:{line}", m.file));378 }379 let (name, cross) = match item {380 Item::Struct(s) => {381 let cross = if HAND.contains(&s.name.as_str()) {382 *hand_seen.entry(s.name.clone()).or_default() += 1;383 TypeCross::Hand {384 name: s.name.clone(),385 }386 } else if with_methods.contains(&key) || s.fields.iter().any(|f| f.serde_skip) {387 TypeCross::Class388 } else if serde_both(&s.derives) {389 TypeCross::Plain390 } else {391 TypeCross::Uncrossable {392 reason: "no serde derives and no methods".into(),393 }394 };395 (&s.name, cross)396 }397 Item::Enum(e) => {398 let unit = e.variants.iter().all(|v| v.fields.is_empty());399 let cross = if unit && serde_both(&e.derives) {400 TypeCross::Enum {401 named: e.named,402 words: e.variants.iter().filter_map(|v| v.word.clone()).collect(),403 }404 } else if with_methods.contains(&key)405 || e.variants.iter().flat_map(|v| &v.fields).any(|f| f.serde_skip)406 {407 TypeCross::Class408 } else if serde_both(&e.derives) {409 TypeCross::Plain410 } else {411 TypeCross::Uncrossable {412 reason: "no serde derives and no methods".into(),413 }414 };415 (&e.name, cross)416 }417 Item::Trait(_)418 | Item::Fn(_)419 | Item::Alias(_)420 | Item::Const(_)421 | Item::Impl(_) => continue,422 };423 if let Some(p) = self.public_path(&key, name) {424 self.paths.insert(key.clone(), p);425 }426 self.cross.insert(key, cross);427 }428 }429 for (name, count) in hand_seen {430 if count > 1 {431 return Err(format!("hand type {name} is defined {count} times"));432 }433 }434 Ok(())435 }436437 // TYPES438439 fn ty(&self, scope: &Scope, t: &syn::Type) -> Ty {440 let text = t.to_token_stream().to_string();441 match t {442 syn::Type::Path(p) => self.path_ty(scope, p, &text),443 syn::Type::Reference(r) => {444 let lifetime = r.lifetime.as_ref().map(|l| format!("'{}", l.ident));445 let mutable = r.mutability.is_some();446 match (&*r.elem, &lifetime) {447 (syn::Type::Slice(s), None) => Ty::Slice {448 mutable,449 item: Box::new(self.ty(scope, &s.elem)),450 },451 (syn::Type::Path(p), None)452 if !mutable && p.qself.is_none() && p.path.is_ident("str") =>453 {454 Ty::Str455 }456 _ => Ty::Ref {457 mutable,458 lifetime,459 item: Box::new(self.ty(scope, &r.elem)),460 },461 }462 }463 syn::Type::Slice(s) => Ty::Slice {464 mutable: false,465 item: Box::new(self.ty(scope, &s.elem)),466 },467 syn::Type::Array(a) => match &a.len {468 syn::Expr::Lit(syn::ExprLit {469 lit: syn::Lit::Int(n),470 ..471 }) => Ty::Array {472 item: Box::new(self.ty(scope, &a.elem)),473 len: n.base10_parse().unwrap_or(0),474 },475 _ => Ty::Unknown { text },476 },477 syn::Type::Tuple(t) if t.elems.is_empty() => Ty::Unit,478 syn::Type::Tuple(t) => Ty::Tuple {479 items: t.elems.iter().map(|e| self.ty(scope, e)).collect(),480 },481 syn::Type::Paren(p) => self.ty(scope, &p.elem),482 syn::Type::ImplTrait(_) => Ty::Unknown {483 text: format!("impl Trait argument {text}"),484 },485 _ => Ty::Unknown { text },486 }487 }488489 fn path_ty(&self, scope: &Scope, p: &syn::TypePath, text: &str) -> Ty {490 let unknown = || Ty::Unknown {491 text: text.to_string(),492 };493 if p.qself.is_some() {494 return unknown();495 }496 let segs: Vec<String> = p497 .path498 .segments499 .iter()500 .map(|s| s.ident.to_string())501 .collect();502 let last = p.path.segments.last().expect("a path has a segment");503 let args: Vec<&syn::Type> = match &last.arguments {504 syn::PathArguments::AngleBracketed(a) => a505 .args506 .iter()507 .filter_map(|g| match g {508 syn::GenericArgument::Type(t) => Some(t),509 _ => None,510 })511 .collect(),512 _ => vec![],513 };514 let arg = |i: usize| args.get(i).map(|t| Box::new(self.ty(scope, t)));515 if segs.len() == 1 {516 let name = segs[0].as_str();517 if name == "Self" {518 return scope.self_ty.clone().unwrap_or_else(unknown);519 }520 if scope.generics.iter().any(|g| g == name) {521 return Ty::Unknown {522 text: format!("generic {name}"),523 };524 }525 match name {526 "bool" | "u8" | "u16" | "u32" | "u64" | "usize" | "i8" | "i16" | "i32" | "i64"527 | "f32" | "f64" | "char" => {528 return Ty::Scalar {529 name: name.to_string(),530 }531 }532 "u128" => return Ty::U128,533 "i128" => return Ty::I128,534 "String" => return Ty::String,535 "str" => return Ty::Str,536 "Vec" => return arg(0).map_or_else(unknown, |item| Ty::Vec { item }),537 "Option" => return arg(0).map_or_else(unknown, |item| Ty::Option { item }),538 "Result" => {539 let crate_error = args.len() == 1540 || (args.len() == 2541 && matches!(self.ty(scope, args[1]), Ty::Opaque { path } if path.rsplit("::").next() == Some("Error")));542 return match (crate_error, arg(0)) {543 (true, Some(item)) => Ty::Result { item },544 _ => unknown(),545 };546 }547 _ => {}548 }549 }550 let targets = if segs.len() == 1 {551 lookup_from(self.tree, scope.module, &segs[0])552 } else {553 resolve_from(self.tree, scope.module, &segs)554 };555 for target in targets {556 match target {557 Target::Item(m, i) => return self.item_ty(&(m, i), dim_of(&last.arguments), text),558 Target::External(e) => {559 return match e.join("::").as_str() {560 "serde_json::Value" | "serde_json::Map" => Ty::Json,561 "std::collections::HashMap" | "std::collections::BTreeMap"562 if args.len() == 2 =>563 {564 Ty::Map {565 key: arg(0).expect("a key"),566 value: arg(1).expect("a value"),567 }568 }569 "std::collections::BTreeSet" if args.len() == 1 => Ty::Set {570 item: arg(0).expect("an item"),571 },572 _ => unknown(),573 }574 }575 Target::Module(_) => {}576 }577 }578 if segs.len() == 1 {579 return Ty::Unknown {580 text: format!("unknown type {text}"),581 };582 }583 unknown()584 }585586 fn item_ty(&self, key: &Key, dim: Option<u8>, text: &str) -> Ty {587 let item = &self.module(&key.0).items[key.1];588 if let Item::Alias(a) = item {589 if a.generic {590 return Ty::Unknown {591 text: format!("generic alias {text}"),592 };593 }594 let scope = Scope {595 module: &key.0,596 generics: vec![],597 self_ty: None,598 };599 return self.ty(&scope, &a.ty);600 }601 let Some(cross) = self.cross.get(key) else {602 return Ty::Unknown {603 text: text.to_string(),604 };605 };606 let name = item_name(item).unwrap_or_default();607 let Some(path) = self.paths.get(key).cloned() else {608 return Ty::Unknown {609 text: format!("private type {name}"),610 };611 };612 match cross {613 TypeCross::Hand { name } if name == "Code" => Ty::Code,614 TypeCross::Hand { name } => Ty::Hand {615 name: name.clone(),616 dim,617 },618 TypeCross::Class => Ty::Class { path, dim },619 TypeCross::Plain => Ty::Plain { path },620 TypeCross::Enum { .. } => Ty::Enum { path },621 TypeCross::Uncrossable { .. } => Ty::Opaque { path },622 }623 }624625 fn owner(&self, module: &str, imp: &parse::Impl) -> Option<Owner> {626 let (key, dim) = self.impl_owner(module, imp)?;627 let cross = self.cross[&key].clone();628 let path = self.paths.get(&key).cloned();629 let ty = self.item_ty(&key, dim, "Self");630 let target = match cross {631 TypeCross::Hand { .. } => self.nearest_public_module(&key.0),632 _ => path.as_deref().map(parent).unwrap_or_else(|| key.0.clone()),633 };634 Some(Owner {635 path,636 module: target,637 cross,638 dim,639 ty,640 })641 }642643 // ENTRIES644645 fn manifest(&self, version: &str) -> Manifest {646 let mut types = vec![];647 let mut consts = vec![];648 let mut functions = vec![];649 for (mk, m) in &self.tree.modules {650 let scope = Scope {651 module: mk,652 generics: vec![],653 self_ty: None,654 };655 for (i, item) in m.items.iter().enumerate() {656 let key = (mk.clone(), i);657 match item {658 Item::Struct(s) => {659 let Some(path) = self.paths.get(&key) else {660 continue;661 };662 types.push(Type {663 path: path.clone(),664 name: s.name.clone(),665 defined_at: format!("{}:{}", m.file, s.line),666 docs: s.docs.clone(),667 kind: TypeKind::Struct,668 cross: self.cross[&key].clone(),669 derives: s.derives.clone(),670 serde: s.serde.clone(),671 const_generic: s.const_generic,672 fields: self.fields(&scope, &s.fields),673 variants: vec![],674 alias: None,675 });676 functions.extend(self.default_of(&key, path, s.const_generic));677 }678 Item::Enum(e) => {679 let Some(path) = self.paths.get(&key) else {680 continue;681 };682 types.push(Type {683 path: path.clone(),684 name: e.name.clone(),685 defined_at: format!("{}:{}", m.file, e.line),686 docs: e.docs.clone(),687 kind: TypeKind::Enum,688 cross: self.cross[&key].clone(),689 derives: e.derives.clone(),690 serde: e.serde.clone(),691 const_generic: false,692 fields: vec![],693 variants: e694 .variants695 .iter()696 .map(|v| Variant {697 name: v.name.clone(),698 word: v.word.clone(),699 docs: v.docs.clone(),700 serde: v.serde.clone(),701 fields: self.fields(&scope, &v.fields),702 })703 .collect(),704 alias: None,705 });706 functions.extend(self.default_of(&key, path, false));707 }708 Item::Alias(a) => {709 let Some(path) = self.public_path(&key, &a.name) else {710 continue;711 };712 let ty = self.item_ty(&key, None, &a.name);713 let cross = match &ty {714 Ty::Hand { name, .. } => TypeCross::Hand { name: name.clone() },715 Ty::Class { .. } => TypeCross::Class,716 Ty::Enum { .. } => TypeCross::Enum {717 named: false,718 words: vec![],719 },720 _ => match uncrossable(&ty) {721 Some(reason) => TypeCross::Uncrossable { reason },722 None => TypeCross::Plain,723 },724 };725 functions.extend(self.alias_default(mk, a, &path, &ty));726 types.push(Type {727 path,728 name: a.name.clone(),729 defined_at: format!("{}:{}", m.file, a.line),730 docs: a.docs.clone(),731 kind: TypeKind::Alias,732 cross,733 derives: vec![],734 serde: vec![],735 const_generic: false,736 fields: vec![],737 variants: vec![],738 alias: Some(ty),739 });740 }741 Item::Const(c) => {742 let Some(path) = self.public_path(&key, &c.name) else {743 continue;744 };745 let ty = self.ty(&scope, &c.ty);746 let cross = match uncrossable(&ty)747 .or_else(|| borrow(&ty).map(|b| format!("{b} const")))748 {749 Some(reason) => Cross::Skip { reason },750 None => Cross::Ok,751 };752 consts.push(Const {753 path,754 name: c.name.clone(),755 defined_at: format!("{}:{}", m.file, c.line),756 docs: c.docs.clone(),757 ty,758 cross,759 });760 }761 Item::Fn(f) => {762 let path = self.public_path(&key, &f.name);763 functions.push(self.function(764 mk,765 &m.file,766 f,767 path,768 None,769 join(mk, &f.name),770 ));771 }772 Item::Trait(_) => {}773 Item::Impl(imp) if imp.trait_path.is_some() => {774 let path = imp.trait_path.as_deref().unwrap_or_default();775 let Some((tkey, tr)) = self.trait_of(mk, path) else {776 continue;777 };778 let Some(via) = self.public_path(&tkey, &tr.name) else {779 continue;780 };781 let Some(owner) = self.owner(mk, imp) else {782 continue;783 };784 let Some(owner_path) = owner.path.clone() else {785 continue;786 };787 let file = self.module(&tkey.0).file.clone();788 for f in &tr.fns {789 let path = match owner.cross {790 TypeCross::Hand { .. } => join(&owner.module, &f.name),791 _ => format!("{owner_path}::{}", f.name),792 };793 let mut entry = self.function(794 &tkey.0,795 &file,796 f,797 Some(path.clone()),798 Some((&owner, imp)),799 path,800 );801 entry.module = owner.module.clone();802 entry.source = Source::Trait;803 entry.via = Some(via.clone());804 functions.push(entry);805 }806 }807 Item::Impl(imp) => {808 let Some(owner) = self.owner(mk, imp) else {809 let text = imp.self_ty.to_token_stream().to_string().replace(' ', "");810 for f in &imp.fns {811 let fallback = format!("{}::{}", join(mk, &text), f.name);812 functions.push(self.function(mk, &m.file, f, None, None, fallback));813 }814 continue;815 };816 for f in &imp.fns {817 let path = owner.path.as_ref().map(|p| match owner.cross {818 TypeCross::Hand { .. } => join(&owner.module, &f.name),819 _ => format!("{p}::{}", f.name),820 });821 let fallback = format!(822 "{}::{}",823 owner.path.clone().unwrap_or_else(|| join(mk, "?")),824 f.name825 );826 let mut entry =827 self.function(mk, &m.file, f, path, Some((&owner, imp)), fallback);828 if entry.cross != Cross::Private {829 entry.module = owner.module.clone();830 }831 functions.push(entry);832 }833 }834 }835 }836 }837 types.sort_by(|a, b| a.path.cmp(&b.path));838 consts.sort_by(|a, b| a.path.cmp(&b.path));839 functions.sort_by(|a, b| {840 (&a.path, &a.dims, &a.defined_at).cmp(&(&b.path, &b.dims, &b.defined_at))841 });842 let mut modules: Vec<Module> = self843 .tree844 .modules845 .iter()846 .filter_map(|(mk, m)| {847 let path = self.module_path(mk)?;848 let items = functions849 .iter()850 .filter(|f| f.cross != Cross::Private && f.module == path)851 .count()852 + types.iter().filter(|t| parent(&t.path) == path).count()853 + consts.iter().filter(|c| parent(&c.path) == path).count();854 Some(Module {855 path,856 file: m.file.clone(),857 docs: m.docs.clone(),858 items,859 })860 })861 .collect();862 modules.sort_by(|a, b| a.path.cmp(&b.path));863 modules.dedup_by(|a, b| a.path == b.path);864 Manifest {865 krate: "mrlyrs".into(),866 version: version.into(),867 modules,868 types,869 consts,870 functions,871 }872 }873874 fn default_of(&self, key: &Key, owner: &str, generic: bool) -> Option<Function> {875 let at = self.defaults.get(key)?;876 let path = owner.to_string();877 let ret = match self.cross.get(key)? {878 TypeCross::Class => Ty::Class { path, dim: None },879 TypeCross::Plain => Ty::Plain { path },880 TypeCross::Enum { .. } => Ty::Enum { path },881 TypeCross::Hand { .. } | TypeCross::Uncrossable { .. } => return None,882 };883 (!generic).then(|| default_fn(owner, at, ret))884 }885886 fn alias_default(&self, module: &str, alias: &parse::Alias, owner: &str, ty: &Ty) -> Option<Function> {887 if alias.generic || !matches!(ty, Ty::Plain { .. } | Ty::Class { .. } | Ty::Enum { .. }) {888 return None;889 }890 let (target, _) = self.type_key(module, &alias.ty)?;891 let at = self.defaults.get(&target)?;892 let generic = matches!(&self.module(&target.0).items[target.1], Item::Struct(s) if s.const_generic);893 generic.then(|| default_fn(owner, at, ty.clone()))894 }895896 fn fields(&self, scope: &Scope, fields: &[parse::Field]) -> Vec<Field> {897 fields898 .iter()899 .map(|f| Field {900 name: f.name.clone(),901 public: f.public,902 docs: f.docs.clone(),903 serde: f.serde.clone(),904 serde_skip: f.serde_skip,905 ty: self.ty(scope, &f.ty),906 })907 .collect()908 }909910 fn function(911 &self,912 module: &str,913 file: &str,914 f: &parse::Fn,915 path: Option<String>,916 owner: Option<(&Owner, &parse::Impl)>,917 fallback: String,918 ) -> Function {919 let mut generics: Vec<String> = f.type_params.iter().map(|(n, _)| n.clone()).collect();920 let mut dims = vec![];921 if let Some((o, imp)) = owner {922 generics.extend(imp.type_params.iter().cloned());923 match o.dim {924 Some(d) => dims.push(d),925 None if !imp.const_params.is_empty() => dims.extend([2, 3]),926 None => {}927 }928 }929 if dims.is_empty() && !f.const_params.is_empty() {930 dims.extend([2, 3]);931 }932 let scope = Scope {933 module,934 generics,935 self_ty: owner.map(|(o, _)| o.ty.clone()),936 };937 let params: Vec<Param> = f938 .params939 .iter()940 .map(|(name, ty)| Param {941 name: name.clone(),942 ty: self.ty(&scope, ty),943 })944 .collect();945 let ret = f.ret.as_ref().map_or(Ty::Unit, |t| self.ty(&scope, t));946 let private = path.is_none();947 let path = path.unwrap_or(fallback);948 let mut entry = Function {949 path: path.clone(),950 name: f.name.clone(),951 module: parent(&path),952 defined_at: format!("{file}:{}", f.line),953 docs: f.docs.clone(),954 owner: owner.and_then(|(o, _)| o.path.clone()),955 self_kind: f.self_kind,956 params,957 ret,958 dims,959 via: None,960 source: if f.generated {961 Source::NamedEnum962 } else {963 Source::Written964 },965 constant: f.constant,966 cross: Cross::Private,967 };968 if !private {969 entry.cross = self.classify(&entry, f);970 }971 entry972 }973974 fn classify(&self, entry: &Function, f: &parse::Fn) -> Cross {975 let skip = |reason: String| Cross::Skip { reason };976 if let Some((name, bounds)) = f.type_params.first() {977 if bounds.contains("Fn") || f.where_text.contains("Fn") {978 return skip(format!("closure parameter {name}"));979 }980 if entry.module == "core::error" {981 return skip("error constructor".into());982 }983 return skip(format!("generic over {name}"));984 }985 for p in &entry.params {986 if let Some(reason) = uncrossable(&p.ty) {987 return skip(format!("{}: {reason}", p.name));988 }989 if let Ty::Ref {990 mutable: true,991 item,992 ..993 } = &p.ty994 {995 if !matches!(**item, Ty::Hand { .. } | Ty::Class { .. }) {996 return skip(format!("{}: mutates plain data in place", p.name));997 }998 }999 if let Ty::Slice { mutable: true, .. } = &p.ty {1000 return skip(format!("{}: mutable slice argument", p.name));1001 }1002 }1003 if let Some(reason) = uncrossable(&entry.ret) {1004 return skip(format!("returns {reason}"));1005 }1006 if let Some(borrow) = borrow(&entry.ret) {1007 return skip(format!("{borrow} return"));1008 }1009 let unit = entry1010 .module1011 .split("::")1012 .next()1013 .unwrap_or_default()1014 .to_string();1015 let mut foreign = None;1016 let mut see = |t: &Ty| {1017 if let Ty::Class { path, .. } = t {1018 let home = path.split("::").next().unwrap_or_default();1019 if home != unit && foreign.is_none() {1020 foreign = Some(format!("class {path} lives in unit {home}"));1021 }1022 }1023 };1024 entry.params.iter().for_each(|p| p.ty.walk(&mut see));1025 entry.ret.walk(&mut see);1026 if let Some(reason) = foreign {1027 return skip(reason);1028 }1029 Cross::Ok1030 }1031}10321033fn default_fn(owner: &str, defined_at: &str, ret: Ty) -> Function {1034 let name = owner.rsplit("::").next().unwrap_or(owner);1035 Function {1036 path: format!("{owner}::default"),1037 name: "default".into(),1038 module: parent(owner),1039 defined_at: defined_at.to_string(),1040 docs: vec![format!("Returns the default {name}.")],1041 owner: Some(owner.to_string()),1042 self_kind: None,1043 params: vec![],1044 ret,1045 dims: vec![],1046 via: None,1047 source: Source::Default,1048 constant: false,1049 cross: Cross::Ok,1050 }1051}10521053fn borrow(ty: &Ty) -> Option<&'static str> {1054 if ty.any(&|t| matches!(t, Ty::Ref { lifetime: Some(l), .. } if l == "'static")) {1055 Some("&'static")1056 } else if ty.any(&|t| {1057 matches!(1058 t,1059 Ty::Ref {1060 lifetime: Some(_),1061 ..1062 }1063 )1064 }) {1065 Some("lifetime-bearing")1066 } else if ty.any(&|t| {1067 matches!(1068 t,1069 Ty::Ref { mutable: true, .. } | Ty::Slice { mutable: true, .. }1070 )1071 }) {1072 Some("mutable borrow")1073 } else {1074 None1075 }1076}10771078fn dim_of(args: &syn::PathArguments) -> Option<u8> {1079 let syn::PathArguments::AngleBracketed(a) = args else {1080 return None;1081 };1082 match a.args.first()? {1083 syn::GenericArgument::Const(syn::Expr::Lit(syn::ExprLit {1084 lit: syn::Lit::Int(n),1085 ..1086 })) => n.base10_parse().ok(),1087 _ => None,1088 }1089}10901091fn serde_both(derives: &[String]) -> bool {1092 derives.iter().any(|d| d == "Serialize") && derives.iter().any(|d| d == "Deserialize")1093}10941095pub fn uncrossable(ty: &Ty) -> Option<String> {1096 let mut reason = None;1097 ty.walk(&mut |t| {1098 if reason.is_some() {1099 return;1100 }1101 reason = match t {1102 Ty::Unknown { text } => Some(text.clone()),1103 Ty::Opaque { path } => Some(format!("uncrossable type {path}")),1104 Ty::Unit => None,1105 Ty::Map { key, .. } if !matches!(**key, Ty::Scalar { .. } | Ty::String | Ty::Str) => {1106 Some("map keyed by a non-scalar".into())1107 }1108 _ => None,1109 };1110 if let Ty::Unknown { text } = t {1111 if text.contains("Iterator") {1112 reason = Some("iterator return".into());1113 }1114 }1115 });1116 reason1117}11181119fn collisions(m: &Manifest) -> Vec<String> {1120 let mut out = vec![];1121 let mut free: BTreeMap<(String, String), Vec<&Function>> = BTreeMap::new();1122 for f in m.functions.iter().filter(|f| f.cross != Cross::Private) {1123 free.entry((f.module.clone(), f.path.clone()))1124 .or_default()1125 .push(f);1126 }1127 for ((_, path), entries) in free {1128 if entries.len() < 2 {1129 continue;1130 }1131 let dims: Vec<&u8> = entries.iter().flat_map(|f| f.dims.iter()).collect();1132 let distinct: HashSet<&u8> = dims.iter().copied().collect();1133 if !dims.is_empty()1134 && dims.len() == distinct.len()1135 && entries.iter().all(|f| !f.dims.is_empty())1136 {1137 continue;1138 }1139 let at: Vec<&str> = entries.iter().map(|f| f.defined_at.as_str()).collect();1140 out.push(format!("collision: {path} at {}", at.join(" and ")));1141 }1142 let homes: HashSet<&str> = m1143 .modules1144 .iter()1145 .filter(|x| x.items > 0)1146 .map(|x| x.path.as_str())1147 .collect();1148 for f in m.functions.iter().filter(|f| f.cross != Cross::Private) {1149 if homes.contains(f.path.as_str()) {1150 out.push(format!(1151 "collision: module {} and fn {} at {}",1152 f.path, f.path, f.defined_at1153 ));1154 }1155 }1156 for t in &m.types {1157 if homes.contains(t.path.as_str()) {1158 out.push(format!(1159 "collision: module {} and type {} at {}",1160 t.path, t.path, t.defined_at1161 ));1162 }1163 }1164 out.sort();1165 out.dedup();1166 out1167}