Skip to content

Commit efcd578

Browse files
fix: preserve symbolic external units across precompile
Co-authored-by: Miles Cranmer <miles.cranmer@gmail.com>
1 parent 473aa79 commit efcd578

3 files changed

Lines changed: 52 additions & 23 deletions

File tree

src/symbolic_dimensions.jl

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -426,13 +426,26 @@ module SymbolicUnits
426426

427427
import ..UNIT_SYMBOLS
428428
import ..CONSTANT_SYMBOLS
429+
import ..ALL_MAPPING
429430
import ..SymbolicDimensionsSingleton
430431
import ..constructorof
431432
import ..DEFAULT_SYMBOLIC_QUANTITY_TYPE
432433
import ..DEFAULT_SYMBOLIC_QUANTITY_OUTPUT_TYPE
433434
import ..DEFAULT_VALUE_TYPE
434435
import ..DEFAULT_DIM_BASE_TYPE
436+
import ..INDEX_TYPE
437+
import ..UnionAbstractQuantity
435438
import ..WriteOnceReadMany
439+
import ..disambiguate_constant_symbol
440+
441+
symbolic_unit_from_symbol(unit::Symbol) = constructorof(DEFAULT_SYMBOLIC_QUANTITY_TYPE)(
442+
DEFAULT_VALUE_TYPE(1.0),
443+
SymbolicDimensionsSingleton{DEFAULT_DIM_BASE_TYPE}(unit)
444+
)
445+
symbolic_constant_from_symbol(unit::Symbol) = constructorof(DEFAULT_SYMBOLIC_QUANTITY_TYPE)(
446+
DEFAULT_VALUE_TYPE(1.0),
447+
SymbolicDimensionsSingleton{DEFAULT_DIM_BASE_TYPE}(disambiguate_constant_symbol(unit))
448+
)
436449

437450
# Lazily create unit symbols (since there are so many)
438451
module Constants
@@ -479,11 +492,7 @@ module SymbolicUnits
479492
# Non-eval version of `update_symbolic_unit_values!` for registering units in
480493
# an external module.
481494
function update_external_symbolic_unit_value(unit)
482-
unit = constructorof(DEFAULT_SYMBOLIC_QUANTITY_TYPE)(
483-
DEFAULT_VALUE_TYPE(1.0),
484-
SymbolicDimensionsSingleton{DEFAULT_DIM_BASE_TYPE}(unit)
485-
)
486-
push!(SYMBOLIC_UNIT_VALUES, unit)
495+
push!(SYMBOLIC_UNIT_VALUES, symbolic_unit_from_symbol(unit))
487496
end
488497

