Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the @public macro #1

Merged
merged 2 commits into from
Jan 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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