Skip to content

Commit 913e1fc

Browse files
committed
Revert "Insert explicit return expressions in functions/macros/do-blocks (fredrikekre#48)"
This reverts commit cca294f.
1 parent f45b9bd commit 913e1fc

File tree

8 files changed

+16
-424
lines changed

8 files changed

+16
-424
lines changed

README.md

Lines changed: 0 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -562,7 +562,6 @@ This is a list of things that Runic currently is doing:
562562
- [Line width limit](#line-width-limit)
563563
- [Newlines in blocks](#newlines-in-blocks)
564564
- [Indentation](#indentation)
565-
- [Explicit `return`](#explicit-return)
566565
- [Spaces around operators, assignment, etc](#spaces-around-operators-assignment-etc)
567566
- [Spaces around keywords](#spaces-around-keywords)
568567
- [Multiline listlike expressions](#multiline-listlike-expressions)
@@ -722,58 +721,6 @@ x = a + b *
722721
d
723722
```
724723

725-
### Explicit `return`
726-
727-
Explicit `return` statements are ensured in function and macro definitions by adding
728-
`return` in front of the last expression, with some exceptions listed below.
729-
730-
- If the last expression is a `for` or `while` loop (which both always evaluate to
731-
`nothing`) `return` is added *after* the loop.
732-
- If the last expression is a `if` or `try` block the `return` is only added in case
733-
there is no `return` inside any of the branches.
734-
- If the last expression is a `let` or `begin` block the `return` is only added in case
735-
there is no `return` inside the block.
736-
- If the last expression is a macro call, the `return` is only added in case there is no
737-
`return` inside the macro.
738-
- No `return` is added in short form functions (`f(...) = ...`), short form anonymous
739-
functions (`(...) -> ...`), and `do`-blocks (`f(...) do ...; ...; end`).
740-
- If the last expression is a function call, and the function name is (or contains) `throw`
741-
or `error`, no `return` is added. This is because it is already obvious that these calls
742-
terminate the function and don't return any value.
743-
744-
Note that adding `return` changes the expression in a way that is visible to macros.
745-
Therefore it is, in general, not valid to add `return` to a function defined inside a macro
746-
since it isn't possible to know what the macro will expand to. For this reason this
747-
formatting rule is disabled for functions defined inside macros with the exception of some
748-
known and safe ones from Base (e.g. `@inline`, `@generated`, ...).
749-
750-
For the same reason mentioned above, if the last expression in a function is a macro call it
751-
isn't valid to step in and add `return` inside. Instead the `return` will be added in front
752-
of the macro call like any other expression (unless there is already a `return` inside of
753-
the macro as described above).
754-
755-
Examples:
756-
```diff
757-
function f(n)
758-
- sum(rand(n))
759-
+ return sum(rand(n))
760-
end
761-
762-
macro m(args...)
763-
- :(generate_expr(args...))
764-
+ return :(generate_expr(args...))
765-
end
766-
```
767-
768-
#### Potential changes
769-
- If the last expression is a `if` or `try` block it might be better to
770-
recurse into the branches and add `return` there. Looking at real code, if a
771-
function ends with an `if` block, it seems about 50/50 whether adding return
772-
*after* the block or adding return inside the branches is the best choice.
773-
Quite often `return if` is not the best but at least Runic's current
774-
formatting will force to think about the return value.
775-
See issue [#52](https://github.com/fredrikekre/Runic.jl/issues/52).
776-
777724
### Spaces around operators, assignment, etc
778725

779726
Runic formats spaces around infix operators, assignments, comparison chains, and type

src/JuliaSyntax.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,4 @@ function _show_green_node(io, node, indent, pos, str, show_trivia)
4848
p += x.span
4949
end
5050
end
51-
return
5251
end

src/Runic.jl

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ function Base.show(io::IO, ::MIME"text/plain", node::Node)
7070
show(io, node)
7171
println(io)
7272
_show_green_node(io, node, "", 1, nothing, true)
73-
return
7473
end
7574

7675
function Base.show(io::IO, node::Node)
@@ -149,7 +148,6 @@ mutable struct Context
149148
next_sibling::Union{Node, Nothing}
150149
# parent::Union{Node, Nothing}
151150
lineage_kinds::Vector{JuliaSyntax.Kind}
152-
lineage_macros::Vector{String}
153151
end
154152

155153
const RANGE_FORMATTING_BEGIN = "#= RUNIC RANGE FORMATTING " * "BEGIN =#"
@@ -284,12 +282,11 @@ function Context(
284282
call_depth = 0
285283
prev_sibling = next_sibling = nothing
286284
lineage_kinds = JuliaSyntax.Kind[]
287-
lineage_macros = String[]
288285
format_on = true
289286
return Context(
290287
src_str, src_tree, src_io, fmt_io, fmt_tree, quiet, verbose, assert, debug, check,
291288
diff, filemode, filename, line_ranges, indent_level, call_depth, format_on,
292-
prev_sibling, next_sibling, lineage_kinds, lineage_macros
289+
prev_sibling, next_sibling, lineage_kinds
293290
)
294291
end
295292

@@ -424,9 +421,6 @@ function format_node_with_kids!(ctx::Context, node::Node)
424421
ctx.prev_sibling = nothing
425422
ctx.next_sibling = nothing
426423
push!(ctx.lineage_kinds, kind(node))
427-
if kind(node) === K"macrocall"
428-
push!(ctx.lineage_macros, macrocall_name(ctx, node))
429-
end
430424

431425
# The new node parts. `kids′` aliases `kids` and only copied below if any of the
432426
# nodes change ("copy-on-write").
@@ -502,9 +496,6 @@ function format_node_with_kids!(ctx::Context, node::Node)
502496
ctx.prev_sibling = prev_sibling
503497
ctx.next_sibling = next_sibling
504498
pop!(ctx.lineage_kinds)
505-
if kind(node) === K"macrocall"
506-
pop!(ctx.lineage_macros)
507-
end
508499
ctx.call_depth -= 1
509500
# Return a new node if any of the kids changed
510501
if any_kid_changed
@@ -561,7 +552,6 @@ function format_node!(ctx::Context, node::Node)::Union{Node, Nothing, NullNode}
561552
@return_something no_spaces_around_colon_etc(ctx, node)
562553
@return_something parens_around_op_calls_in_colon(ctx, node)
563554
@return_something for_loop_use_in(ctx, node)
564-
@return_something explicit_return(ctx, node)
565555
@return_something braces_around_where_rhs(ctx, node)
566556
@return_something indent_multiline_strings(ctx, node)
567557
@return_something four_space_indent(ctx, node)

src/chisels.jl

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -822,57 +822,6 @@ function kmatch(kids, kinds, i = firstindex(kids))
822822
return true
823823
end
824824

825-
# Extract the macro name as written in the source.
826-
function macrocall_name(ctx, node)
827-
@assert kind(node) === K"macrocall"
828-
kids = verified_kids(node)
829-
pred = x -> kind(x) in KSet"MacroName StringMacroName CmdMacroName core_@cmd"
830-
mkind = kind(first_leaf_predicate(node, pred)::Node)
831-
if kmatch(kids, KSet"@ MacroName")
832-
p = position(ctx.fmt_io)
833-
bytes = read(ctx.fmt_io, span(kids[1]) + span(kids[2]))
834-
seek(ctx.fmt_io, p)
835-
return String(bytes)
836-
elseif kmatch(kids, KSet".") || kmatch(kids, KSet"CmdMacroName") ||
837-
kmatch(kids, KSet"StringMacroName")
838-
bytes = read_bytes(ctx, kids[1])
839-
if mkind === K"CmdMacroName"
840-
append!(bytes, "_cmd")
841-
elseif mkind === K"StringMacroName"
842-
append!(bytes, "_str")
843-
end
844-
return String(bytes)
845-
elseif kmatch(kids, KSet"core_@cmd")
846-
bytes = read_bytes(ctx, kids[1])
847-
@assert length(bytes) == 0
848-
return "core_@cmd"
849-
else
850-
# Don't bother looking in more complex expressions, just return unknown
851-
return "<unknown macro>"
852-
end
853-
end
854-
855-
# Inserting `return` modifies the AST in a way that is visible to macros.. In general it is
856-
# never safe to change the AST inside a macro, but we make an exception for some common
857-
# "known" macros in order to be able to format functions that e.g. have an `@inline`
858-
# annotation in front.
859-
const MACROS_SAFE_TO_INSERT_RETURN = let set = Set{String}()
860-
for m in (
861-
"inline", "noinline", "propagate_inbounds", "generated", "eval",
862-
"assume_effects", "doc",
863-
)
864-
push!(set, "@$m", "Base.@$m")
865-
end
866-
push!(set, "Core.@doc")
867-
set
868-
end
869-
function safe_to_insert_return(ctx, node)
870-
for m in ctx.lineage_macros
871-
m in MACROS_SAFE_TO_INSERT_RETURN || return false
872-
end
873-
return true
874-
end
875-
876825
##########################
877826
# Utilities for IOBuffer #
878827
##########################

src/debug.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ function Base.showerror(io::IO, err::AssertionError)
2424
"please file an issue with a reproducible example at " *
2525
"https://github.com/fredrikekre/Runic.jl/issues/new."
2626
)
27-
return
2827
end
2928

3029
macro assert(expr)

src/main.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,6 @@ function main(argv)
526526
cmd = setenv(ignorestatus(cmd); dir = dir)
527527
cmd = pipeline(cmd, stdout = stderr, stderr = stderr)
528528
run_cmd(cmd)
529-
return
530529
end
531530
end
532531

src/runestone.jl

Lines changed: 0 additions & 183 deletions
Original file line numberDiff line numberDiff line change
@@ -3463,186 +3463,3 @@ function spaces_around_comments(ctx::Context, node::Node)
34633463
return make_node(node, kids′)
34643464
end
34653465
end
3466-
3467-
function return_node(ctx::Context, ret::Node)
3468-
ws = Node(JuliaSyntax.SyntaxHead(K"Whitespace", JuliaSyntax.TRIVIA_FLAG), 1)
3469-
kids = [
3470-
Node(JuliaSyntax.SyntaxHead(K"return", 0), 6),
3471-
]
3472-
if is_leaf(ret)
3473-
replace_bytes!(ctx, "return ", 0)
3474-
push!(kids, ws)
3475-
push!(kids, ret)
3476-
else
3477-
@assert !(kind(first_leaf(ret)) in KSet"NewlineWs Whitespace")
3478-
replace_bytes!(ctx, "return ", 0)
3479-
push!(kids, ws)
3480-
push!(kids, ret)
3481-
end
3482-
return Node(JuliaSyntax.SyntaxHead(K"return", 0), kids)
3483-
end
3484-
3485-
function has_return(node::Node)
3486-
kids = verified_kids(node)
3487-
if kind(node) in KSet"let catch else finally"
3488-
idx = findfirst(x -> kind(x) === K"block", kids)::Int
3489-
if kind(node) === K"let"
3490-
idx = findnext(x -> kind(x) === K"block", kids, idx + 1)::Int
3491-
end
3492-
return has_return(kids[idx])
3493-
elseif kind(node) in KSet"try if elseif"
3494-
# Look for the initial try/if block and then for
3495-
# catch/else/finally (for try) or elseif/else (for if).
3496-
pred = function (x)
3497-
return !is_leaf(x) && kind(x) in KSet"catch else finally elseif block"
3498-
end
3499-
idx = findfirst(pred, kids)
3500-
while idx !== nothing
3501-
has_return(kids[idx]) && return true
3502-
idx = findnext(pred, kids, idx + 1)
3503-
end
3504-
return false
3505-
elseif kind(node) === K"macrocall"
3506-
# Check direct kids but also recurse into blocks to catch e.g. `@foo begin ... end`.
3507-
idx = findfirst(x -> kind(x) === K"return", kids)
3508-
idx === nothing || return true
3509-
return any(kids) do x
3510-
return !is_leaf(x) && kind(x) in KSet"let try if block macrocall" && has_return(x)
3511-
end
3512-
elseif kind(node) === K"block"
3513-
# Don't care whether this is the last expression,
3514-
# that is the job of a linter or something I guess.
3515-
return findfirst(x -> kind(x) === K"return", kids) !== nothing
3516-
else
3517-
unreachable()
3518-
end
3519-
end
3520-
3521-
function explicit_return_block(ctx, node)
3522-
@assert kind(node) === K"block"
3523-
if has_return(node)
3524-
# If the block already has a return node (anywhere) we accept it and move on.
3525-
return nothing
3526-
end
3527-
kids = verified_kids(node)
3528-
kids′ = kids
3529-
rexpr_idx = findlast(!JuliaSyntax.is_whitespace, kids′)
3530-
if rexpr_idx === nothing
3531-
# Empty block. TODO: Perhaps add `return nothing`?
3532-
return nothing
3533-
end
3534-
rexpr = kids′[rexpr_idx]
3535-
@assert kind(rexpr) !== K"return" # Should have been caught by has_return
3536-
if is_leaf(rexpr) ||
3537-
kind(rexpr) in KSet"call dotcall tuple vect ref hcat typed_hcat vcat typed_vcat \
3538-
? && || :: juxtapose <: >: comparison string . -> comprehension do macro \
3539-
typed_comprehension where parens curly function quote global local =" ||
3540-
is_string_macro(rexpr) || is_assignment(rexpr) ||
3541-
(kind(rexpr) in KSet"let if try" && !has_return(rexpr)) ||
3542-
(kind(rexpr) === K"macrocall" && !has_return(rexpr)) ||
3543-
(is_begin_block(rexpr) && !has_return(rexpr)) ||
3544-
kind(rexpr) === K"quote" && JuliaSyntax.has_flags(rexpr, JuliaSyntax.COLON_QUOTE)
3545-
# The cases caught in this branch are simple, just wrap the last expression in a
3546-
# return node. Also make sure the previous node is a K"NewlineWs".
3547-
for i in 1:(rexpr_idx - 1)
3548-
accept_node!(ctx, kids′[i])
3549-
end
3550-
# If this is a call node, and the call the function name contains `throw` or `error` we
3551-
# bail because `return throw(...)` looks kinda stupid.
3552-
if kind(rexpr) === K"call"
3553-
call_kids = verified_kids(rexpr)
3554-
fname_idx = findfirst(!JuliaSyntax.is_whitespace, call_kids)::Int
3555-
@assert fname_idx == firstindex(call_kids)
3556-
local fname
3557-
let p = position(ctx.fmt_io)
3558-
fname = String(read_bytes(ctx, call_kids[fname_idx]))
3559-
seek(ctx.fmt_io, p)
3560-
end
3561-
if contains(fname, "throw") || contains(fname, "error")
3562-
return nothing
3563-
end
3564-
end
3565-
# We will make changes so copy
3566-
kids′ = kids′ === kids ? copy(kids) : kids′
3567-
# Make sure the previous node is a K"NewlineWs"
3568-
if !kmatch(kids′, KSet"NewlineWs", rexpr_idx - 1)
3569-
spn = 0
3570-
@assert kind(first_leaf(rexpr)) !== K"NewlineWs"
3571-
# Can it happen that there are whitespace hidden in the previous node?
3572-
# Let's see if this assert ever fire.
3573-
if rexpr_idx > 1
3574-
prev = kids′[rexpr_idx - 1]
3575-
if !is_leaf(prev)
3576-
@assert !(kind(last_leaf(prev)) in KSet"Whitespace NewlineWs")
3577-
end
3578-
end
3579-
# Check whether there are whitespace we need to overwrite
3580-
if kmatch(kids′, KSet"Whitespace", rexpr_idx - 1)
3581-
# The previous node is whitespace
3582-
spn = span(popat!(kids′, rexpr_idx - 1))
3583-
seek(ctx.fmt_io, position(ctx.fmt_io) - spn)
3584-
rexpr_idx -= 1
3585-
@assert kind(first_leaf(rexpr)) !== K"Whitespace"
3586-
end
3587-
@assert kind(first_leaf(rexpr)) !== K"Whitespace"
3588-
nl = "\n" * " "^(4 * ctx.indent_level)
3589-
nlnode = Node(JuliaSyntax.SyntaxHead(K"NewlineWs", JuliaSyntax.TRIVIA_FLAG), sizeof(nl))
3590-
insert!(kids′, rexpr_idx, nlnode)
3591-
rexpr_idx += 1
3592-
replace_bytes!(ctx, nl, spn)
3593-
accept_node!(ctx, nlnode)
3594-
end
3595-
ret = return_node(ctx, rexpr)
3596-
kids′[rexpr_idx] = ret
3597-
return make_node(node, kids′)
3598-
elseif kind(rexpr) in KSet"for while"
3599-
# For `for` and `while` loops we add `return` after the block.
3600-
@assert kind(kids′[end]) === K"NewlineWs"
3601-
@assert kind(last_leaf(rexpr)) === K"end"
3602-
insert_idx = lastindex(kids)
3603-
kids′ = kids′ === kids ? copy(kids) : kids′
3604-
for i in 1:(insert_idx - 1)
3605-
accept_node!(ctx, kids′[i])
3606-
end
3607-
# Insert newline
3608-
nl = "\n" * " "^(4 * ctx.indent_level)
3609-
nlnode = Node(JuliaSyntax.SyntaxHead(K"NewlineWs", JuliaSyntax.TRIVIA_FLAG), sizeof(nl))
3610-
insert!(kids′, insert_idx, nlnode)
3611-
replace_bytes!(ctx, nl, 0)
3612-
accept_node!(ctx, nlnode)
3613-
# Insert `return`
3614-
replace_bytes!(ctx, "return", 0)
3615-
retnode = Node(
3616-
JuliaSyntax.SyntaxHead(K"return", 0), [
3617-
Node(JuliaSyntax.SyntaxHead(K"return", 0), 6),
3618-
]
3619-
)
3620-
insert!(kids′, insert_idx + 1, retnode)
3621-
return make_node(node, kids′)
3622-
else
3623-
# error("Unhandled node in explicit_return_block: $(kind(rexpr))")
3624-
end
3625-
return nothing
3626-
end
3627-
3628-
function explicit_return(ctx::Context, node::Node)
3629-
if !(!is_leaf(node) && kind(node) in KSet"function macro")
3630-
return nothing
3631-
end
3632-
if !safe_to_insert_return(ctx, node)
3633-
return nothing
3634-
end
3635-
kids = verified_kids(node)
3636-
pos = position(ctx.fmt_io)
3637-
block_idx = findlast(x -> kind(x) === K"block", verified_kids(node))
3638-
block_idx === nothing && return nothing
3639-
for i in 1:(block_idx - 1)
3640-
accept_node!(ctx, kids[i])
3641-
end
3642-
block′ = explicit_return_block(ctx, kids[block_idx])
3643-
seek(ctx.fmt_io, pos)
3644-
block′ === nothing && return nothing
3645-
kids′ = copy(kids)
3646-
kids′[block_idx] = block′
3647-
return make_node(node, kids′)
3648-
end

0 commit comments

Comments
 (0)