Skip to content

Commit 6b613aa

Browse files
committed
Fix Double-negated instanceof narrowing.
1 parent 7be791b commit 6b613aa

6 files changed

Lines changed: 11 additions & 37 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ PHPantom understands Composer projects out of the box:
7575
- **[Architecture](docs/ARCHITECTURE.md).** Symbol resolution, stub loading, and inheritance merging.
7676
- **[Contributing](docs/CONTRIBUTING.md)**
7777
- **[Changelog](docs/CHANGELOG.md)**
78+
- **[Benchmarks](https://ajenbo.github.io/phpantom_lsp/dev/bench/).** Completion latency tracked on every commit.
7879
- **[Roadmap](docs/todo.md).** Planned features and domain-specific plans.
7980
8081
## Acknowledgements

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
- **Hover on variable definition sites.** Hovering a parameter, foreach binding, or catch variable no longer shows a redundant popup when the type is already visible in the signature. Assignment variables still show their resolved type.
6565
- **Inline `@var` annotations no longer leak across scopes.** A `/** @var Type $var */` annotation in one method no longer affects hover or completion for a same-named variable in a different method or class.
6666
- **Docblock tag parsing in description text.** Tags like `@throws` appearing mid-sentence in docblock descriptions (e.g. `"filtered out of @throws suggestions."`) are no longer mistakenly parsed as tags. Only `@` at a valid tag position (after the `* ` line prefix) is recognized.
67+
- **Double-negated `instanceof` narrowing.** `if (!!$x instanceof Foo)` now correctly narrows `$x` to `Foo` inside the block. Previously the double negation was not simplified and the type stayed unresolved.
6768

6869
## [0.4.0] - 2026-03-01
6970

docs/todo/testing.md

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# PHPantom — Ignored Fixture Tasks
22

3-
There are **228 fixture tests** in `tests/fixtures/`. Of these, **175
4-
pass** and **53 are ignored** because they exercise features or bug
3+
There are **228 fixture tests** in `tests/fixtures/`. Of these, **176
4+
pass** and **52 are ignored** because they exercise features or bug
55
fixes that are not yet implemented. Each ignored fixture has a
66
`// ignore:` comment explaining what is missing.
77

8-
This document groups the 53 ignored fixtures by the underlying work
8+
This document groups the 52 ignored fixtures by the underlying work
99
needed to un-ignore them. Tasks are ordered by the number of fixtures
1010
they unblock (descending), then by estimated effort. Once a task is
1111
complete, remove the `// ignore:` line from each fixture, verify the
@@ -338,20 +338,6 @@ clauses.
338338

339339
---
340340

341-
## 14. Double negated `instanceof` narrowing (1 fixture)
342-
343-
**Ref:** [type-inference.md §23](type-inference.md#23-double-negated-instanceof-narrowing)
344-
**Impact: Low · Effort: Low**
345-
346-
`if (!!($x instanceof Foo))` does not narrow. The double negation
347-
should cancel out.
348-
349-
**Fixture:**
350-
351-
- [ ] `narrowing/bangbang_instanceof.fixture``!!$x instanceof Foo` narrows inside the block
352-
353-
---
354-
355341
## 15. Negated `@phpstan-assert !Type` (1 fixture)
356342

357343
**Ref:** [type-inference.md §19](type-inference.md#19-negated-phpstan-assert-type)
@@ -546,7 +532,6 @@ Quick wins (Low effort, 1 fixture each):
546532

547533
| Task | Fixture |
548534
|---|---|
549-
| §14 Double negated `instanceof` | `narrowing/bangbang_instanceof` |
550535
| §17 Accessor on new line | `completion/accessor_on_new_line` |
551536
| §18 Partial static property prefix | `completion/partial_static_property` |
552537
| §23 `@phpstan-type` in foreach | `type/phpstan_type_alias` |

docs/todo/type-inference.md

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -692,17 +692,6 @@ last assertion's narrowing applies.
692692

693693
---
694694

695-
## 23. Double negated `instanceof` narrowing
696-
**Impact: Low · Effort: Low**
697-
698-
`if (!!($x instanceof Foo))` and `if (!(!$x instanceof Foo) { return; }`
699-
do not resolve correctly. The double negation cancels out but the
700-
narrowing engine does not simplify it.
701-
702-
**Discovered via:** fixture conversion (bangbang_instanceof).
703-
704-
---
705-
706695
## 24. Literal string conditional return type
707696
**Impact: Low · Effort: Low-Medium**
708697

src/completion/types/narrowing.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -249,13 +249,11 @@ pub(in crate::completion) fn try_extract_instanceof_with_negation<'b>(
249249
try_extract_instanceof_with_negation(inner.expression, var_name)
250250
}
251251
Expression::UnaryPrefix(prefix) if prefix.operator.is_not() => {
252-
// `!expr` — the inner expr should be a (possibly parenthesised) instanceof
253-
try_extract_instanceof(prefix.operand, var_name)
254-
.map(|cls| (cls, true))
255-
.or_else(|| {
256-
// Also support `!is_a($var, ClassName::class)`
257-
try_extract_is_a(prefix.operand, var_name).map(|cls| (cls, true))
258-
})
252+
// `!expr` — recurse so that `!!expr` (double negation) and
253+
// deeper chains like `!!!expr` are handled correctly: each
254+
// `!` flips the negation flag.
255+
try_extract_instanceof_with_negation(prefix.operand, var_name)
256+
.map(|(cls, neg)| (cls, !neg))
259257
}
260258
_ => {
261259
try_extract_instanceof(expr, var_name)

tests/fixtures/narrowing/bangbang_instanceof.fixture

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// test: double negation with instanceof narrows type
22
// feature: completion
33
// Adapted from phpactor if-statement/bangbang.test
4-
// ignore: double negation (!!) with instanceof not resolved
4+
55
// expect: fooMethod(
66
---
77
<?php

0 commit comments

Comments
 (0)