Skip to main content

backhopper_core/model/
symbol.rs

1// Copyright (C) 2026 Michael S. Klishin and Contributors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// See LICENSE-APACHE and LICENSE-MIT for details.
4
5use std::path::PathBuf;
6
7use serde::{Deserialize, Serialize};
8
9use crate::model::names::{Arity, FunctionName, Mfa, ModuleName, RecordName, TypeName};
10
11/// Where in the source a reference was extracted from. The same
12/// textual `mod:ident(...)` shape means a function call in body
13/// context and a type reference in type-attribute context, so the
14/// extractor must carry the surrounding context with each symbol it
15/// emits.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub enum RefContext {
18    /// Inside a function body, top-level expression, or anywhere that
19    /// is not a `-spec`, `-callback`, `-type`, or `-opaque` attribute.
20    Body,
21    /// Inside a `-spec`, `-callback`, `-type`, or `-opaque` attribute,
22    /// where `mod:ident(...)` is a type reference, not a function call.
23    TypeAttribute,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
27#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
28#[serde(tag = "kind", rename_all = "snake_case")]
29pub enum SymbolKind {
30    Behaviour {
31        module: ModuleName,
32    },
33    Callback {
34        module: ModuleName,
35        function: FunctionName,
36        arity: Arity,
37    },
38    Function {
39        mfa: Mfa,
40    },
41    /// A function reference whose arity could not be determined from
42    /// the source line (the call's closing paren sits on a later
43    /// line). Resolves against a snapshot when *any* export carries
44    /// the name; never produces an arity mismatch.
45    FunctionAnyArity {
46        module: ModuleName,
47        function: FunctionName,
48    },
49    Macro {
50        name: String,
51    },
52    Record {
53        name: RecordName,
54    },
55    Type {
56        module: ModuleName,
57        name: TypeName,
58        arity: Arity,
59    },
60}
61
62/// Which side of the hunk a reference was extracted from. `Added`
63/// lines are requirements the patch introduces; `Context` lines are
64/// pre-existing facts about the target and never drive verdicts.
65/// `Added` orders first so a kind-level dedup keeps the stronger
66/// origin.
67#[derive(
68    Debug, Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize,
69)]
70#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
71#[serde(rename_all = "snake_case")]
72pub enum RefOrigin {
73    #[default]
74    Added,
75    Context,
76}
77
78impl RefOrigin {
79    // `&self` is required by serde's `skip_serializing_if` predicate shape.
80    #[allow(clippy::trivially_copy_pass_by_ref)]
81    #[must_use]
82    pub fn is_added(&self) -> bool {
83        matches!(self, Self::Added)
84    }
85}
86
87#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
88#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
89pub struct SymbolRef {
90    #[serde(flatten)]
91    pub kind: SymbolKind,
92    #[serde(default, skip_serializing_if = "RefOrigin::is_added")]
93    pub origin: RefOrigin,
94}
95
96impl SymbolRef {
97    pub fn function(mfa: Mfa) -> Self {
98        Self {
99            kind: SymbolKind::Function { mfa },
100            origin: RefOrigin::Added,
101        }
102    }
103
104    pub fn function_any_arity(module: ModuleName, function: FunctionName) -> Self {
105        Self {
106            kind: SymbolKind::FunctionAnyArity { module, function },
107            origin: RefOrigin::Added,
108        }
109    }
110
111    pub fn record(name: RecordName) -> Self {
112        Self {
113            kind: SymbolKind::Record { name },
114            origin: RefOrigin::Added,
115        }
116    }
117
118    pub fn macro_use(name: impl Into<String>) -> Self {
119        Self {
120            kind: SymbolKind::Macro { name: name.into() },
121            origin: RefOrigin::Added,
122        }
123    }
124
125    pub fn type_ref(module: ModuleName, name: TypeName, arity: Arity) -> Self {
126        Self {
127            kind: SymbolKind::Type {
128                module,
129                name,
130                arity,
131            },
132            origin: RefOrigin::Added,
133        }
134    }
135
136    #[must_use]
137    pub fn with_origin(mut self, origin: RefOrigin) -> Self {
138        self.origin = origin;
139        self
140    }
141}
142
143#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
144#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
145pub struct Reference {
146    pub symbol: SymbolRef,
147    pub from_path: PathBuf,
148    pub line: usize,
149}