Skip to content

Commit b89d74d

Browse files
committed
Parameter name inlay hints no longer shift to the wrong parameter when
only part of a multi-line call is visible
1 parent 1f102e3 commit b89d74d

3 files changed

Lines changed: 61 additions & 5 deletions

File tree

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5050

5151
### Fixed
5252

53+
- **Parameter name inlay hints no longer shift to the wrong parameter when only part of a multi-line call is visible.** Editors request inlay hints only for the currently visible viewport, and when a call's arguments were split across the viewport boundary, every hint after the first excluded argument was labelled with the previous parameter's name instead of its own. This was most noticeable on multi-line constructor calls with several arguments, such as those using constructor property promotion.
5354
- **Linked editing no longer types into unrelated parts of the file.** Adding a line above a variable (for example inserting a `/** */` docblock) and then typing could insert the typed characters at two arbitrary places further down, such as in the middle of a method name and in front of a trailing comment. PHPantom is re-reading the file in the background while you type, and linked editing was handing the editor positions measured against the previous version of the file, which the editor then edited on trust. Linked editing now verifies that the positions it reports still point at the variable, and offers nothing at all if they do not, so a keystroke can never land somewhere unexpected. It also becomes available in Blade templates, where the reported positions are checked against the template itself rather than the generated PHP.
5455
- **Diagnostic and request workers no longer copy the embedded stub indexes.** Every diagnostic pass and every hover, completion, or go-to-definition request cloned the full embedded stub class, function, and constant indexes (thousands of entries each) instead of sharing them. During workspace analysis this happened twice per file, and the resulting allocation churn serialised the parallel diagnostic workers in the memory allocator. The indexes are now shared, roughly halving `analyze` wall time on large Laravel projects and removing the same overhead from every editor request.
5556
- **Member resolution no longer degrades on deeply nested class dependencies.** Class resolution previously cut off at internal nesting limits, silently returning incomplete member sets (missing completions, spurious unknown-member diagnostics) on projects with deeply intertwined hierarchies and virtual members. Those limits are gone: only genuine dependency cycles, such as two Eloquent models whose relationships reference each other, fall back to a partial view, and they now do so deterministically instead of depending on which class happened to resolve first on a given thread.

src/inlay_hints.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -462,11 +462,6 @@ impl Backend {
462462
let mut positional_counter: usize = 0;
463463

464464
for (arg_idx, &arg_offset) in call_site.arg_offsets.iter().enumerate() {
465-
// Skip arguments outside the viewport range.
466-
if arg_offset < range_start || arg_offset > range_end {
467-
continue;
468-
}
469-
470465
// Skip named arguments — the parameter name is already visible.
471466
if call_site.named_arg_indices.contains(&(arg_idx as u32)) {
472467
continue;
@@ -498,6 +493,14 @@ impl Backend {
498493

499494
positional_counter += 1;
500495

496+
// Skip rendering arguments outside the viewport range, but only
497+
// after the positional counter above has been advanced — the
498+
// counter must track every argument regardless of visibility so
499+
// that arguments rendered later still map to the right parameter.
500+
if arg_offset < range_start || arg_offset > range_end {
501+
continue;
502+
}
503+
501504
let param = &params[param_idx];
502505

503506
// Build the hint label parts.

tests/integration/inlay_hints.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,58 @@ foo('line4');
608608
);
609609
}
610610

611+
#[tokio::test]
612+
async fn range_excluding_early_arg_does_not_shift_later_hints() {
613+
let backend = create_test_backend();
614+
let uri = Url::parse("file:///test/inlay.php").unwrap();
615+
let text = r#"<?php
616+
function make(string $prefix, string $name, int $entries, int $versions, int $stale): void {}
617+
make(
618+
'p',
619+
'n',
620+
1,
621+
2,
622+
3,
623+
);
624+
"#;
625+
626+
let open_params = DidOpenTextDocumentParams {
627+
text_document: TextDocumentItem {
628+
uri: uri.clone(),
629+
language_id: "php".to_string(),
630+
version: 1,
631+
text: text.to_string(),
632+
},
633+
};
634+
backend.did_open(open_params).await;
635+
636+
// Exclude the first argument's line ('p') from the requested range; the
637+
// remaining arguments must still map to their own parameter, not the
638+
// previous one, even though the positional counter must skip over the
639+
// excluded argument.
640+
let range = Range {
641+
start: Position {
642+
line: 4,
643+
character: 0,
644+
},
645+
end: Position {
646+
line: 8,
647+
character: 20,
648+
},
649+
};
650+
651+
let hints = backend
652+
.handle_inlay_hints(uri.as_ref(), text, range)
653+
.unwrap_or_default();
654+
655+
let lbls: Vec<String> = hints.iter().map(hint_label).collect();
656+
assert_eq!(
657+
lbls,
658+
vec!["name:", "entries:", "versions:", "stale:"],
659+
"hints after a range-excluded argument must still map to their own parameter"
660+
);
661+
}
662+
611663
// ─── String literal matching suppression ────────────────────────────────────
612664

613665
#[tokio::test]

0 commit comments

Comments
 (0)