Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LSP: add call inlays for complex literals #25202

Merged
merged 6 commits into from
Jun 10, 2024
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
55 changes: 54 additions & 1 deletion tools/chpl-language-server/src/chpl-language-server.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,59 @@
import argparse
import configargparse

REAL_NUMBERIC = (chapel.RealLiteral, chapel.IntLiteral, chapel.UintLiteral)
NUMERIC = REAL_NUMBERIC + (chapel.ImagLiteral,)


def is_basic_literal_like(node: chapel.AstNode) -> Optional[chapel.Literal]:
"""
Check for "basic" literals: basically, 1, "hello", -42, etc.
Returns the "underlying" literal removing surrounding AST (1, "hello", 42).
This helps do type comparisons in more complex checks. If the node is
not a basic literal, returns None.
"""
if isinstance(node, chapel.Literal):
return node

if (
isinstance(node, chapel.OpCall)
and node.op() == "-"
and node.num_actuals() == 1
):
# Do not recurse; do not consider --42 as a basic literal.
act = node.actual(0)
if isinstance(act, NUMERIC):
return act

return None


def is_literal_like(node: chapel.AstNode) -> bool:
if is_basic_literal_like(node):
return True

if isinstance(node, chapel.OpCall):
# A complex number is far from a literal in the AST; in fact, it
# potentially has as many as 4 AST nodes: -1 + 2i has a unary negation,
# an addition, and two "pure" literals.
op = node.op()
if (op == "+" or op == "-") and node.num_actuals() == 2:
# The left element can be a 'basic literal-like', like 1 or -42i.
# But the right element shouldn't have any operators, otherwise
# we'd get somethig that looks like 1 - -42i. So we just
# use the right argument directly.
left = is_basic_literal_like(node.actual(0))
right = node.actual(1)

real_number = REAL_NUMBERIC
imag_number = chapel.ImagLiteral
if isinstance(left, real_number) and isinstance(right, imag_number):
return True
if isinstance(left, imag_number) and isinstance(right, real_number):
return True

return False


def decl_kind(decl: chapel.NamedDecl) -> Optional[SymbolKind]:
if isinstance(decl, chapel.Module) and decl.kind() != "implicit":
Expand Down Expand Up @@ -1222,7 +1275,7 @@ def get_call_inlays(
# tuples. We don't need hints for those.
continue

if not isinstance(act, chapel.core.Literal):
if not is_literal_like(act):
# Only show named arguments for literals.
continue

Expand Down
38 changes: 36 additions & 2 deletions tools/chpl-language-server/test/call_inlays.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ async def test_call_inlays(client: LanguageClient):


@pytest.mark.asyncio
@pytest.mark.xfail
async def test_call_inlays_complex(client: LanguageClient):
"""
Ensure that call inlays are shown for complex literals in call expressions.
Expand All @@ -84,9 +83,44 @@ async def test_call_inlays_complex(client: LanguageClient):
file = """
proc foo(a) {}
foo(3i+10);
foo(3i+10.0);
foo(3.0i+10.0);
foo(3.0i+10);
foo(10+3i);
foo(10.0+3i);
foo(10.0+3.0i);
foo(10+3.0i);
// Not 'literals' in our sense:
foo(3i + 4i);
foo(1 + 1);
foo((-1) - (-i));
"""

inlays = [(pos((1, 4)), "a = ", None)]
inlays = [(pos((i, 4)), "a = ", None) for i in range(1, 9)]

async with source_file(client, file) as doc:
await check_inlay_hints(client, doc, rng((0, 0), endpos(file)), inlays)


@pytest.mark.asyncio
async def test_call_inlays_negative(client: LanguageClient):
"""
Ensure that call inlays are shown for negative literals in call expressions.
"""

file = """
proc foo(a) {}
foo(-42);
foo(-42.0);
foo(-42i);
foo(-42.0i);
// Not 'literals' in our sense:
foo(- -42);
"""

inlays = [(pos((i, 4)), "a = ", None) for i in range(1, 5)]

async with source_file(client, file) as doc:
await check_inlay_hints(client, doc, rng((0, 0), endpos(file)), inlays)
Expand Down
Loading