Skip to content

Commit 3368d8b

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

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
@@ -460,7 +460,6 @@ This is a list of things that Runic currently is doing:
460460
- [Line width limit](#line-width-limit)
461461
- [Newlines in blocks](#newlines-in-blocks)
462462
- [Indentation](#indentation)
463-
- [Explicit `return`](#explicit-return)
464463
- [Spaces around operators, assignment, etc](#spaces-around-operators-assignment-etc)
465464
- [Spaces around keywords](#spaces-around-keywords)
466465
- [Multiline listlike expressions](#multiline-listlike-expressions)
@@ -620,58 +619,6 @@ x = a + b *
620619
d
621620
```
622621

623-
### Explicit `return`
624-
625-
Explicit `return` statements are ensured in function and macro definitions by adding
626-
`return` in front of the last expression, with some exceptions listed below.
627-
628-
- If the last expression is a `for` or `while` loop (which both always evaluate to
629-
`nothing`) `return` is added *after* the loop.
630-
- If the last expression is a `if` or `try` block the `return` is only added in case
631-
there is no `return` inside any of the branches.
632-
- If the last expression is a `let` or `begin` block the `return` is only added in case
633-
there is no `return` inside the block.
634-
- If the last expression is a macro call, the `return` is only added in case there is no
635-
`return` inside the macro.
636-
- No `return` is added in short form functions (`f(...) = ...`), short form anonymous
637-
functions (`(...) -> ...`), and `do`-blocks (`f(...) do ...; ...; end`).
638-
- If the last expression is a function call, and the function name is (or contains) `throw`
639-
or `error`, no `return` is added. This is because it is already obvious that these calls
640-
terminate the function and don't return any value.
641-
642-
Note that adding `return` changes the expression in a way that is visible to macros.
643-
Therefore it is, in general, not valid to add `return` to a function defined inside a macro
644-
since it isn't possible to know what the macro will expand to. For this reason this
645-
formatting rule is disabled for functions defined inside macros with the exception of some
646-
known and safe ones from Base (e.g. `@inline`, `@generated`, ...).
647-
648-
For the same reason mentioned above, if the last expression in a function is a macro call it
649-
isn't valid to step in and add `return` inside. Instead the `return` will be added in front
650-
of the macro call like any other expression (unless there is already a `return` inside of
651-
the macro as described above).
652-
653-
Examples:
654-
```diff
655-
function f(n)
656-
- sum(rand(n))
657-
+ return sum(rand(n))
658-
end
659-
660-
macro m(args...)
661-
- :(generate_expr(args...))
662-
+ return :(generate_expr(args...))
663-
end
664-
```
665-
666-
#### Potential changes
667-
- If the last expression is a `if` or `try` block it might be better to
668-
recurse into the branches and add `return` there. Looking at real code, if a
669-
function ends with an `if` block, it seems about 50/50 whether adding return
670-
*after* the block or adding return inside the branches is the best choice.
671-
Quite often `return if` is not the best but at least Runic's current
672-
formatting will force to think about the return value.
673-
See issue [#52](https://github.com/fredrikekre/Runic.jl/issues/52).
674-
675622
### Spaces around operators, assignment, etc
676623

677624
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)
@@ -148,7 +147,6 @@ mutable struct Context
148147
next_sibling::Union{Node, Nothing}
149148
# parent::Union{Node, Nothing}
150149
lineage_kinds::Vector{JuliaSyntax.Kind}
151-
lineage_macros::Vector{String}
152150
end
153151

154152
const RANGE_FORMATTING_BEGIN = "#= RUNIC RANGE FORMATTING " * "BEGIN =#"
@@ -272,12 +270,11 @@ function Context(
272270
call_depth = 0
273271
prev_sibling = next_sibling = nothing
274272
lineage_kinds = JuliaSyntax.Kind[]
275-
lineage_macros = String[]
276273
format_on = true
277274
return Context(
278275
src_str, src_tree, src_io, fmt_io, fmt_tree, quiet, verbose, assert, debug, check,
279276
diff, filemode, line_ranges, indent_level, call_depth, format_on, prev_sibling, next_sibling,
280-
lineage_kinds, lineage_macros
277+
lineage_kinds
281278
)
282279
end
283280

@@ -412,9 +409,6 @@ function format_node_with_kids!(ctx::Context, node::Node)
412409
ctx.prev_sibling = nothing
413410
ctx.next_sibling = nothing
414411
push!(ctx.lineage_kinds, kind(node))
415-
if kind(node) === K"macrocall"
416-
push!(ctx.lineage_macros, macrocall_name(ctx, node))
417-
end
418412

419413
# The new node parts. `kids′` aliases `kids` and only copied below if any of the
420414
# nodes change ("copy-on-write").
@@ -490,9 +484,6 @@ function format_node_with_kids!(ctx::Context, node::Node)
490484
ctx.prev_sibling = prev_sibling
491485
ctx.next_sibling = next_sibling
492486
pop!(ctx.lineage_kinds)
493-
if kind(node) === K"macrocall"
494-
pop!(ctx.lineage_macros)
495-
end
496487
ctx.call_depth -= 1
497488
# Return a new node if any of the kids changed
498489
if any_kid_changed
@@ -549,7 +540,6 @@ function format_node!(ctx::Context, node::Node)::Union{Node, Nothing, NullNode}
549540
@return_something no_spaces_around_colon_etc(ctx, node)
550541
@return_something parens_around_op_calls_in_colon(ctx, node)
551542
@return_something for_loop_use_in(ctx, node)
552-
@return_something explicit_return(ctx, node)
553543
@return_something braces_around_where_rhs(ctx, node)
554544
@return_something indent_multiline_strings(ctx, node)
555545
@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
@@ -495,7 +495,6 @@ function main(argv)
495495
cmd = setenv(ignorestatus(cmd); dir = dir)
496496
cmd = pipeline(cmd, stdout = stderr, stderr = stderr)
497497
run_cmd(cmd)
498-
return
499498
end
500499
end
501500

src/runestone.jl

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

0 commit comments

Comments
 (0)