Skip to content

Commit edda6f8

Browse files
committed
Fix namespace context for resolve_function_name
1 parent 596a795 commit edda6f8

36 files changed

Lines changed: 190 additions & 156 deletions

File tree

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6464
- **Blade files with raw `<?php ... ?>` tags no longer report false syntax errors.** PHP code embedded directly in a Blade template (outside `@php`/`@endphp`) is now recognized and passed through unmodified, so string literals that happen to start with `@` (e.g. a JSON-LD `'@context'` array key) are no longer misread as Blade directives.
6565
- **`@switch`/`@case` with a class-constant case value no longer reports a syntax error.** `@case (Some\Namespaced\Enum::VALUE)` now translates to a valid `case` arm instead of silently corrupting the rest of the file.
6666
- **Generated `@return` types infer the same rich type from multi-line array literals.** A function returning an array literal written across several lines now infers the precise type (for example `list<string>`) in its generated docblock, matching the single-line form. Previously breaking the literal onto multiple lines degraded the inferred type to `array<mixed>`.
67+
- **Calls to functions declared in another `namespace` block of the same file resolve their return type.** In a file that declares more than one `namespace`, a call to a function from a later block used to leave the returned value untyped unless the function carried an `@return` docblock. The call and its return type now resolve regardless, so completion, hover, and diagnostics see the value's type.
6768

6869
## [0.9.0] - 2026-07-20
6970

