From e949236f29347f3e57a1a3a7d5ec656872ad8347 Mon Sep 17 00:00:00 2001 From: Lionel Zoubritzky Date: Thu, 12 Oct 2023 13:15:22 +0200 Subject: [PATCH] Handle infix operators in REPL completion (#51366) Fix https://github.com/JuliaLang/julia/issues/51194 This PR fixes a regression introduced in https://github.com/JuliaLang/julia/pull/49294, so I believe it should be backported to v1.10. In the current code, completion of `qux(foo, bar.` is detected by parsing `foo(qux, bar` as an incomplete expression, and then looking for the sub-expression to complete (here, `bar.`). This approach fails however for infix calls, since completing `foo + bar.` starts by parsing `foo + bar`, which is a complete call expression, and so the code behaves as if completing `(foo + bar).` instead of `bar.`. This leads to the current problematic behaviour: ```julia julia> Complex(1, 3) + (4//5).#TAB im re ``` which would be correct for `(Complex(1, 3) + (4//5)).#TAB`, but here we expect ```julia julia> Complex(1, 3) + (4//5).#TAB den num ``` This PR fixes that by trying to detect infix calls. In the long term, all this ad-hoc and probably somewhat wrong string processing should be replaced by proper use of `JuliaSyntax` (as mentioned in https://github.com/JuliaLang/julia/pull/49294#issue-1659443492, https://github.com/JuliaLang/julia/issues/50817#issuecomment-1668773346 and probably other places), but for now at least this fixes the regression. --- stdlib/REPL/src/REPLCompletions.jl | 11 +++++++++++ stdlib/REPL/test/replcompletions.jl | 15 +++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index eeebeeb51f788..04e376b013114 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -1038,6 +1038,17 @@ function complete_identifiers!(suggestions::Vector{Completion}, @nospecialize(ff ex = Meta.parse(lookup_name, raise=false, depwarn=false) end isexpr(ex, :incomplete) && (ex = nothing) + elseif isexpr(ex, :call) && length(ex.args) > 1 + isinfix = s[end] != ')' + # A complete call expression that does not finish with ')' is an infix call. + if !isinfix + # Handle infix call argument completion of the form bar + foo(qux). + frange, end_of_identifier = find_start_brace(@view s[1:prevind(s, end)]) + isinfix = Meta.parse(@view(s[frange[1]:end]), raise=false, depwarn=false) == ex.args[end] + end + if isinfix + ex = ex.args[end] + end end end append!(suggestions, complete_symbol(ex, name, ffunc, context_module)) diff --git a/stdlib/REPL/test/replcompletions.jl b/stdlib/REPL/test/replcompletions.jl index c48deac2f2d50..a61a737ed4f85 100644 --- a/stdlib/REPL/test/replcompletions.jl +++ b/stdlib/REPL/test/replcompletions.jl @@ -25,6 +25,7 @@ let ex = quote (::Test_y)() = "", "" unicode_αβγ = Test_y(1) + Base.:(+)(x::Test_x, y::Test_y) = Test_x(Test_y(x.xx.yy + y.yy)) module CompletionFoo2 end @@ -2069,3 +2070,17 @@ end # If this last test starts failing, that's okay, just pick a new example symbol: @test !Base.isexported(Base, :ispublic) end + +# issue #51194 +for (s, compl) in (("2*CompletionFoo.nam", "named"), + (":a isa CompletionFoo.test!1", "test!12"), + ("-CompletionFoo.Test_y(3).", "yy"), + ("99 ⨷⁻ᵨ⁷ CompletionFoo.type_test.", "xx"), + ("CompletionFoo.type_test + CompletionFoo.Test_y(2).", "yy"), + ("(CompletionFoo.type_test + CompletionFoo.Test_y(2)).", "xx"), + ("CompletionFoo.type_test + CompletionFoo.unicode_αβγ.", "yy"), + ("(CompletionFoo.type_test + CompletionFoo.unicode_αβγ).", "xx"), + ("foo'CompletionFoo.test!1", "test!12")) + c, r = test_complete(s) + @test only(c) == compl +end