Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions features/edit/code_lens.feature
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Feature: Code lens
Scenario: Emit a "Show references" lens for a declaration
When I request code lenses for "/Foo.xphp"
Then a code lens titled "Show references" is offered
And every code lens range is within the bounds of "/Foo.xphp"

Scenario: Resolve a lens to a usage count
When I request code lenses for "/Foo.xphp"
Expand Down
36 changes: 36 additions & 0 deletions features/navigate/definition.feature
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,39 @@ Feature: Go to definition
When I request "textDocument/definition" on "Plastic" at line 7 of "SelfUse.xphp"
Then the response points to "Models/Plastic.xphp"
And the target range covers the "Plastic" class name

Scenario: Jump through a nullsafe method chain to the final method declaration
# Self-contained fixtures (NOT Background overrides): a warmed FQN index goes
# stale when a Background file is redefined, so the chain's terminal hop would
# miss. Fresh files keep the index consistent.
Given the file at "Chain/Bag.xphp" contains the following lines:
"""
<?php
namespace App\Chain;
class Bag<T>
{
public function first(): ?T { return null; }
}
"""
And the file at "Chain/Widget.xphp" contains the following lines:
"""
<?php
namespace App\Chain;
final class Widget
{
public function spin(): ?Widget { return null; }
}
"""
And the file at "ChainUse.xphp" contains the following lines:
"""
<?php
namespace App;
use App\Chain\Bag;
use App\Chain\Widget;
$bag = new Bag::<Widget>();
$w = $bag->first()?->spin();
"""
And the FQN index has been warmed on initialize
When I request "textDocument/definition" on "spin" at line 5 of "ChainUse.xphp"
Then the response points to "Chain/Widget.xphp"
And the target range covers the "spin" method declaration
1 change: 1 addition & 0 deletions features/navigate/document_highlight.feature
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Feature: Document highlight
When I request "textDocument/documentHighlight" on "User" at line 2 of "/Use.xphp"
Then the response contains 3 highlights
And each highlight covers "User" in "/Use.xphp"
And every document highlight range is within the bounds of "/Use.xphp"

Scenario: Classify the declaration as a write and the uses as reads
When I request "textDocument/documentHighlight" on "User" at line 2 of "/Use.xphp"
Expand Down
1 change: 1 addition & 0 deletions features/navigate/document_symbol.feature
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Feature: Document symbol outline
When I request "textDocument/documentSymbol" for "/User.xphp"
Then the outline contains a class "User" with 5 members
And the "User" selection range in "/User.xphp" covers "User"
And every document symbol range is within the bounds of "/User.xphp"

Scenario Outline: Each declared member appears nested in the outline
When I request "textDocument/documentSymbol" for "/User.xphp"
Expand Down
1 change: 1 addition & 0 deletions features/understand/folding_range.feature
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Feature: Folding ranges
When I request "textDocument/foldingRange" for "/Box.xphp"
Then the response contains 3 folding ranges
And a folding range of kind "region" spans 2 to 12
And every folding range is within the bounds of "/Box.xphp"

Scenario: Single-line declarations are not folded
Given the file at "/One.xphp" contains the following lines:
Expand Down
130 changes: 130 additions & 0 deletions features/understand/hover.feature
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,133 @@ Feature: Hover
And the FQN index has been warmed on initialize
When I request "textDocument/hover" on "first" at line 6 of "/Use.xphp"
Then the hover contents contain "Pair<App\Plastic, App\User> $first"

Scenario: A nullsafe property access on a nullable generic result is itself nullable
Given the file at "/Collection.xphp" contains the following lines:
"""
<?php
namespace App\Containers;
class Collection<T>
{
public function first(): ?T { return null; }
}
"""
And the file at "/User.xphp" contains the following lines:
"""
<?php
namespace App\Models;
class User { public string $name = ''; }
"""
And the file at "/Use.xphp" contains the following lines:
"""
<?php
use App\Containers\Collection;
use App\Models\User;
$users = new Collection::<User>();
$firstName = $users->first()?->name;
"""
And the FQN index has been warmed on initialize
When I request "textDocument/hover" on "firstName" at line 4 of "/Use.xphp"
Then the hover contents contain "?string $firstName"

Scenario: A multi-hop nullsafe property chain through a nullable generic result is nullable
Given the file at "/Collection.xphp" contains the following lines:
"""
<?php
namespace App\Containers;
class Collection<T>
{
public function first(): ?T { return null; }
}
"""
And the file at "/User.xphp" contains the following lines:
"""
<?php
namespace App\Models;
class User
{
public string $name = '';
public ?User $bestFriend = null;
}
"""
And the file at "/Use.xphp" contains the following lines:
"""
<?php
use App\Containers\Collection;
use App\Models\User;
$users = new Collection::<User>();
$friendName = $users->first()?->bestFriend?->name;
"""
And the FQN index has been warmed on initialize
When I request "textDocument/hover" on "friendName" at line 4 of "/Use.xphp"
Then the hover contents contain "?string $friendName"