docs/todo.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,6 @@ unlikely to move the needle for most users.
105105
| D5 | [External tool diagnostic suppression actions](todo/diagnostics.md#d5-external-tool-diagnostic-suppression-actions) | Low | Low |
106106
| D15 | [Unused parameter diagnostic](todo/diagnostics.md#d15-unused-parameter-diagnostic) | Low | Low |
107107
| | **[Bug Fixes](todo/bugs.md)** | | |
108-
| B1 | [`resolve_function_name` guesses a single namespace, missing same-file multi-namespace declarations](todo/bugs.md#b1-resolve_function_name-guesses-a-single-namespace-missing-same-file-multi-namespace-declarations) | Low-Medium | Medium |
109108
| | **[Code Actions](todo/actions.md)** | | |
110109
| A40 | [Generate method from call](todo/actions.md#a40-generate-method-from-call) | Medium-High | Medium |
111110
| A41 | [Create class from non-existing name](todo/actions.md#a41-create-class-from-non-existing-name) | Medium | Medium |

docs/todo/bugs.md

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,36 +7,4 @@ pipeline so it produces correct data. Downstream consumers
77
(diagnostics, hover, completion, definition) should never need
88
to second-guess upstream output.
99

10-
## B1. `resolve_function_name` guesses a single namespace, missing same-file multi-namespace declarations
11-
12-
`resolve_function_name` (`src/resolution.rs`) builds its candidate
13-
list from a single `file_namespace` — the *call site's* namespace
14-
block (`FileContext::namespace` / `namespace_at_offset`). In a file
15-
that declares multiple `namespace` blocks, a function declared under
16-
a namespace other than the call site's does not match any candidate,
17-
even though it is already sitting in `global_functions` under its own
18-
FQN.
19-
20-
`FileContext::resolve_name_at` (`src/types/mod.rs`) already solves
21-
this correctly for other identifiers by consulting `resolved_names`
22-
(mago-names' per-offset resolution, which understands multiple
23-
namespace blocks in one file). `resolve_function_name` should try
24-
`ctx.resolve_name_at(name, offset)` as a candidate before falling
25-
back to the single-namespace guess.
26-
27-
This is why `src/completion/source/helpers.rs::
28-
extract_function_return_from_source` still exists: it is a same-file
29-
backward-text scanner for a function's `@return` docblock, used as a
30-
fallback in `rhs_resolution.rs` (guarded by `!loader_found`) exactly
31-
when `resolve_function_name` fails on this multi-namespace case. Once
32-
`resolve_function_name` is offset-aware, this text scanner becomes
33-
dead code and should be deleted along with its call site.
34-
35-
**Scope note:** `function_loader`/`function_loader_with`
36-
(`src/resolution.rs`) are `Fn(&str) -> Option<FunctionInfo>` closures
37-
bound once per `FileContext`, with no offset parameter, across ~30
38-
call sites (completion, hover, definition, references, code actions).
39-
Fixing this requires either threading the call expression's byte
40-
offset through to `resolve_function_name` or adding an offset-aware
41-
variant of the loader closure. Size the fix accordingly — this is not
42-
a one-line change.
10+
No outstanding items.

src/blade/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,8 @@ impl crate::Backend {
195195
fn blade_e_hover(&self, start: Position, len: u32) -> Hover {
196196
// Try to resolve the actual `e()` function from the project/stubs.
197197
let empty_use_map = std::collections::HashMap::new();
198-
let loader = self.function_loader_with(&empty_use_map, &None);
199-
let content = if let Some(func) = loader("e") {
198+
let loader = self.function_loader_with(None, &empty_use_map, &None);
199+
let content = if let Some(func) = loader("e", 0) {
200200
crate::hover::hover_for_function(&func, None, None, false).contents
201201
} else {
202202
HoverContents::Markup(MarkupContent {

src/code_actions/extract_function/scope.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub(crate) fn resolve_var_type(
3030
let constant_loader = backend.constant_loader();
3131
let loaders = Loaders {
3232
function_loader: Some(
33-
&function_loader as &dyn Fn(&str) -> Option<crate::types::FunctionInfo>,
33+
&function_loader as &dyn Fn(&str, u32) -> Option<crate::types::FunctionInfo>,
3434
),
3535
constant_loader: Some(&constant_loader),
3636
};

src/code_actions/phpstan/fix_return_type/inference.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ impl Backend {
6969
let file_use_map: HashMap<String, String> = self.file_use_map(uri);
7070
let file_namespace: Option<String> = self.first_file_namespace(uri);
7171
let class_loader = self.class_loader_with(&local_classes, &file_use_map, &file_namespace);
72-
let function_loader = self.function_loader_with(&file_use_map, &file_namespace);
72+
let function_loader = self.function_loader_with(None, &file_use_map, &file_namespace);
7373

7474
infer_return_type(
7575
content,

src/code_actions/replace_deprecated.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -501,7 +501,7 @@ fn resolve_subject_to_class(
501501
backend: &Backend,
502502
) -> Option<ClassInfo> {
503503
let class_loader = backend.class_loader_with(ctx.local_classes, ctx.use_map, ctx.namespace);
504-
let function_loader = backend.function_loader_with(ctx.use_map, ctx.namespace);
504+
let function_loader = backend.function_loader_with(None, ctx.use_map, ctx.namespace);
505505
let res_ctx = crate::subject_resolution::SubjectResolutionCtx {
506506
local_classes: ctx.local_classes,
507507
use_map: ctx.use_map,

src/completion/call_resolution/return_types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,7 @@ impl Backend {
615615

616616
// Regular function lookup.
617617
if let Some(fl) = ctx.function_loader
618-
&& let Some(func_info) = fl(func_name)
618+
&& let Some(func_info) = fl(func_name, 0)
619619
{
620620
if let Some(ref cond) = func_info.conditional_return {
621621
let var_resolver = build_var_resolver(ctx);

src/completion/handler/phpdoc.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ impl Backend {
4545
position,
4646
&ctx.classes,
4747
&class_loader,
48-
Some(&function_loader as &dyn Fn(&str) -> Option<crate::types::FunctionInfo>),
48+
Some(
49+
&function_loader
50+
as &dyn Fn(&str, u32) -> Option<crate::types::FunctionInfo>,
51+
),
4952
)
5053
} else {
5154
None

src/completion/phpdoc/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ pub type ClassLoaderFn<'a> = &'a dyn Fn(&str) -> Option<Arc<ClassInfo>>;
5555
///
5656
/// Used by [`SmartContext`] for cross-file `@throws` propagation from
5757
/// standalone function calls.
58-
pub type FunctionLoaderFn<'a> = &'a dyn Fn(&str) -> Option<crate::types::FunctionInfo>;
58+
pub type FunctionLoaderFn<'a> = &'a dyn Fn(&str, u32) -> Option<crate::types::FunctionInfo>;
5959

6060
// Re-export comment-position helpers so existing consumers (tests,
6161
// handler, catch_completion) that import from `phpdoc::` keep working.

0 commit comments

Comments
 (0)