Skip to main content

grip/
method_purity_registry.rs

1// Copyright 2026 Umberto Gotti <umberto.gotti@umbertogotti.dev>
2// Licensed under the MIT License
3// SPDX-License-Identifier: MIT
4
5use std::collections::{HashMap, HashSet};
6use std::path::PathBuf;
7
8use syn::visit::Visit;
9use syn::{ImplItem, ImplItemFn, Item, ItemImpl, parse_file};
10
11use crate::function_purity::FunctionPurity;
12use crate::hidden_dep_finder::HiddenDepFinder;
13use crate::struct_registry::{StructRegistry, self_ty_name};
14
15#[derive(Debug, Default)]
16pub struct MethodPurityRegistry {
17    pure_methods: HashSet<(String, String)>,
18}
19
20impl MethodPurityRegistry {
21    #[must_use]
22    pub fn build(files: &[(PathBuf, String)], struct_registry: &StructRegistry) -> Self {
23        let mut registry = Self::default();
24        let no_nested_trust = Self::default();
25        for (_, source) in files {
26            if let Ok(syntax) = parse_file(source) {
27                for item in &syntax.items {
28                    registry.visit_top_level_item(item, struct_registry, &no_nested_trust);
29                }
30            }
31        }
32        registry
33    }
34
35    #[must_use]
36    pub fn is_known_pure_method(&self, type_name: &str, method: &str) -> bool {
37        self.pure_methods
38            .contains(&(type_name.to_string(), method.to_string()))
39    }
40
41    fn visit_top_level_item(
42        &mut self,
43        item: &Item,
44        struct_registry: &StructRegistry,
45        no_nested_trust: &MethodPurityRegistry,
46    ) {
47        match item {
48            Item::Impl(item_impl) => {
49                self.record_impl(item_impl, struct_registry, no_nested_trust);
50            }
51            Item::Mod(item_mod) => {
52                if let Some((_, items)) = &item_mod.content {
53                    for inner in items {
54                        self.visit_top_level_item(inner, struct_registry, no_nested_trust);
55                    }
56                }
57            }
58            _ => {}
59        }
60    }
61
62    fn record_impl(
63        &mut self,
64        item_impl: &ItemImpl,
65        struct_registry: &StructRegistry,
66        no_nested_trust: &MethodPurityRegistry,
67    ) {
68        if item_impl.trait_.is_some() {
69            return;
70        }
71        let type_name = self_ty_name(&item_impl.self_ty);
72        let concrete_fields = struct_registry.concrete_fields_of(&type_name);
73        for item in &item_impl.items {
74            if let ImplItem::Fn(method) = item {
75                self.record_method(
76                    &type_name,
77                    method,
78                    struct_registry,
79                    no_nested_trust,
80                    &concrete_fields,
81                );
82            }
83        }
84    }
85
86    fn record_method(
87        &mut self,
88        type_name: &str,
89        method: &ImplItemFn,
90        struct_registry: &StructRegistry,
91        no_nested_trust: &MethodPurityRegistry,
92        concrete_fields: &HashMap<String, String>,
93    ) {
94        if !Self::has_pure_signature(method) {
95            return;
96        }
97        let mut finder = HiddenDepFinder::new(struct_registry, no_nested_trust);
98        finder.set_concrete_fields(concrete_fields.clone());
99        finder.visit_block(&method.block);
100        if finder.count == 0 {
101            self.pure_methods
102                .insert((type_name.to_string(), method.sig.ident.to_string()));
103        }
104    }
105
106    fn has_pure_signature(method: &ImplItemFn) -> bool {
107        !FunctionPurity::has_mut_param(&method.sig)
108            && !FunctionPurity::is_unit_return(&method.sig)
109            && method.sig.unsafety.is_none()
110            && !FunctionPurity::has_unsafe_block(&method.block)
111            && !FunctionPurity::has_io_call(&method.block)
112    }
113}