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}