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}