MCPcopy Create free account
hub / github.com/PHPantom-dev/phpantom_lsp / check_scope

Function check_scope

src/diagnostics/undefined_variables.rs:324–490  ·  view source on GitHub ↗

Check a single scope (function/method body) for undefined variable reads. For each variable read, we check whether the variable has been written (assigned, declared as a parameter, etc.) at a **lower byte offset** in the same frame. Writes inside control-flow branches (if/else, switch, try/catch) still count — we are conservative about branches but strict about source order. This catches the co

(
    scope: &ScopeMap,
    statements: &[Statement<'_>],
    ctx: &mut DiagnosticCtx<'_>,
    this_is_defined: bool,
)

Source from the content-addressed store, hash-verified

322/// common "used before assigned" typo while still avoiding false
323/// positives from branch-dependent definitions.
324fn check_scope(
325 scope: &ScopeMap,
326 statements: &[Statement<'_>],
327 ctx: &mut DiagnosticCtx<'_>,
328 this_is_defined: bool,
329) {
330 // Bail out early if the function uses features that make static
331 // analysis unsound.
332 if has_dynamic_variables(statements) || has_extract_call(statements) {
333 return;
334 }
335
336 // Collect variable names referenced by compact() calls — these
337 // variables are used by string name and should be treated as
338 // defined.
339 let compact_vars = collect_compact_vars(statements);
340
341 // Collect variable names annotated with `/** @var Type $var */`
342 // inline docblocks.
343 let var_annotated = collect_var_annotations(ctx.content);
344
345 // Collect byte offsets suppressed by the `@` error control
346 // operator (e.g. `@$var`).
347 let error_suppressed_offsets = collect_error_suppressed_offsets(statements);
348
349 // Collect byte offsets of variables inside `isset()` and `empty()`.
350 let guarded_offsets = collect_guarded_offsets(statements);
351
352 // Bail out if there are no frames at all.
353 if scope.frames.is_empty() {
354 return;
355 }
356
357 // Build a set of "always-defined" names that do not require a
358 // prior write: superglobals, compact-referenced vars, @var
359 // annotations, and optionally $this.
360 let mut always_defined: HashSet<&str> = HashSet::new();
361 for sg in SUPERGLOBALS {
362 always_defined.insert(sg);
363 }
364 if this_is_defined {
365 always_defined.insert("$this");
366 }
367 for cv in &compact_vars {
368 always_defined.insert(cv.as_str());
369 }
370 for av in &var_annotated {
371 always_defined.insert(av.as_str());
372 }
373
374 // Pre-compute the "own writes" for each frame: writes that are
375 // directly inside the frame (not inside a nested sub-frame).
376 let frame_own_writes: Vec<Vec<(&str, u32)>> = scope
377 .frames
378 .iter()
379 .map(|frame| {
380 let mut writes: Vec<(&str, u32)> = Vec::new();
381 // Parameters (offset 0 = always before any read).

Callers 2

collect_from_statementFunction · 0.70

Calls 15

has_dynamic_variablesFunction · 0.85
has_extract_callFunction · 0.85
collect_compact_varsFunction · 0.85
collect_var_annotationsFunction · 0.85
collect_guarded_offsetsFunction · 0.85
build_visible_writesFunction · 0.85
as_strMethod · 0.80
iterMethod · 0.80
pushMethod · 0.80
containsMethod · 0.80

Tested by

no test coverage detected