Scenario: A chain through a non-generic method resolves
Given the file at "/Collection.xphp" contains the following lines:
"""
<?php
namespace App\Containers;
class Collection<T>
{
public function first(): ?T { return null; }
}
"""
And the file at "/User.xphp" contains the following lines:
"""
<?php
namespace App\Models;
class User
{
public string $name = '';
public function mirror(): ?User { return null; }
}
"""
And the file at "/Use.xphp" contains the following lines:
"""
<?php
use App\Containers\Collection;
use App\Models\User;
$users = new Collection::<User>();
$picked = $users->first()?->mirror()?->name;
"""
And the FQN index has been warmed on initialize
When I request "textDocument/hover" on "picked" at line 4 of "/Use.xphp"
Then the hover contents contain "?string $picked"

Scenario: A chain through a property typed via a cross-namespace use import resolves
Given the file at "/Collection.xphp" contains the following lines:
"""
<?php
namespace App\Containers;
class Collection<T>
{
public function first(): ?T { return null; }
}
"""
And the file at "/Profile.xphp" contains the following lines:
"""
<?php
namespace App\Other;
class Profile { public string $bio = ''; }
"""
And the file at "/User.xphp" contains the following lines:
"""
<?php
namespace App\Models;
use App\Other\Profile;
class User
{
public ?Profile $profile = null;
}
"""
And the file at "/Use.xphp" contains the following lines:
"""
<?php
use App\Containers\Collection;
use App\Models\User;
$users = new Collection::<User>();
$profileBio = $users->first()?->profile?->bio;
"""
And the FQN index has been warmed on initialize
When I request "textDocument/hover" on "profileBio" at line 4 of "/Use.xphp"
Then the hover contents contain "?string $profileBio"
1 change: 1 addition & 0 deletions features/understand/inlay_hints.feature
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Feature: Inlay hints
When I request "textDocument/inlayHint" for the visible range of "/Use.xphp"
Then exactly 1 inlay hint is rendered
And an inlay hint ": ?App\Models\User" is rendered after "$first" on line 4 of "/Use.xphp"
And every inlay hint position is within the bounds of "/Use.xphp"

Scenario: Hint a generic method turbofish called on a local-variable receiver
Given the file at "/Util.xphp" contains the following lines:
Expand Down
1 change: 1 addition & 0 deletions features/understand/semantic_tokens.feature
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Feature: Semantic tokens
When I request "textDocument/semanticTokens/full" for "/box.xphp"
Then the semantic tokens are non-empty
And a "typeParameter" token covers "T" in "/box.xphp"
And every semantic token is within the bounds of "/box.xphp"

Scenario: Highlight the type parameter of a generic closure
Given the file at "/closure.xphp" contains the following lines:
Expand Down
56 changes: 56 additions & 0 deletions features/validate/diagnostics.feature
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,59 @@ Feature: Diagnostics
When I analyze "/Bounds.xphp" for diagnostics
Then a "xphp.ctor-arg-mismatch" diagnostic is reported
And the "xphp.ctor-arg-mismatch" diagnostic underlines "new User()"

Scenario: Warn about a null dereference on a chained nullable receiver
Given the file at "/Collection.xphp" contains the following lines:
"""
<?php
namespace App;
class Collection<T>
{
public function first(): ?T { return null; }
}
"""
And the file at "/User.xphp" contains the following lines:
"""
<?php
namespace App;
final class User { public string $name = ''; }
"""
And the file at "/Use.xphp" contains the following lines:
"""
<?php
namespace App;
$users = new Collection::<User>();
$name = $users->first()->name;
"""
And the FQN index has been warmed on initialize
When I analyze "/Use.xphp" for diagnostics
Then a "xphp.null-deref" diagnostic is reported saying "possibly-null"
And the "xphp.null-deref" diagnostic underlines "name"
And every reported diagnostic range is within document bounds

Scenario: A nullsafe access on the same nullable chain is not flagged
Given the file at "/Collection.xphp" contains the following lines:
"""
<?php
namespace App;
class Collection<T>
{
public function first(): ?T { return null; }
}
"""
And the file at "/User.xphp" contains the following lines:
"""
<?php
namespace App;
final class User { public string $name = ''; }
"""
And the file at "/Use.xphp" contains the following lines:
"""
<?php
namespace App;
$users = new Collection::<User>();
$name = $users->first()?->name;
"""
And the FQN index has been warmed on initialize
When I analyze "/Use.xphp" for diagnostics
Then no diagnostics are reported
18 changes: 18 additions & 0 deletions src/Analyzer/DiagnosticCode.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,24 @@ enum DiagnosticCode: string
*/
case ArgumentMismatch = 'xphp.arg-mismatch';

/**
* A property/method was accessed via plain `->` on a receiver whose
* static type is nullable -- a would-be runtime
* `Error: Attempt to read property "x" on null` (or
* `Call to a member function on null`).
*
* Conservative scope (low false-positive): fires ONLY when the immediate
* receiver is itself a nullable member-access sub-expression -- a chain
* like `$users->first()->name` where `first()` returns `?User` and no
* inline guard is syntactically possible. Bare-variable receivers
* (`$x->y`) are deferred to a future flow-narrowing pass, and a nullsafe
* access (`$users->first()?->name`) is correct so it never fires.
*
* Severity is Warning, not Error: the inference is conservative but the
* editor should keep it dismissable, matching `xphp.undefined-name`.
*/
case NullDeref = 'xphp.null-deref';

/**
* Map a RuntimeException raised by Registry::recordInstantiation to its
* diagnostic code. The Registry doesn't (currently) use a typed exception
Expand Down
Loading
Loading