Skip to content

Commit

Permalink
Add the @public macro (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
DilumAluthge authored Jan 20, 2024
1 parent 6f7801a commit 0661d0a
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Manifest.toml
10 changes: 10 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,13 @@ name = "Public"
uuid = "431bcebd-1456-4ced-9d72-93c2757fff0b"
authors = ["The Julia Project", "contributors"]
version = "1.0.0-DEV"

[compat]
Test = "<0.0.1, 1"
julia = "1"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test"]
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Public.jl

## Example usage

```julia
module HelloWorld

using Public: @public

@public f

function f()
return "hello"
end

function g()
return "world"
end

end # module
```
82 changes: 82 additions & 0 deletions src/Public.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
module Public

# Based on Compat.jl
# License: MIT
# https://github.com/JuliaLang/Compat.jl
# https://github.com/JuliaLang/Compat.jl/blob/master/src/compatmacro.jl
# https://github.com/JuliaLang/Compat.jl/blob/a62d95906f3c16c9fa0fe8369a6613dcfd2a4659/src/compatmacro.jl

# https://github.com/JuliaLang/julia/pull/50105
@static if Base.VERSION >= v"1.11.0-DEV.469"
macro public(symbols_expr::Union{Symbol, Expr})
symbols = _get_symbols(symbols_expr)
return esc(Expr(:public, symbols...))
end
else
macro public(symbols_expr::Union{Symbol, Expr})
return nothing
end
end

_get_symbols(sym::Symbol) = [sym]

function _get_symbols(expr::Expr)
# `expr` must either be a "valid macro expression" or a tuple expression.

# If `expr` is a "valid macro expression", then we simply return it:
if _is_valid_macro_expr(expr::Expr)
return [expr.args[1]]
end

# If `expr` is not a "valid macro expression", then we check to make sure
# that it is a tuple expression:
if expr.head != :tuple
msg = """
Invalid expression head `$(expr.head)` in expression `$(expr)`.
Try `@public foo, bar, @hello, @world`
"""
throw(ArgumentError(msg))
end

# Now that we know that `expr` is a tuple expression, we iterate over
# each element of the tuple
num_symbols = length(expr.args)
symbols = Vector{Symbol}(undef, num_symbols)
for (i, arg) in enumerate(expr.args)
end
return symbols
end

# Return true if and only if `expr` is a valid macro call with no arguments.
#
# Example of "good" input: `@foo`
function _is_valid_macro_expr(expr::Expr)
# `expr` must be a `:macrocall` expression:
Meta.isexpr(expr, :macrocall) || return false

# `expr` must have exactly two arguments:
(length(expr.args) == 2) || return false

# The first argument must be a Symbol:
(expr.args[1] isa Symbol) || return false

# The first argument must begin with `@`
arg1_str = string(expr.args[1])
(arg1_str[1] == '@') || return false

# The first argument must have length >= 2
# (because otherwise the first argument would just be `@`, which doesn't
# make sense)
(length(arg1_str) >= 2) || return false

# The second argument must be a `LineNumberNode`
(expr.args[2] isa LineNumberNode) || return false

return true
end

# TODO: figure out if we actually want to `export`,
# or if we just want to mark as public.
export @public

end # module Public
55 changes: 55 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import Public
import Test

using Test: @testset
using Test: @test

@testset "Public.jl package" begin
@testset "_is_valid_macro_expr" begin
good_exprs = [
:(@hello),
Meta.parse("@hello"),
]
bad_exprs = [
Meta.parse("@foo bar"),
Meta.parse("@foo(bar)"),
]
for expr in good_exprs
@testset let context = (; expr)
@test Public._is_valid_macro_expr(expr)
end

end
for expr in bad_exprs
@testset let context = (; expr)
@test !Public._is_valid_macro_expr(expr)
end
end
end
end

module TestModule1

using Public: @public

export f
@public g

function f end
function g end
function h end

end # module TestModule1

@test Base.isexported(TestModule1, :f)
@test !Base.isexported(TestModule1, :g)
@test !Base.isexported(TestModule1, :h)

# @test Base.ispublic(TestModule1, :f)
# @test !Base.ispublic(TestModule1, :h)

@static if Base.VERSION >= v"1.11.0-DEV.469"
# @test Base.ispublic(TestModule1, :g)
else
# @test !Base.ispublic(TestModule1, :g)
end

0 comments on commit 0661d0a

Please sign in to comment.