489498
"""
@@ -512,39 +521,50 @@ module SymbolicUnits
512521
as_quantity(x::Number) = convert(DEFAULT_SYMBOLIC_QUANTITY_OUTPUT_TYPE, x)
513522
as_quantity(x) = error("Unexpected type evaluated: $(typeof(x))")
514523

515-
@unstable function map_to_scope(ex::Expr)
524+
@unstable map_to_scope(ex::Expr) = map_to_scope(@__MODULE__, ex)
525+
@unstable function map_to_scope(mod::Module, ex::Expr)
516526
if !(ex.head == :call) && !(ex.head == :. && ex.args[1] == :Constants)
517527
throw(ArgumentError("Unexpected expression: $ex. Only `:call` and `:.` (for `SymbolicConstants`) are expected."))
518528
end
519529
if ex.head == :call
520-
ex.args[2:end] = map(map_to_scope, ex.args[2:end])
530+
ex.args[2:end] = map(arg -> map_to_scope(mod, arg), ex.args[2:end])
521531
return ex
522532
else # if ex.head == :. && ex.args[1] == :Constants
523533
@assert ex.args[2] isa QuoteNode
524-
return lookup_constant(ex.args[2].value)
534+
return Expr(:call, GlobalRef(@__MODULE__, :lookup_constant), QuoteNode(ex.args[2].value))
525535
end
526536
end
527-
function map_to_scope(sym::Symbol)
528-
if sym in UNIT_SYMBOLS
537+
map_to_scope(sym::Symbol) = map_to_scope(@__MODULE__, sym)
538+
function map_to_scope(mod::Module, sym::Symbol)
539+
if sym in UNIT_SYMBOLS || _has_quantity_binding(mod, sym)
529540
# return at end
530541
elseif sym in CONSTANT_SYMBOLS
531542
throw(ArgumentError("Symbol $sym found in `Constants` but not `Units`. Please use `us\"Constants.$sym\"` instead."))
532543
else
533544
throw(ArgumentError("Symbol $sym not found in `Units` or `Constants`."))
534545
end
535-
return lookup_unit(sym)
546+
return Expr(:call, GlobalRef(@__MODULE__, :lookup_unit), mod, QuoteNode(sym))
536547
end
537-
function map_to_scope(ex)
538-
return ex
548+
map_to_scope(ex) = ex
549+
map_to_scope(::Module, ex) = ex
550+
551+
function _has_quantity_binding(mod::Module, sym::Symbol)
552+
return isdefined(mod, sym) && Base.invokelatest(getproperty, mod, sym) isa UnionAbstractQuantity
539553
end
540-
function lookup_unit(ex::Symbol)
541-
i = findfirst(==(ex), UNIT_SYMBOLS)::Int
542-
return as_quantity(SYMBOLIC_UNIT_VALUES[i])
554+
function _ensure_registered(mod::Module, sym::Symbol)
555+
if iszero(get(ALL_MAPPING, sym, INDEX_TYPE(0))) && _has_quantity_binding(mod, sym)
556+
update_all_values = getfield(parentmodule(@__MODULE__), :update_all_values)
557+
unit = Base.invokelatest(getproperty, mod, sym)
558+
Base.invokelatest(update_all_values, sym, unit)
559+
end
560+
return nothing
543561
end
544-
function lookup_constant(ex::Symbol)
545-
i = findfirst(==(ex), CONSTANT_SYMBOLS)::Int
546-
return as_quantity(SYMBOLIC_CONSTANT_VALUES[i])
562+
function lookup_unit(mod::Module, ex::Symbol)
563+
_ensure_registered(mod, ex)
564+
return as_quantity(symbolic_unit_from_symbol(ex))
547565
end
566+
lookup_unit(ex::Symbol) = lookup_unit(@__MODULE__, ex)
567+
lookup_constant(ex::Symbol) = as_quantity(symbolic_constant_from_symbol(ex))
548568
end
549569

550570
import .SymbolicUnits: as_quantity, sym_uparse, SymbolicConstants, map_to_scope
@@ -565,7 +585,7 @@ module. So, for example, `us"Constants.c^2 * Hz^2"` would evaluate to
565585
namespace collisions, a few physical constants are automatically converted.
566586
"""
567587
macro us_str(s)
568-
ex = map_to_scope(Meta.parse(s))
588+
ex = map_to_scope(__module__, Meta.parse(s))
569589
ex = :($as_quantity($ex))
570590
return esc(ex)
571591
end

test/precompile_test/ExternalUnitRegistration.jl

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,21 @@ using Test
77

88
@register_unit MyWb u"m^2*kg*s^-2*A^-1"
99

10+
expanded_mywb() = 1u"MyWb"
11+
symbolic_mywb() = 1us"MyWb"
12+
1013
@testset "Register Unit Inside a Module" begin
1114
for collection in (UNIT_SYMBOLS, ALL_SYMBOLS, keys(ALL_MAPPING._raw_data), keys(UNIT_MAPPING._raw_data))
1215
@test :MyWb collection
1316
end
1417

15-
w = u"MyWb"
16-
ws = us"MyWb"
18+
w = expanded_mywb()
19+
ws = symbolic_mywb()
1720
@test w isa DEFAULT_QUANTITY_TYPE
1821
@test ws isa DEFAULT_SYMBOLIC_QUANTITY_OUTPUT_TYPE
22+
@test w == u"MyWb"
23+
@test ws == us"MyWb"
24+
@test string(ws) == "1.0 MyWb"
1925
end
2026

2127
end

test/unittests.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2343,10 +2343,13 @@ end
23432343

23442344
push!(LOAD_PATH, joinpath(@__DIR__, "precompile_test"))
23452345

2346-
using ExternalUnitRegistration: MyWb
2346+
using ExternalUnitRegistration: MyWb, expanded_mywb, symbolic_mywb
23472347
@testset "Type of External Unit" begin
23482348
@test MyWb isa DEFAULT_QUANTITY_TYPE
23492349
@test MyWb/u"m^2*kg*s^-2*A^-1" == 1.0
2350+
@test expanded_mywb() == MyWb
2351+
@test uexpand(symbolic_mywb()) == MyWb
2352+
@test string(symbolic_mywb()) == "1.0 MyWb"
23502353
end
23512354

23522355
pop!(LOAD_PATH)

0 commit comments

Comments
 (0)