diff --git a/HISTORY.md b/HISTORY.md index 0351c875ed404..dfb96241bef13 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -50,6 +50,7 @@ Language changes * It is now an error to mark a binding as both `public` and `export`ed ([#53664]). * Errors during `getfield` now raise a new `FieldError` exception type instead of the generic `ErrorException` ([#54504]). +* Macros in function-signature-position no longer require parentheses. E.g. `function @main(args) ... end` is now permitted, whereas `function (@main)(args) ... end` was required in prior Julia versions. Compiler/Runtime improvements ----------------------------- diff --git a/base/client.jl b/base/client.jl index 3449e2fbf2e62..df0cf9043996e 100644 --- a/base/client.jl +++ b/base/client.jl @@ -605,7 +605,7 @@ The `@main` macro may be used standalone or as part of the function definition, case, parentheses are required. In particular, the following are equivalent: ``` -function (@main)(args) +function @main(args) println("Hello World") end ``` @@ -624,7 +624,7 @@ imported into `Main`, it will be treated as an entrypoint in `Main`: ``` module MyApp export main - (@main)(args) = println("Hello World") + @main(args) = println("Hello World") end using .MyApp # `julia` Will execute MyApp.main at the conclusion of script execution @@ -634,7 +634,7 @@ Note that in particular, the semantics do not attach to the method or the name: ``` module MyApp - (@main)(args) = println("Hello World") + @main(args) = println("Hello World") end const main = MyApp.main # `julia` Will *NOT* execute MyApp.main unless there is a separate `@main` annotation in `Main` @@ -644,9 +644,6 @@ const main = MyApp.main This macro is new in Julia 1.11. At present, the precise semantics of `@main` are still subject to change. """ macro main(args...) - if !isempty(args) - error("`@main` is expected to be used as `(@main)` without macro arguments.") - end if isdefined(__module__, :main) if Base.binding_module(__module__, :main) !== __module__ error("Symbol `main` is already a resolved import in module $(__module__). `@main` must be used in the defining module.") @@ -657,5 +654,9 @@ macro main(args...) global main global var"#__main_is_entrypoint__#"::Bool = true end) - esc(:main) + if !isempty(args) + Expr(:call, esc(:main), map(esc, args)...) + else + esc(:main) + end end diff --git a/src/julia-parser.scm b/src/julia-parser.scm index 891a26bb0ea49..4415dc8686065 100644 --- a/src/julia-parser.scm +++ b/src/julia-parser.scm @@ -1329,13 +1329,13 @@ (define (valid-func-sig? paren sig) (and (pair? sig) - (or (eq? (car sig) 'call) - (eq? (car sig) 'tuple) + (or (memq (car sig) '(call tuple)) + (and (not paren) (eq? (car sig) 'macrocall)) (and paren (eq? (car sig) 'block)) (and paren (eq? (car sig) '...)) (and (eq? (car sig) '|::|) (pair? (cadr sig)) - (eq? (car (cadr sig)) 'call)) + (memq (car (cadr sig)) '(call macrocall))) (and (eq? (car sig) 'where) (valid-func-sig? paren (cadr sig)))))) diff --git a/test/syntax.jl b/test/syntax.jl index c9a5d5dd52a41..b409dba5335f1 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -4050,3 +4050,34 @@ end import ..Base using .Base: zero, zero end + +# PR# 55040 - Macrocall as function sig +@test :(function @f()() end) == :(function (@f)() end) + +function callme end +macro callmemacro(args...) + Expr(:call, esc(:callme), map(esc, args)...) +end +function @callmemacro(a::Int) + return 1 +end +@callmemacro(b::Float64) = 2 +function @callmemacro(a::T, b::T) where T <: Int + return 3 +end +function @callmemacro(a::Int, b::Int, c::Int)::Float64 + return 4 +end +function @callmemacro(d::String) + (a, b, c) + # ^ Should not be accidentally parsed as an argument list + return 4 +end + +@test callme(1) === 1 +@test callme(2.0) === 2 +@test callme(3, 3) === 3 +@test callme(4, 4, 4) === 4.0 + +# Ambiguous 1-arg anymous vs macrosig +@test_parseerror "function (@foo(a)) end"