diff --git a/base/docs/basedocs.jl b/base/docs/basedocs.jl index 9b6b1f1bcc15c..cefdd75c494b8 100644 --- a/base/docs/basedocs.jl +++ b/base/docs/basedocs.jl @@ -52,13 +52,23 @@ kw"import" """ export -`export` is used within modules to tell Julia which functions should be +`export` is used within modules to tell Julia which names should be made available to the user. For example: `export foo` makes the name `foo` available when [`using`](@ref) the module. See the [manual section about modules](@ref modules) for details. """ kw"export" +""" + public + +`public` is used within modules to tell Julia which names are part of the +public API of the module . For example: `public foo` indicates that the name +`foo` is public, without making it available available when [`using`](@ref) +the module. See the [manual section about modules](@ref modules) for details. +""" +kw"public" + """ as diff --git a/base/exports.jl b/base/exports.jl index 0959fa1c391e2..bc22c95e1a919 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -1063,3 +1063,84 @@ export @view, @views, @static + +# TODO: use normal syntax once JuliaSyntax.jl becomes available at this point in bootstrapping +eval(Expr(:public, +# Modules + :Checked, + :Filesystem, + :Order, + :Sort, + +# Types + :AbstractLock, + :AsyncCondition, + :CodeUnits, + :Event, + :Fix1, + :Fix2, + :Generator, + :ImmutableDict, + :OneTo, + :UUID, + +# Semaphores + :Semaphore, + :acquire, + :release, + +# collections + :IteratorEltype, + :IteratorSize, + :to_index, + :vect, + :isdone, + :front, + :rest, + :split_rest, + :tail, + :checked_length, + +# Loading + :DL_LOAD_PATH, + :load_path, + :active_project, + +# Reflection and introspection + :isambiguous, + :isexpr, + :isidentifier, + :issingletontype, + :identify_package, + :locate_package, + :moduleroot, + :jit_total_bytes, + :summarysize, + :isexported, + :ispublic, + +# Opperators + :operator_associativity, + :operator_precedence, + :isbinaryoperator, + :isoperator, + :isunaryoperator, + +# C interface + :cconvert, + :unsafe_convert, + +# Error handling + :exit_on_sigint, + :windowserror, + +# Macros + Symbol("@assume_effects"), + Symbol("@constprop"), + Symbol("@locals"), + Symbol("@propagate_inbounds"), + +# misc + :notnothing, + :runtests, + :text_colors)) diff --git a/base/reflection.jl b/base/reflection.jl index 242d628161928..3af30b396dc72 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -75,24 +75,100 @@ end """ names(x::Module; all::Bool = false, imported::Bool = false) -Get an array of the names exported by a `Module`, excluding deprecated names. -If `all` is true, then the list also includes non-exported names defined in the module, +Get an array of the public names of a `Module`, excluding deprecated names. +If `all` is true, then the list also includes non-public names defined in the module, deprecated names, and compiler-generated names. If `imported` is true, then names explicitly imported from other modules are also included. -As a special case, all names defined in `Main` are considered \"exported\", -since it is not idiomatic to explicitly export names from `Main`. +As a special case, all names defined in `Main` are considered \"public\", +since it is not idiomatic to explicitly mark names from `Main` as public. -See also: [`@locals`](@ref Base.@locals), [`@__MODULE__`](@ref). +See also: [`isexported`](@ref), [`ispublic`](@ref), [`@locals`](@ref Base.@locals), [`@__MODULE__`](@ref). """ names(m::Module; all::Bool = false, imported::Bool = false) = sort!(unsorted_names(m; all, imported)) unsorted_names(m::Module; all::Bool = false, imported::Bool = false) = ccall(:jl_module_names, Array{Symbol,1}, (Any, Cint, Cint), m, all, imported) +""" + isexported(m::Module, s::Symbol) -> Bool + +Returns whether a symbol is exported from a module. + +See also: [`ispublic`](@ref), [`names`](@ref) + +```jldoctest +julia> module Mod + export foo + public bar + end +Mod + +julia> Base.isexported(Mod, :foo) +true + +julia> Base.isexported(Mod, :bar) +false + +julia> Base.isexported(Mod, :baz) +false +``` +""" isexported(m::Module, s::Symbol) = ccall(:jl_module_exports_p, Cint, (Any, Any), m, s) != 0 + +""" + ispublic(m::Module, s::Symbol) -> Bool + +Returns whether a symbol is marked as public in a module. + +Exported symbols are considered public. + +See also: [`isexported`](@ref), [`names`](@ref) + +```jldoctest +julia> module Mod + export foo + public bar + end +Mod + +julia> Base.ispublic(Mod, :foo) +true + +julia> Base.ispublic(Mod, :bar) +true + +julia> Base.ispublic(Mod, :baz) +false +``` +""" +ispublic(m::Module, s::Symbol) = ccall(:jl_module_public_p, Cint, (Any, Any), m, s) != 0 + +# TODO: this is vaguely broken because it only works for explicit calls to +# `Base.deprecate`, not the @deprecated macro: isdeprecated(m::Module, s::Symbol) = ccall(:jl_is_binding_deprecated, Cint, (Any, Any), m, s) != 0 + +""" + isbindingresolved(m::Module, s::Symbol) -> Bool + +Returns whether the binding of a symbol in a module is resolved. + +See also: [`isexported`](@ref), [`ispublic`](@ref), [`isdeprecated`](@ref) + +```jldoctest +julia> module Mod + foo() = 17 + end +Mod + +julia> Base.isbindingresolved(Mod, :foo) +true + +julia> Base.isbindingresolved(Mod, :bar) +false +``` +""" isbindingresolved(m::Module, var::Symbol) = ccall(:jl_binding_resolved_p, Cint, (Any, Any), m, var) != 0 function binding_module(m::Module, s::Symbol) diff --git a/base/show.jl b/base/show.jl index b887352f62c7d..fcf5e87900dbd 100644 --- a/base/show.jl +++ b/base/show.jl @@ -2226,7 +2226,7 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In print(io, head, ' ') show_list(io, args, ", ", indent, 0, quote_level) - elseif head === :export + elseif head in (:export, :public) print(io, head, ' ') show_list(io, mapany(allow_macroname, args), ", ", indent) diff --git a/deps/JuliaSyntax.version b/deps/JuliaSyntax.version index e726eea3656fc..1d7dfd8efd096 100644 --- a/deps/JuliaSyntax.version +++ b/deps/JuliaSyntax.version @@ -1,4 +1,4 @@ JULIASYNTAX_BRANCH = main -JULIASYNTAX_SHA1 = 045d156c44dbb87769c7416d049a7c08908539d4 +JULIASYNTAX_SHA1 = a9110fa8ecbe79943bb9525b4ccd99a3976cfcb7 JULIASYNTAX_GIT_URL := https://github.com/JuliaLang/JuliaSyntax.jl.git JULIASYNTAX_TAR_URL = https://api.github.com/repos/JuliaLang/JuliaSyntax.jl/tarball/$1 diff --git a/deps/checksums/JuliaSyntax-045d156c44dbb87769c7416d049a7c08908539d4.tar.gz/md5 b/deps/checksums/JuliaSyntax-045d156c44dbb87769c7416d049a7c08908539d4.tar.gz/md5 deleted file mode 100644 index 387ff43532a6e..0000000000000 --- a/deps/checksums/JuliaSyntax-045d156c44dbb87769c7416d049a7c08908539d4.tar.gz/md5 +++ /dev/null @@ -1 +0,0 @@ -ac6d344a55ec798abd2b4fb68304794e diff --git a/deps/checksums/JuliaSyntax-045d156c44dbb87769c7416d049a7c08908539d4.tar.gz/sha512 b/deps/checksums/JuliaSyntax-045d156c44dbb87769c7416d049a7c08908539d4.tar.gz/sha512 deleted file mode 100644 index a43f4dfd4bcae..0000000000000 --- a/deps/checksums/JuliaSyntax-045d156c44dbb87769c7416d049a7c08908539d4.tar.gz/sha512 +++ /dev/null @@ -1 +0,0 @@ -89cf036193135337ae722e05dccbcd6535d2fa54316ae801a8c764f5f4eaf90fa95a644d024dbefb09433781043dfb4830a1679a049e47b7b35e9acd1e834b90 diff --git a/deps/checksums/JuliaSyntax-a9110fa8ecbe79943bb9525b4ccd99a3976cfcb7.tar.gz/md5 b/deps/checksums/JuliaSyntax-a9110fa8ecbe79943bb9525b4ccd99a3976cfcb7.tar.gz/md5 new file mode 100644 index 0000000000000..25f5adf71897d --- /dev/null +++ b/deps/checksums/JuliaSyntax-a9110fa8ecbe79943bb9525b4ccd99a3976cfcb7.tar.gz/md5 @@ -0,0 +1 @@ +a42bbb42babbbd727556f6bc01455826 diff --git a/deps/checksums/JuliaSyntax-a9110fa8ecbe79943bb9525b4ccd99a3976cfcb7.tar.gz/sha512 b/deps/checksums/JuliaSyntax-a9110fa8ecbe79943bb9525b4ccd99a3976cfcb7.tar.gz/sha512 new file mode 100644 index 0000000000000..cd3f3f3043d45 --- /dev/null +++ b/deps/checksums/JuliaSyntax-a9110fa8ecbe79943bb9525b4ccd99a3976cfcb7.tar.gz/sha512 @@ -0,0 +1 @@ +d542766e72b57418b9b4e17743f89d8535c1f36497346b57538bd0cb451e64af9493015692179f80ec4ee8cf18349c1e0888f5710db895e19f9bb0322f0f7464 diff --git a/doc/src/base/base.md b/doc/src/base/base.md index a4f90ba6c6d0a..39e61738bf1ed 100644 --- a/doc/src/base/base.md +++ b/doc/src/base/base.md @@ -60,6 +60,7 @@ However, you can create variables with names: Finally: `where` is parsed as an infix operator for writing parametric method and type definitions; `in` and `isa` are parsed as infix operators; +`public` is parsed as a keyword when beginning a toplevel statement; `outer` is parsed as a keyword when used to modify the scope of a variable in an iteration specification of a `for` loop; and `as` is used as a keyword to rename an identifier brought into scope by `import` or `using`. Creation of variables named `where`, `in`, `isa`, `outer` and `as` is allowed, though. @@ -67,6 +68,7 @@ Creation of variables named `where`, `in`, `isa`, `outer` and `as` is allowed, t ```@docs module export +public import using as @@ -451,6 +453,8 @@ Base.@__DIR__ Base.@__LINE__ Base.fullname Base.names +Base.isexported +Base.ispublic Base.nameof(::Function) Base.functionloc(::Any, ::Any) Base.functionloc(::Method) diff --git a/doc/src/base/reflection.md b/doc/src/base/reflection.md index e9da82475fd68..b6246c06472a4 100644 --- a/doc/src/base/reflection.md +++ b/doc/src/base/reflection.md @@ -4,9 +4,9 @@ Julia provides a variety of runtime reflection capabilities. ## Module bindings -The exported names for a `Module` are available using [`names(m::Module)`](@ref), which will return -an array of [`Symbol`](@ref) elements representing the exported bindings. `names(m::Module, all = true)` -returns symbols for all bindings in `m`, regardless of export status. +The public names for a `Module` are available using [`names(m::Module)`](@ref), which will return +an array of [`Symbol`](@ref) elements representing the public bindings. `names(m::Module, all = true)` +returns symbols for all bindings in `m`, regardless of public status. ## DataType fields diff --git a/doc/src/manual/faq.md b/doc/src/manual/faq.md index bdecb5ecf106f..d9199638182e8 100644 --- a/doc/src/manual/faq.md +++ b/doc/src/manual/faq.md @@ -22,11 +22,28 @@ On the other hand, language *interoperability* is extremely useful: we want to e ### How does Julia define its public API? -Julia `Base` and standard library functionality described in the -[the documentation](https://docs.julialang.org/) that is not marked as unstable -(e.g. experimental and internal) is covered by [SemVer](https://semver.org/). -Functions, types, and constants are not part of the public API if they are not -included in the documentation, _even if they have docstrings_. +Julia's public [API](https://en.wikipedia.org/wiki/API) is the behavior described in +documentation of public symbols from `Base` and the standard libraries. Functions, +types, and constants are not part of the public API if they are not public, even if +they have docstrings or are described in the documentation. Further, only the documented +behavior of public symbols is part of the public API. Undocumented behavior of public +symbols is internal. + +Public symbols are those marked with either `public foo` or `export foo`. + +In other words: + +- Documented behavior of public symbols is part of the public API. +- Undocumented behavior of public symbols is not part of the public API. +- Documented behavior of private symbols is not part of the public API. +- Undocumented behavior of private symbols is not part of the public API. + +You can get a complete list of the public symbols from a module with `names(MyModule)`. + +Package authors are encouraged to define their public API similarly. + +Anything in Julia's Public API is covered by [SemVer](https://semver.org/) and therefore +will not be removed or receive meaningful breaking changes before Julia 2.0. ### There is a useful undocumented function/type/constant. Can I use it? diff --git a/doc/src/manual/modules.md b/doc/src/manual/modules.md index 4ffb1bca26e50..7c9a743deb35d 100644 --- a/doc/src/manual/modules.md +++ b/doc/src/manual/modules.md @@ -7,7 +7,8 @@ Modules in Julia help organize code into coherent units. They are delimited synt allows the same name to be used for different functions or global variables without conflict, as long as they are in separate modules. 2. Modules have facilities for detailed namespace management: each defines a set of names it - `export`s, and can import names from other modules with `using` and `import` (we explain these below). + `export`s and marks as `public`, and can import names from other modules with `using` and + `import` (we explain these below). 3. Modules can be precompiled for faster loading, and may contain code for runtime initialization. @@ -16,7 +17,7 @@ Typically, in larger Julia packages you will see module code organized into file ```julia module SomeModule -# export, using, import statements are usually here; we discuss these below +# export, public, using, import statements are usually here; we discuss these below include("file1.jl") include("file2.jl") @@ -103,6 +104,10 @@ Also, some modules don't export names at all. This is usually done if they use c words, such as `derivative`, in their API, which could easily clash with the export lists of other modules. We will see how to manage name clashes below. +To mark a name as public without exporting it into the namespace of folks who call `using NiceStuff`, +one can use `public` instead of `export`. This marks the public name(s) as part of the public API, +but does not have any namespace implications. + ### Standalone `using` and `import` Possibly the most common way of loading a module is `using ModuleName`. This [loads](@ref diff --git a/doc/src/manual/noteworthy-differences.md b/doc/src/manual/noteworthy-differences.md index 7a2bb0e9ace03..6c55bee59cadf 100644 --- a/doc/src/manual/noteworthy-differences.md +++ b/doc/src/manual/noteworthy-differences.md @@ -413,15 +413,16 @@ For users coming to Julia from R, these are some noteworthy differences: file are `include`d only once (No `#ifdef` confusion). ### Julia ⇔ C/C++: Module interface - * C++ exposes interfaces using "public" `.h`/`.hpp` files whereas Julia `module`s `export` - symbols that are intended for their users. + * C++ exposes interfaces using "public" `.h`/`.hpp` files whereas Julia `module`s mark + specific symbols that are intended for their users as `public`or `export`ed. * Often, Julia `module`s simply add functionality by generating new "methods" to existing functions (ex: `Base.push!`). * Developers of Julia packages therefore cannot rely on header files for interface documentation. * Interfaces for Julia packages are typically described using docstrings, README.md, static web pages, ... - * Some developers choose not to `export` all symbols required to use their package/module. + * Some developers choose not to `export` all symbols required to use their package/module, + but should still mark unexported user facing symbols as `public`. * Users might be expected to access these components by qualifying functions/structs/... with the package/module name (ex: `MyModule.run_this_task(...)`). diff --git a/src/ast.c b/src/ast.c index 06727b453d6a3..600b1a229ea80 100644 --- a/src/ast.c +++ b/src/ast.c @@ -28,6 +28,7 @@ JL_DLLEXPORT jl_sym_t *jl_top_sym; JL_DLLEXPORT jl_sym_t *jl_module_sym; JL_DLLEXPORT jl_sym_t *jl_slot_sym; JL_DLLEXPORT jl_sym_t *jl_export_sym; +JL_DLLEXPORT jl_sym_t *jl_public_sym; JL_DLLEXPORT jl_sym_t *jl_import_sym; JL_DLLEXPORT jl_sym_t *jl_toplevel_sym; JL_DLLEXPORT jl_sym_t *jl_quote_sym; @@ -304,6 +305,7 @@ void jl_init_common_symbols(void) jl_lambda_sym = jl_symbol("lambda"); jl_module_sym = jl_symbol("module"); jl_export_sym = jl_symbol("export"); + jl_public_sym = jl_symbol("public"); jl_import_sym = jl_symbol("import"); jl_using_sym = jl_symbol("using"); jl_assign_sym = jl_symbol("="); diff --git a/src/builtins.c b/src/builtins.c index d0d4b3bf1dbef..2defeb4b3d833 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -1939,7 +1939,7 @@ static void add_intrinsic(jl_module_t *inm, const char *name, enum intrinsic f) jl_value_t *i = jl_permbox32(jl_intrinsic_type, 0, (int32_t)f); jl_sym_t *sym = jl_symbol(name); jl_set_const(inm, sym, i); - jl_module_export(inm, sym); + jl_module_public(inm, sym, 1); } void jl_init_intrinsic_properties(void) JL_GC_DISABLED diff --git a/src/jl_exported_funcs.inc b/src/jl_exported_funcs.inc index a336eb5d6d2f2..c429ec59d48b4 100644 --- a/src/jl_exported_funcs.inc +++ b/src/jl_exported_funcs.inc @@ -329,13 +329,14 @@ XX(jl_methtable_lookup) \ XX(jl_mi_cache_insert) \ XX(jl_module_build_id) \ - XX(jl_module_export) \ XX(jl_module_exports_p) \ XX(jl_module_globalref) \ XX(jl_module_import) \ XX(jl_module_name) \ XX(jl_module_names) \ XX(jl_module_parent) \ + XX(jl_module_public) \ + XX(jl_module_public_p) \ XX(jl_module_use) \ XX(jl_module_using) \ XX(jl_module_usings) \ diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 82f0f91271bb1..a808a915be6bc 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -2364,7 +2364,7 @@ `(= ,lhs ,rhs))) (define (expand-forms e) - (if (or (atom? e) (memq (car e) '(quote inert top core globalref outerref module toplevel ssavalue null true false meta using import export thismodule toplevel-only))) + (if (or (atom? e) (memq (car e) '(quote inert top core globalref outerref module toplevel ssavalue null true false meta using import export public thismodule toplevel-only))) e (let ((ex (get expand-table (car e) #f))) (if ex @@ -3708,7 +3708,7 @@ f(x) = yt(x) thunk with-static-parameters toplevel-only global globalref outerref const-if-global thismodule const atomic null true false ssavalue isdefined toplevel module lambda - error gc_preserve_begin gc_preserve_end import using export inline noinline))) + error gc_preserve_begin gc_preserve_end import using export public inline noinline))) (define (local-in? s lam (tab #f)) (or (and tab (has? tab s)) @@ -4834,7 +4834,7 @@ f(x) = yt(x) val)) ;; other top level expressions - ((import using export) + ((import using export public) (check-top-level e) (emit e) (let ((have-ret? (and (pair? code) (pair? (car code)) (eq? (caar code) 'return)))) diff --git a/src/julia.h b/src/julia.h index 92c383ccc5525..84a749b4e08ea 100644 --- a/src/julia.h +++ b/src/julia.h @@ -595,11 +595,12 @@ typedef struct _jl_binding_t { _Atomic(struct _jl_binding_t*) owner; // for individual imported bindings (NULL until 'resolved') _Atomic(jl_value_t*) ty; // binding type uint8_t constp:1; - uint8_t exportp:1; + uint8_t exportp:1; // `public foo` sets `publicp`, `export foo` sets both `publicp` and `exportp` + uint8_t publicp:1; // exportp without publicp is not allowed. uint8_t imported:1; uint8_t usingfailed:1; uint8_t deprecated:2; // 0=not deprecated, 1=renamed, 2=moved to another package - uint8_t padding:2; + uint8_t padding:1; } jl_binding_t; typedef struct { @@ -1763,7 +1764,7 @@ JL_DLLEXPORT void jl_module_use(jl_module_t *to, jl_module_t *from, jl_sym_t *s) JL_DLLEXPORT void jl_module_use_as(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_sym_t *asname); JL_DLLEXPORT void jl_module_import(jl_module_t *to, jl_module_t *from, jl_sym_t *s); JL_DLLEXPORT void jl_module_import_as(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_sym_t *asname); -JL_DLLEXPORT void jl_module_export(jl_module_t *from, jl_sym_t *s); +JL_DLLEXPORT void jl_module_public(jl_module_t *from, jl_sym_t *s, int exported); JL_DLLEXPORT int jl_is_imported(jl_module_t *m, jl_sym_t *s); JL_DLLEXPORT int jl_module_exports_p(jl_module_t *m, jl_sym_t *var); JL_DLLEXPORT void jl_add_standard_imports(jl_module_t *m); diff --git a/src/julia_internal.h b/src/julia_internal.h index b2ba10d7bd084..c1006fa711a18 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -1513,6 +1513,7 @@ extern JL_DLLEXPORT jl_sym_t *jl_top_sym; extern JL_DLLEXPORT jl_sym_t *jl_module_sym; extern JL_DLLEXPORT jl_sym_t *jl_slot_sym; extern JL_DLLEXPORT jl_sym_t *jl_export_sym; +extern JL_DLLEXPORT jl_sym_t *jl_public_sym; extern JL_DLLEXPORT jl_sym_t *jl_import_sym; extern JL_DLLEXPORT jl_sym_t *jl_toplevel_sym; extern JL_DLLEXPORT jl_sym_t *jl_quote_sym; diff --git a/src/module.c b/src/module.c index 59bd308d99a41..4cda3479adba1 100644 --- a/src/module.c +++ b/src/module.c @@ -49,7 +49,7 @@ JL_DLLEXPORT jl_module_t *jl_new_module_(jl_sym_t *name, jl_module_t *parent, ui if (default_names) { jl_set_const(m, name, (jl_value_t*)m); } - jl_module_export(m, name); + jl_module_public(m, name, 1); JL_GC_POP(); return m; } @@ -180,6 +180,7 @@ static jl_binding_t *new_binding(jl_module_t *mod, jl_sym_t *name) b->globalref = NULL; b->constp = 0; b->exportp = 0; + b->publicp = 0; b->imported = 0; b->deprecated = 0; b->usingfailed = 0; @@ -670,10 +671,11 @@ JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from) } } -JL_DLLEXPORT void jl_module_export(jl_module_t *from, jl_sym_t *s) +JL_DLLEXPORT void jl_module_public(jl_module_t *from, jl_sym_t *s, int exported) { jl_binding_t *b = jl_get_module_binding(from, s, 1); - b->exportp = 1; + b->publicp = 1; + b->exportp = exported; } JL_DLLEXPORT int jl_boundp(jl_module_t *m, jl_sym_t *var) @@ -694,6 +696,12 @@ JL_DLLEXPORT int jl_module_exports_p(jl_module_t *m, jl_sym_t *var) return b && b->exportp; } +JL_DLLEXPORT int jl_module_public_p(jl_module_t *m, jl_sym_t *var) +{ + jl_binding_t *b = jl_get_module_binding(m, var, 0); + return b && b->publicp; +} + JL_DLLEXPORT int jl_binding_resolved_p(jl_module_t *m, jl_sym_t *var) { jl_binding_t *b = jl_get_module_binding(m, var, 0); @@ -945,7 +953,7 @@ JL_DLLEXPORT jl_value_t *jl_module_names(jl_module_t *m, int all, int imported) break; jl_sym_t *asname = b->globalref->name; int hidden = jl_symbol_name(asname)[0]=='#'; - if ((b->exportp || + if ((b->publicp || (imported && b->imported) || (jl_atomic_load_relaxed(&b->owner) == b && !b->imported && (all || m == jl_main_module))) && (all || (!b->deprecated && !hidden))) { diff --git a/src/toplevel.c b/src/toplevel.c index cb2a0d789157a..ca2033e58727d 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -560,6 +560,7 @@ int jl_is_toplevel_only_expr(jl_value_t *e) JL_NOTSAFEPOINT ((jl_expr_t*)e)->head == jl_import_sym || ((jl_expr_t*)e)->head == jl_using_sym || ((jl_expr_t*)e)->head == jl_export_sym || + ((jl_expr_t*)e)->head == jl_public_sym || ((jl_expr_t*)e)->head == jl_thunk_sym || ((jl_expr_t*)e)->head == jl_global_sym || ((jl_expr_t*)e)->head == jl_const_sym || @@ -575,8 +576,9 @@ int jl_needs_lowering(jl_value_t *e) JL_NOTSAFEPOINT jl_expr_t *ex = (jl_expr_t*)e; jl_sym_t *head = ex->head; if (head == jl_module_sym || head == jl_import_sym || head == jl_using_sym || - head == jl_export_sym || head == jl_thunk_sym || head == jl_toplevel_sym || - head == jl_error_sym || head == jl_incomplete_sym || head == jl_method_sym) { + head == jl_export_sym || head == jl_public_sym || head == jl_thunk_sym || + head == jl_toplevel_sym || head == jl_error_sym || head == jl_incomplete_sym || + head == jl_method_sym) { return 0; } if (head == jl_global_sym || head == jl_const_sym) { @@ -837,12 +839,14 @@ jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_value_t *e, int JL_GC_POP(); return jl_nothing; } - else if (head == jl_export_sym) { + else if (head == jl_export_sym || head == jl_public_sym) { + int exp = (head == jl_export_sym); for (size_t i = 0; i < jl_array_len(ex->args); i++) { jl_sym_t *name = (jl_sym_t*)jl_array_ptr_ref(ex->args, i); if (!jl_is_symbol(name)) - jl_eval_errorf(m, "syntax: malformed \"export\" statement"); - jl_module_export(m, name); + jl_eval_errorf(m, exp ? "syntax: malformed \"export\" statement" : + "syntax: malformed \"public\" statement"); + jl_module_public(m, name, exp); } JL_GC_POP(); return jl_nothing; diff --git a/stdlib/InteractiveUtils/src/InteractiveUtils.jl b/stdlib/InteractiveUtils/src/InteractiveUtils.jl index 82c0752d3f78d..b63b3d905d605 100644 --- a/stdlib/InteractiveUtils/src/InteractiveUtils.jl +++ b/stdlib/InteractiveUtils/src/InteractiveUtils.jl @@ -23,12 +23,12 @@ include("clipboard.jl") """ varinfo(m::Module=Main, pattern::Regex=r""; all=false, imported=false, recursive=false, sortby::Symbol=:name, minsize::Int=0) -Return a markdown table giving information about exported global variables in a module, optionally restricted +Return a markdown table giving information about public global variables in a module, optionally restricted to those matching `pattern`. The memory consumption estimate is an approximate lower bound on the size of the internal structure of the object. -- `all` : also list non-exported objects defined in the module, deprecated objects, and compiler-generated objects. +- `all` : also list non-public objects defined in the module, deprecated objects, and compiler-generated objects. - `imported` : also list objects explicitly imported from other modules. - `recursive` : recursively include objects in sub-modules, observing the same settings in each. - `sortby` : the column to sort results by. Options are `:name` (default), `:size`, and `:summary`. diff --git a/stdlib/REPL/src/docview.jl b/stdlib/REPL/src/docview.jl index 0122aa41abb4a..9b35765c0c511 100644 --- a/stdlib/REPL/src/docview.jl +++ b/stdlib/REPL/src/docview.jl @@ -20,14 +20,20 @@ using Unicode: normalize ## Help mode ## # This is split into helpmode and _helpmode to easier unittest _helpmode -helpmode(io::IO, line::AbstractString, mod::Module=Main) = :($REPL.insert_hlines($(REPL._helpmode(io, line, mod)))) +function helpmode(io::IO, line::AbstractString, mod::Module=Main) + internal_accesses = Set{Pair{Module,Symbol}}() + quote + docs = $REPL.insert_hlines($(REPL._helpmode(io, line, mod, internal_accesses))) + $REPL.insert_internal_warning(docs, $internal_accesses) + end +end helpmode(line::AbstractString, mod::Module=Main) = helpmode(stdout, line, mod) # A hack to make the line entered at the REPL available at trimdocs without # passing the string through the entire mechanism. const extended_help_on = Ref{Any}(nothing) -function _helpmode(io::IO, line::AbstractString, mod::Module=Main) +function _helpmode(io::IO, line::AbstractString, mod::Module=Main, internal_accesses::Union{Nothing, Set{Pair{Module,Symbol}}}=nothing) line = strip(line) ternary_operator_help = (line == "?" || line == "?:") if startswith(line, '?') && !ternary_operator_help @@ -66,7 +72,7 @@ function _helpmode(io::IO, line::AbstractString, mod::Module=Main) end # the following must call repl(io, expr) via the @repl macro # so that the resulting expressions are evaluated in the Base.Docs namespace - :($REPL.@repl $io $expr $brief $mod) + :($REPL.@repl $io $expr $brief $mod $internal_accesses) end _helpmode(line::AbstractString, mod::Module=Main) = _helpmode(stdout, line, mod) @@ -150,6 +156,47 @@ end _trimdocs(md, brief::Bool) = md, false + +is_tuple(expr) = false +is_tuple(expr::Expr) = expr.head == :tuple + +struct Logged{F} + f::F + mod::Module + collection::Set{Pair{Module,Symbol}} +end +function (la::Logged)(m::Module, s::Symbol) + m !== la.mod && !Base.ispublic(m, s) && push!(la.collection, m => s) + la.f(m, s) +end +(la::Logged)(args...) = la.f(args...) + +function log_nonpublic_access(expr::Expr, mod::Module, internal_access::Set{Pair{Module,Symbol}}) + if expr.head === :. && length(expr.args) == 2 && !is_tuple(expr.args[2]) + Expr(:call, Logged(getproperty, mod, internal_access), log_nonpublic_access.(expr.args, (mod,), (internal_access,))...) + elseif expr.head === :call && expr.args[1] === Base.Docs.Binding + Expr(:call, Logged(Base.Docs.Binding, mod, internal_access), log_nonpublic_access.(expr.args[2:end], (mod,), (internal_access,))...) + else + Expr(expr.head, log_nonpublic_access.(expr.args, (mod,), (internal_access,))...) + end +end +log_nonpublic_access(expr, ::Module, _) = expr + +function insert_internal_warning(md::Markdown.MD, internal_access::Set{Pair{Module,Symbol}}) + if !isempty(internal_access) + items = Any[Any[Markdown.Paragraph(Any[Markdown.Code("", s)])] for s in sort("$mod.$sym" for (mod, sym) in internal_access)] + admonition = Markdown.Admonition("warning", "Warning", Any[ + Markdown.Paragraph(Any["The following bindings may be internal; they may change or be removed in future versions:"]), + Markdown.List(items, -1, false)]) + pushfirst!(md.content, admonition) + end + md +end +function insert_internal_warning(other, internal_access::Set{Pair{Module,Symbol}}) + println("oops.") + other +end + """ Docs.doc(binding, sig) @@ -195,6 +242,8 @@ function doc(binding::Binding, sig::Type = Union{}) md = catdoc(mapany(parsedoc, results)...) # Save metadata in the generated markdown. if isa(md, Markdown.MD) + # We don't know how to insert an internal symbol warning into non-markdown + # content, so we don't. md.meta[:results] = results md.meta[:binding] = binding md.meta[:typesig] = sig @@ -252,7 +301,13 @@ function summarize(binding::Binding, sig) io = IOBuffer() if defined(binding) binding_res = resolve(binding) - !isa(binding_res, Module) && println(io, "No documentation found.\n") + if !isa(binding_res, Module) + if Base.ispublic(binding.mod, binding.var) + println(io, "No documentation found for public symbol.\n") + else + println(io, "No documentation found for private symbol.\n") + end + end summarize(io, binding_res, binding) else println(io, "No documentation found.\n") @@ -344,16 +399,17 @@ function find_readme(m::Module)::Union{String, Nothing} end function summarize(io::IO, m::Module, binding::Binding; nlines::Int = 200) readme_path = find_readme(m) + public = Base.ispublic(binding.mod, binding.var) ? "public" : "internal" if isnothing(readme_path) - println(io, "No docstring or readme file found for module `$m`.\n") + println(io, "No docstring or readme file found for $public module `$m`.\n") else - println(io, "No docstring found for module `$m`.") + println(io, "No docstring found for $public module `$m`.") end exports = filter!(!=(nameof(m)), names(m)) if isempty(exports) - println(io, "Module does not export any names.") + println(io, "Module does not have any public names.") else - println(io, "# Exported names") + println(io, "# Public names") print(io, " `") join(io, exports, "`, `") println(io, "`\n") @@ -474,9 +530,9 @@ end repl_latex(s::String) = repl_latex(stdout, s) macro repl(ex, brief::Bool=false, mod::Module=Main) repl(ex; brief, mod) end -macro repl(io, ex, brief, mod) repl(io, ex; brief, mod) end +macro repl(io, ex, brief, mod, internal_accesses) repl(io, ex; brief, mod, internal_accesses) end -function repl(io::IO, s::Symbol; brief::Bool=true, mod::Module=Main) +function repl(io::IO, s::Symbol; brief::Bool=true, mod::Module=Main, internal_accesses::Union{Nothing, Set{Pair{Module,Symbol}}}=nothing) str = string(s) quote repl_latex($io, $str) @@ -485,19 +541,19 @@ function repl(io::IO, s::Symbol; brief::Bool=true, mod::Module=Main) # n.b. we call isdefined for the side-effect of resolving the binding, if possible :(repl_corrections($io, $str, $mod)) end) - $(_repl(s, brief)) + $(_repl(s, brief, mod, internal_accesses)) end end isregex(x) = isexpr(x, :macrocall, 3) && x.args[1] === Symbol("@r_str") && !isempty(x.args[3]) -repl(io::IO, ex::Expr; brief::Bool=true, mod::Module=Main) = isregex(ex) ? :(apropos($io, $ex)) : _repl(ex, brief) -repl(io::IO, str::AbstractString; brief::Bool=true, mod::Module=Main) = :(apropos($io, $str)) -repl(io::IO, other; brief::Bool=true, mod::Module=Main) = esc(:(@doc $other)) +repl(io::IO, ex::Expr; brief::Bool=true, mod::Module=Main, internal_accesses::Union{Nothing, Set{Pair{Module,Symbol}}}=nothing) = isregex(ex) ? :(apropos($io, $ex)) : _repl(ex, brief, mod, internal_accesses) +repl(io::IO, str::AbstractString; brief::Bool=true, mod::Module=Main, internal_accesses::Union{Nothing, Set{Pair{Module,Symbol}}}=nothing) = :(apropos($io, $str)) +repl(io::IO, other; brief::Bool=true, mod::Module=Main, internal_accesses::Union{Nothing, Set{Pair{Module,Symbol}}}=nothing) = esc(:(@doc $other)) # TODO: track internal_accesses #repl(io::IO, other) = lookup_doc(other) # TODO repl(x; brief::Bool=true, mod::Module=Main) = repl(stdout, x; brief, mod) -function _repl(x, brief::Bool=true) +function _repl(x, brief::Bool=true, mod::Module=Main, internal_accesses::Union{Nothing, Set{Pair{Module,Symbol}}}=nothing) if isexpr(x, :call) x = x::Expr # determine the types of the values @@ -563,6 +619,7 @@ function _repl(x, brief::Bool=true) else docs end + docs = log_nonpublic_access(macroexpand(mod, docs), mod, internal_accesses) :(REPL.trimdocs($docs, $brief)) end diff --git a/stdlib/REPL/test/docview.jl b/stdlib/REPL/test/docview.jl index b606155f78dea..a8c7955b6952f 100644 --- a/stdlib/REPL/test/docview.jl +++ b/stdlib/REPL/test/docview.jl @@ -74,3 +74,47 @@ end b = REPL.Binding(@__MODULE__, :R) @test REPL.summarize(b, Tuple{}) isa Markdown.MD end + +module InternalWarningsTests + + module A + public B, B3 + module B + public e + c = 4 + "d is 5" + d = 5 + "e is 6" + e = 6 + end + + module B2 + module C + public e + d = 1 + "e is 2" + e = 2 + end + end + + module B3 end + end + + using Test, REPL + @testset "internal warnings" begin + header = "!!! warning\n The following bindings may be internal; they may change or be removed in future versions:\n\n" + prefix(warnings) = header * join(" * `$(@__MODULE__).$w`\n" for w in warnings) * "\n\n" + docstring(input) = string(eval(REPL.helpmode(input, @__MODULE__))) + + @test docstring("A") == "No docstring or readme file found for internal module `$(@__MODULE__).A`.\n\n# Public names\n\n`B`, `B3`\n" + @test docstring("A.B") == "No docstring or readme file found for public module `$(@__MODULE__).A.B`.\n\n# Public names\n\n`e`\n" + @test startswith(docstring("A.B.c"), prefix(["A.B.c"])) + @test startswith(docstring("A.B.d"), prefix(["A.B.d"])) + @test docstring("A.B.e") == "e is 6\n" + @test startswith(docstring("A.B2"), prefix(["A.B2"])) + @test startswith(docstring("A.B2.C"), prefix(["A.B2", "A.B2.C"])) + @test startswith(docstring("A.B2.C.d"), prefix(["A.B2", "A.B2.C", "A.B2.C.d"])) + @test startswith(docstring("A.B2.C.e"), prefix(["A.B2", "A.B2.C"])) + @test docstring("A.B3") == "No docstring or readme file found for public module `$(@__MODULE__).A.B3`.\n\nModule does not have any public names.\n" + end +end diff --git a/stdlib/REPL/test/repl.jl b/stdlib/REPL/test/repl.jl index 20f07864a275b..4081a3c7162e2 100644 --- a/stdlib/REPL/test/repl.jl +++ b/stdlib/REPL/test/repl.jl @@ -1164,7 +1164,7 @@ fake_repl() do stdin_write, stdout_read, repl Base.wait(repltask) end -help_result(line, mod::Module=Base) = Core.eval(mod, REPL._helpmode(IOBuffer(), line)) +help_result(line, mod::Module=Base) = Core.eval(mod, REPL._helpmode(IOBuffer(), line, mod)) # Docs.helpmode tests: we test whether the correct expressions are being generated here, # rather than complete integration with Julia's REPL mode system. @@ -1249,6 +1249,7 @@ let emptyH1 = Markdown.parse("# "), end module BriefExtended +public f, f_plain """ f() diff --git a/test/docs.jl b/test/docs.jl index 7f6ece4e76ab4..a2f556b7ee848 100644 --- a/test/docs.jl +++ b/test/docs.jl @@ -623,6 +623,7 @@ end let d = @doc(I15424.LazyHelp) @test repr("text/plain", d) == "LazyHelp\nLazyHelp(text)\n" + # (no internal warning is inserted for non-markdown content) end # Issue #13385. @@ -650,7 +651,7 @@ end let d = (@doc :@m2_11993), macro_doc = Markdown.parse("`$(curmod_prefix == "Main." ? "" : curmod_prefix)@m2_11993` is a macro.") @test docstring_startswith(d, doc""" - No documentation found. + No documentation found for private symbol. $macro_doc""") end @@ -849,9 +850,9 @@ undocumented(x,y) = 3 end # module doc_str = Markdown.parse(""" -No docstring or readme file found for module `$(curmod_prefix)Undocumented`. +No docstring or readme file found for internal module `$(curmod_prefix)Undocumented`. -# Exported names +# Public names `A`, `B`, `C`, `at0`, `pt2` """) @@ -865,7 +866,7 @@ Binding `$(curmod_prefix)Undocumented.bindingdoesnotexist` does not exist. @test docstrings_equal(@doc(Undocumented.bindingdoesnotexist), doc"$doc_str") doc_str = Markdown.parse(""" -No documentation found. +No documentation found for public symbol. # Summary ``` @@ -881,7 +882,7 @@ $(curmod_prefix)Undocumented.C @test docstrings_equal(@doc(Undocumented.A), doc"$doc_str") doc_str = Markdown.parse(""" -No documentation found. +No documentation found for public symbol. # Summary ``` @@ -901,7 +902,7 @@ $(curmod_prefix)Undocumented.B <: $(curmod_prefix)Undocumented.A <: Any @test docstrings_equal(@doc(Undocumented.B), doc"$doc_str") doc_str = Markdown.parse(""" -No documentation found. +No documentation found for public symbol. # Summary ``` @@ -916,7 +917,7 @@ $(curmod_prefix)Undocumented.C <: $(curmod_prefix)Undocumented.A <: Any @test docstrings_equal(@doc(Undocumented.C), doc"$doc_str") doc_str = Markdown.parse(""" -No documentation found. +No documentation found for private symbol. # Summary ``` @@ -938,7 +939,7 @@ $(curmod_prefix)Undocumented.D <: $(curmod_prefix)Undocumented.B <: $(curmod_pre @test docstrings_equal(@doc(Undocumented.D), doc"$doc_str") doc_str = Markdown.parse(""" -No documentation found. +No documentation found for public symbol. # Summary @@ -958,7 +959,7 @@ $(curmod_prefix)Undocumented.st4{T<:Number, N} @test docstrings_equal(@doc(Undocumented.at0), doc"$doc_str") doc_str = Markdown.parse(""" -No documentation found. +No documentation found for private symbol. # Summary @@ -981,7 +982,7 @@ $(curmod_prefix)Undocumented.at1{T>:Integer, N} <: $(curmod_prefix)Undocumented. @test docstrings_equal(@doc(Undocumented.at1), doc"$doc_str") doc_str = Markdown.parse(""" -No documentation found. +No documentation found for private symbol. # Summary @@ -1000,7 +1001,7 @@ $(curmod_prefix)Undocumented.st4{Int64, N} @test docstrings_equal(@doc(Undocumented.at_), doc"$doc_str") doc_str = Markdown.parse(""" -No documentation found. +No documentation found for public symbol. # Summary @@ -1017,7 +1018,7 @@ $(curmod_prefix)Undocumented.pt2{T<:Number, N, A>:Integer} <: $(curmod_prefix)Un @test docstrings_equal(@doc(Undocumented.pt2), doc"$doc_str") doc_str = Markdown.parse(""" -No documentation found. +No documentation found for private symbol. # Summary @@ -1040,7 +1041,7 @@ $(curmod_prefix)Undocumented.st3{T<:Integer, N} <: $(curmod_prefix)Undocumented. @test docstrings_equal(@doc(Undocumented.st3), doc"$doc_str") doc_str = Markdown.parse(""" -No documentation found. +No documentation found for private symbol. # Summary @@ -1062,7 +1063,7 @@ $(curmod_prefix)Undocumented.st4{T, N} <: $(curmod_prefix)Undocumented.at0{T, N} @test docstrings_equal(@doc(Undocumented.st4), doc"$doc_str") doc_str = Markdown.parse(""" -No documentation found. +No documentation found for private symbol. # Summary @@ -1083,7 +1084,7 @@ $(curmod_prefix)Undocumented.st5{T>:Int64, N} <: $(curmod_prefix)Undocumented.at @test docstrings_equal(@doc(Undocumented.st5), doc"$doc_str") doc_str = Markdown.parse(""" -No documentation found. +No documentation found for private symbol. # Summary @@ -1104,7 +1105,7 @@ $(curmod_prefix)Undocumented.mt6{T<:Integer, N} <: $(curmod_prefix)Undocumented. @test docstrings_equal(@doc(Undocumented.mt6), doc"$doc_str") doc_str = Markdown.parse(""" -No documentation found. +No documentation found for private symbol. # Summary @@ -1118,7 +1119,7 @@ No documentation found. @test docstrings_equal(@doc(Undocumented.ut7), doc"$doc_str") doc_str = Markdown.parse(""" -No documentation found. +No documentation found for private symbol. # Summary @@ -1134,7 +1135,7 @@ No documentation found. @test docstrings_equal(@doc(Undocumented.ut8), doc"$doc_str") doc_str = Markdown.parse(""" -No documentation found. +No documentation found for private symbol. # Summary @@ -1153,7 +1154,7 @@ let d = @doc(Undocumented.f) io = IOBuffer() show(io, MIME"text/markdown"(), d) @test startswith(String(take!(io)),""" - No documentation found. + No documentation found for private symbol. `$(curmod_prefix)Undocumented.f` is a `Function`. """) @@ -1163,7 +1164,7 @@ let d = @doc(Undocumented.undocumented) io = IOBuffer() show(io, MIME"text/markdown"(), d) @test startswith(String(take!(io)), """ - No documentation found. + No documentation found for private symbol. `$(curmod_prefix)Undocumented.undocumented` is a `Function`. """) @@ -1309,30 +1310,30 @@ end let dt1 = striptrimdocs(_repl(:(dynamic_test(1.0)))) @test dt1 isa Expr @test dt1.args[1] isa Expr - @test dt1.args[1].head === :macrocall - @test dt1.args[1].args[1] === Symbol("@doc") - @test dt1.args[1].args[3] == :(dynamic_test(::typeof(1.0))) + @test dt1.args[1].head === :call + @test dt1.args[1].args[1] === Base.Docs.doc + @test dt1.args[1].args[3] == :(Union{Tuple{typeof(1.0)}}) end let dt2 = striptrimdocs(_repl(:(dynamic_test(::String)))) @test dt2 isa Expr @test dt2.args[1] isa Expr - @test dt2.args[1].head === :macrocall - @test dt2.args[1].args[1] === Symbol("@doc") - @test dt2.args[1].args[3] == :(dynamic_test(::String)) + @test dt2.args[1].head === :call + @test dt2.args[1].args[1] === Base.Docs.doc + @test dt2.args[1].args[3] == :(Union{Tuple{String}}) end let dt3 = striptrimdocs(_repl(:(dynamic_test(a)))) @test dt3 isa Expr @test dt3.args[1] isa Expr - @test dt3.args[1].head === :macrocall - @test dt3.args[1].args[1] === Symbol("@doc") - @test dt3.args[1].args[3].args[2].head === :(::) # can't test equality due to line numbers + @test dt3.args[1].head === :call + @test dt3.args[1].args[1] === Base.Docs.doc + @test dt3.args[1].args[3].args[2].head === :curly # can't test equality due to line numbers end let dt4 = striptrimdocs(_repl(:(dynamic_test(1.0,u=2.0)))) @test dt4 isa Expr @test dt4.args[1] isa Expr - @test dt4.args[1].head === :macrocall - @test dt4.args[1].args[1] === Symbol("@doc") - @test dt4.args[1].args[3] == :(dynamic_test(::typeof(1.0); u::typeof(2.0)=2.0)) + @test dt4.args[1].head === :call + @test dt4.args[1].args[1] === Base.Docs.doc + @test dt4.args[1].args[3] == :(Union{Tuple{typeof(1.0)}}) end # Equality testing diff --git a/test/precompile.jl b/test/precompile.jl index 3d537c638946e..4d3587b586234 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -134,6 +134,7 @@ precompile_test_harness(false) do dir import $Foo2_module: $Foo2_module, override, overridenc import $FooBase_module.hash import Test + public foo, Bar module Inner import $FooBase_module.hash using ..$Foo_module @@ -150,6 +151,7 @@ precompile_test_harness(false) do dir include_dependency("foo.jl") include_dependency("foo.jl") module Bar + public bar include_dependency("bar.jl") end @doc "Bar module" Bar # this needs to define the META dictionary via eval diff --git a/test/reflection.jl b/test/reflection.jl index f4e78ce37c518..a67407c2d0f48 100644 --- a/test/reflection.jl +++ b/test/reflection.jl @@ -211,15 +211,21 @@ include("testenv.jl") # for curmod_str import Base.isexported global this_is_not_defined export this_is_not_defined +public this_is_public @test_throws ErrorException("\"this_is_not_defined\" is not defined in module Main") which(Main, :this_is_not_defined) @test_throws ErrorException("\"this_is_not_exported\" is not defined in module Main") which(Main, :this_is_not_exported) @test isexported(@__MODULE__, :this_is_not_defined) @test !isexported(@__MODULE__, :this_is_not_exported) +@test !isexported(@__MODULE__, :this_is_public) const a_value = 1 @test which(@__MODULE__, :a_value) === @__MODULE__ @test_throws ErrorException("\"a_value\" is not defined in module Main") which(Main, :a_value) @test which(Main, :Core) === Main @test !isexported(@__MODULE__, :a_value) +@test !Base.ispublic(@__MODULE__, :a_value) +@test Base.ispublic(@__MODULE__, :this_is_not_defined) +@test Base.ispublic(@__MODULE__, :this_is_public) +@test !Base.ispublic(@__MODULE__, :this_is_not_exported) end # PR 13825 @@ -1057,3 +1063,16 @@ end @test !Base.ismutationfree(Vector{UInt64}) @test Base.ismutationfree(Type{Union{}}) + +module TestNames + +public publicized +export exported + +publicized() = 1 +exported() = 1 +private() = 1 + +end + +@test names(TestNames) == [:TestNames, :exported, :publicized] diff --git a/test/show.jl b/test/show.jl index 7f32c8c8ff2e2..caeab7d17600c 100644 --- a/test/show.jl +++ b/test/show.jl @@ -523,6 +523,13 @@ end # Hidden macro names @test sprint(show, Expr(:macrocall, Symbol("@#"), nothing, :a)) == ":(@var\"#\" a)" +# Test that public expressions are rendered nicely +# though they are hard to create with quotes because public is not a context dependant keyword +@test sprint(show, Expr(:public, Symbol("@foo"))) == ":(public @foo)" +@test sprint(show, Expr(:public, :f,:o,:o)) == ":(public f, o, o)" +s = sprint(show, :(module A; public x; end)) +@test match(r"^:\(module A\n #= .* =#\n #= .* =#\n public x\n end\)$", s) !== nothing + # PR #38418 module M1 var"#foo#"() = 2 end @test occursin("M1.var\"#foo#\"", sprint(show, M1.var"#foo#", context = :module=>@__MODULE__))