mago_analyzer/settings.rs
1use mago_algebra::AlgebraThresholds;
2use mago_atom::AtomSet;
3use mago_codex::ttype::combiner::CombinerOptions;
4use mago_php_version::PHPVersion;
5
6/// Default maximum logical formula size during conditional analysis.
7pub const DEFAULT_FORMULA_SIZE_THRESHOLD: u16 = 512;
8
9/// Configuration settings that control the behavior of the Mago analyzer.
10///
11/// This struct allows you to enable/disable specific checks, suppress categories of issues,
12/// and tune the analyzer's performance and strictness.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct Settings {
15 /// The target PHP version for the analysis.
16 pub version: PHPVersion,
17
18 /// Find and report expressions whose results are not used (e.g., `$a + $b;`). Defaults to `false`.
19 pub find_unused_expressions: bool,
20
21 /// Find and report unused definitions (e.g., private methods that are never called). Defaults to `false`.
22 pub find_unused_definitions: bool,
23
24 /// Analyze code that appears to be unreachable. Defaults to `false`.
25 pub analyze_dead_code: bool,
26
27 /// Track the literal values of class properties when they are assigned.
28 /// This improves type inference but may increase memory usage. Defaults to `true`.
29 pub memoize_properties: bool,
30
31 /// Allow accessing array keys that may not be defined without reporting an issue. Defaults to `true`.
32 pub allow_possibly_undefined_array_keys: bool,
33
34 /// Enable checking for unhandled thrown exceptions.
35 ///
36 /// When `true`, the analyzer will report any exception that is thrown but not caught
37 /// in a `try-catch` block or documented in a `@throws` tag.
38 ///
39 /// This check is disabled by default (`false`) as it can be computationally expensive.
40 pub check_throws: bool,
41
42 /// Exceptions to ignore including all subclasses (hierarchy-aware).
43 ///
44 /// When an exception class is in this set, any exception of that class or any of its
45 /// subclasses will be ignored during `check_throws` analysis.
46 ///
47 /// For example, adding `LogicException` will ignore `LogicException`, `InvalidArgumentException`,
48 /// `OutOfBoundsException`, and all other subclasses.
49 pub unchecked_exceptions: AtomSet,
50
51 /// Exceptions to ignore (exact class match only, not subclasses).
52 ///
53 /// When an exception class is in this set, only that exact class will be ignored
54 /// during `check_throws` analysis. Parent classes and subclasses are not affected.
55 pub unchecked_exception_classes: AtomSet,
56
57 /// Check for missing `#[Override]` attributes on overriding methods.
58 ///
59 /// When enabled, the analyzer reports methods that override a parent method without
60 /// the `#[Override]` attribute (PHP 8.3+).
61 ///
62 /// Defaults to `true`.
63 pub check_missing_override: bool,
64
65 /// Find and report unused function/method parameters.
66 ///
67 /// When enabled, the analyzer reports parameters that are declared but never used
68 /// within the function body.
69 ///
70 /// Defaults to `true`.
71 pub find_unused_parameters: bool,
72
73 /// Enforce strict checks when accessing list elements by index.
74 ///
75 /// When `true`, the analyzer requires that any integer used to access a `list`
76 /// element is provably non-negative (e.g., of type `int<0, max>`). This helps
77 /// prevent potential runtime errors from using a negative index.
78 ///
79 /// When `false` (the default), any `int` is permitted as an index, offering
80 /// more flexibility at the cost of type safety.
81 pub strict_list_index_checks: bool,
82
83 /// Disable comparisons to boolean literals (`true`/`false`).
84 ///
85 /// When enabled, comparisons to boolean literals will not be reported as issues.
86 ///
87 /// Defaults to `false`.
88 pub no_boolean_literal_comparison: bool,
89
90 /// Check for missing type hints on parameters, properties, and return types.
91 ///
92 /// When enabled, the analyzer will report warnings for function parameters, class properties,
93 /// and function return types that lack explicit type declarations. The analyzer uses its
94 /// type system knowledge to avoid false positives - for instance, it won't require a type hint
95 /// on a property if adding one would conflict with a parent class or trait that has no type hint.
96 ///
97 /// Defaults to `false`.
98 pub check_missing_type_hints: bool,
99
100 /// Check for missing type hints (both parameters and return types) in closures when `check_missing_type_hints` is enabled.
101 ///
102 /// When `true`, closures (anonymous functions declared with `function() {}`) will be
103 /// checked for missing type hints. When `false`, closures are ignored, which is useful
104 /// because closures often rely on type inference.
105 ///
106 /// Defaults to `false`.
107 pub check_closure_missing_type_hints: bool,
108
109 /// Check for missing type hints (both parameters and return types) in arrow functions when `check_missing_type_hints` is enabled.
110 ///
111 /// When `true`, arrow functions (declared with `fn() => ...`) will be checked for missing
112 /// type hints. When `false`, arrow functions are ignored, which is useful because arrow
113 /// functions often rely on type inference and are typically short, making types obvious.
114 ///
115 /// Defaults to `false`.
116 pub check_arrow_function_missing_type_hints: bool,
117
118 /// Register superglobals (e.g., `$_GET`, `$_POST`, `$_SERVER`) in the analysis context.
119 ///
120 /// If disabled, super globals won't be available unless explicitly imported using
121 /// the `global` keyword.
122 ///
123 /// Defaults to `true`.
124 pub register_super_globals: bool,
125
126 /// Enable colored output in terminal environments that support it. Defaults to `true`.
127 ///
128 /// This setting is primarily used for enabling/disabling colored diffs in
129 /// issue reports.
130 pub use_colors: bool,
131
132 /// **Internal use only.**
133 ///
134 /// Enables a diffing mode for incremental analysis, used by integrations like LSPs.
135 /// This avoids re-analyzing unchanged code in the same session. Defaults to `false`.
136 pub diff: bool,
137
138 /// Trust symbol existence checks to narrow types.
139 ///
140 /// When enabled, conditional checks like `method_exists()`, `property_exists()`,
141 /// `function_exists()`, and `defined()` will narrow the type within the conditional block,
142 /// suppressing errors for symbols that are verified to exist at runtime.
143 ///
144 /// When disabled, these checks are ignored and the analyzer requires explicit type hints,
145 /// which is stricter but may produce more false positives for dynamic code.
146 ///
147 /// Defaults to `true`.
148 pub trust_existence_checks: bool,
149
150 /// Method names treated as class initializers (like `__construct`).
151 ///
152 /// Properties initialized in these methods count as "definitely initialized"
153 /// just like in the constructor. This is useful for frameworks that use
154 /// lifecycle methods like `PHPUnit`'s `setUp()` or framework `boot()` methods.
155 ///
156 /// Example: `["setUp", "initialize", "boot"]`
157 ///
158 /// Defaults to empty (no additional initializers).
159 pub class_initializers: AtomSet,
160
161 /// Enable property initialization checking (`missing-constructor`, `uninitialized-property`).
162 ///
163 /// When `false`, disables both `missing-constructor` and `uninitialized-property` issues
164 /// entirely. This is useful for projects that prefer to rely on runtime errors for
165 /// property initialization.
166 ///
167 /// Defaults to `false`.
168 pub check_property_initialization: bool,
169
170 /// Check for non-existent symbols in use statements.
171 ///
172 /// When enabled, the analyzer will report use statements that import symbols
173 /// (classes, interfaces, traits, enums, functions, or constants) that do not exist
174 /// in the codebase.
175 ///
176 /// Defaults to `false`.
177 pub check_use_statements: bool,
178
179 // Performance tuning thresholds
180 // Higher values allow deeper analysis at the cost of performance.
181 // Lower values improve speed but may reduce precision on complex code.
182 /// Maximum number of clauses to process during CNF saturation.
183 ///
184 /// Controls how many clauses the simplification algorithm will work with.
185 /// If exceeded, saturation returns an empty result to avoid performance issues.
186 ///
187 /// Defaults to `8192`.
188 pub saturation_complexity_threshold: u16,
189
190 /// Maximum number of clauses per side in disjunction operations.
191 ///
192 /// Controls the complexity limit for OR operations between clause sets.
193 /// If either side exceeds this, the disjunction returns an empty result.
194 ///
195 /// Defaults to `4096`.
196 pub disjunction_complexity_threshold: u16,
197
198 /// Maximum cumulative complexity during formula negation.
199 ///
200 /// Controls how complex the negation of a formula can become.
201 /// If exceeded, negation gives up to avoid exponential blowup.
202 ///
203 /// Defaults to `4096`.
204 pub negation_complexity_threshold: u16,
205
206 /// Upper limit for consensus optimization during saturation.
207 ///
208 /// Controls when the consensus rule is applied during saturation.
209 /// Only applies when clause count is between 3 and this limit.
210 ///
211 /// Defaults to `256`.
212 pub consensus_limit_threshold: u16,
213
214 /// Maximum logical formula size during conditional analysis.
215 ///
216 /// Limits the size of generated formulas to prevent exponential blowup
217 /// in deeply nested conditionals.
218 ///
219 /// Defaults to `512`.
220 pub formula_size_threshold: u16,
221
222 /// Maximum number of literal strings to track before generalizing.
223 ///
224 /// When combining types with many different literal string values, tracking each
225 /// literal individually causes O(n) memory and O(n²) comparison time.
226 /// Once the threshold is exceeded, we generalize to the base string type.
227 ///
228 /// Defaults to `128`.
229 pub string_combination_threshold: u16,
230
231 /// Maximum number of literal integers to track before generalizing.
232 ///
233 /// When combining types with many different literal integer values, tracking each
234 /// literal individually causes O(n) memory and O(n²) comparison time.
235 /// Once the threshold is exceeded, we generalize to the base int type.
236 ///
237 /// Defaults to `128`.
238 pub integer_combination_threshold: u16,
239
240 /// Maximum number of array elements to track individually.
241 ///
242 /// When building array types through repeated push operations (`$arr[] = ...`),
243 /// this limits how many individual elements are tracked before generalizing
244 /// to a simpler array type. This prevents memory explosion on files with
245 /// thousands of array pushes.
246 ///
247 /// Defaults to `128`.
248 pub array_combination_threshold: u16,
249}
250
251impl Default for Settings {
252 fn default() -> Self {
253 Self::new(PHPVersion::LATEST)
254 }
255}
256
257impl Settings {
258 #[must_use]
259 pub fn new(version: PHPVersion) -> Self {
260 let default_thresholds = AlgebraThresholds::default();
261 let default_combiner_options = CombinerOptions::default();
262
263 Self {
264 version,
265 find_unused_expressions: true,
266 find_unused_definitions: true,
267 analyze_dead_code: false,
268 memoize_properties: true,
269 allow_possibly_undefined_array_keys: true,
270 check_throws: false,
271 unchecked_exceptions: AtomSet::default(),
272 unchecked_exception_classes: AtomSet::default(),
273 use_colors: true,
274 check_missing_override: false,
275 find_unused_parameters: false,
276 strict_list_index_checks: false,
277 no_boolean_literal_comparison: false,
278 check_missing_type_hints: false,
279 check_closure_missing_type_hints: false,
280 check_arrow_function_missing_type_hints: false,
281 register_super_globals: true,
282 diff: false,
283 trust_existence_checks: true,
284 class_initializers: AtomSet::default(),
285 check_property_initialization: false,
286 check_use_statements: false,
287 saturation_complexity_threshold: default_thresholds.saturation_complexity,
288 disjunction_complexity_threshold: default_thresholds.disjunction_complexity,
289 negation_complexity_threshold: default_thresholds.negation_complexity,
290 consensus_limit_threshold: default_thresholds.consensus_limit,
291 formula_size_threshold: DEFAULT_FORMULA_SIZE_THRESHOLD,
292 string_combination_threshold: default_combiner_options.string_combination_threshold,
293 integer_combination_threshold: default_combiner_options.integer_combination_threshold,
294 array_combination_threshold: default_combiner_options.array_combination_threshold,
295 }
296 }
297
298 /// Returns the algebra thresholds derived from the settings.
299 #[must_use]
300 pub fn algebra_thresholds(&self) -> AlgebraThresholds {
301 AlgebraThresholds {
302 saturation_complexity: self.saturation_complexity_threshold,
303 disjunction_complexity: self.disjunction_complexity_threshold,
304 negation_complexity: self.negation_complexity_threshold,
305 consensus_limit: self.consensus_limit_threshold,
306 }
307 }
308
309 /// Returns the combiner options derived from the settings.
310 #[must_use]
311 pub fn combiner_options(&self) -> CombinerOptions {
312 CombinerOptions {
313 overwrite_empty_array: false,
314 array_combination_threshold: self.array_combination_threshold,
315 string_combination_threshold: self.string_combination_threshold,
316 integer_combination_threshold: self.integer_combination_threshold,
317 }
318 }
319}