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

Allow use of @static within @objcproperties. #48

Merged
merged 2 commits into from
Jan 7, 2025
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
40 changes: 28 additions & 12 deletions src/syntax.jl
Original file line number Diff line number Diff line change
Expand Up @@ -431,27 +431,43 @@ macro objcproperties(typ, ex)
read_properties = Dict{Symbol,Expr}()
write_properties = Dict{Symbol,Expr}()

for arg in ex.args
isa(arg, LineNumberNode) && continue
Meta.isexpr(arg, :macrocall) || propertyerror("invalid property declaration $arg")
# collect property declarations
properties = []
function process_property(ex)
isa(ex, LineNumberNode) && return
Meta.isexpr(ex, :macrocall) || propertyerror("invalid property declaration $ex")

# split the contained macrocall into its parts
cmd = arg.args[1]
cmd = ex.args[1]
args = []
kwargs = Dict()
positionals = []
for arg in arg.args[2:end]
for arg in ex.args[2:end]
isa(arg, LineNumberNode) && continue
if isa(arg, Expr) && arg.head == :(=)
kwargs[arg.args[1]] = arg.args[2]
else
push!(positionals, arg)
push!(args, arg)
end
end

# if we're dealing with `@static`, so recurse into the block
# TODO: liberally support all unknown macros?
if cmd == Symbol("@static")
ex = macroexpand(__module__, ex; recursive=false)
if ex !== nothing
process_property.(ex.args)
end
else
push!(properties, (; cmd, args, kwargs))
end
end
process_property.(ex.args)

for (cmd, args, kwargs) in properties
# there should only be a single positional argument,
# containing the property name (and optionally its type)
length(positionals) >= 1 || propertyerror("$cmd requires a positional argument")
property_arg = popfirst!(positionals)
length(args) >= 1 || propertyerror("$cmd requires a positional argument")
property_arg = popfirst!(args)
if property_arg isa Symbol
property = property_arg
srcTyp = nothing
Expand Down Expand Up @@ -509,14 +525,14 @@ macro objcproperties(typ, ex)
end
elseif cmd == Symbol("@getproperty")
haskey(read_properties, property) && propertyerror("duplicate property $property")
function_arg = popfirst!(positionals)
function_arg = popfirst!(args)
read_properties[property] = quote
f = $(esc(function_arg))
f(object)
end
elseif cmd == Symbol("@setproperty!")
haskey(write_properties, property) && propertyerror("duplicate property $property")
function_arg = popfirst!(positionals)
function_arg = popfirst!(args)
write_properties[property] = quote
f = $(esc(function_arg))
f(object, value)
Expand All @@ -525,7 +541,7 @@ macro objcproperties(typ, ex)
propertyerror("unrecognized property declaration $cmd")
end

isempty(positionals) || propertyerror("too many positional arguments")
isempty(args) || propertyerror("too many positional arguments")
end

# generate Base.propertynames definition
Expand Down
55 changes: 55 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,61 @@ end
@test_throws UndefRefError TestNSString(nil)
end

@objcproperties TestNSString begin
@autoproperty length::Culong
@static if true
@autoproperty UTF8String::Ptr{Cchar}
end
@static if false
@autoproperty NonExistingProperty::Cint
end
end
@objcwrapper TestNSMutableString <: TestNSString
@objcproperties TestNSMutableString begin
@setproperty! string function(obj, val)
@objc [obj::id{TestNSMutableString} setString:val::id{TestNSString}]::Nothing
end
end
@objcwrapper TestNSOperationQueue <: Object
@objcproperties TestNSOperationQueue begin
@autoproperty name::id{TestNSString} setter=setName
end
@testset "@objcproperties" begin
# immutable object with only read properties
str1 = "foo"
immut = TestNSString(@objc [NSString stringWithUTF8String:str1::Ptr{UInt8}]::id{TestNSString})

@test :length in propertynames(immut)
@test :UTF8String in propertynames(immut)
@test :NonExistingProperty ∉ propertynames(immut)

@test immut.length == length(str1)
@test unsafe_string(immut.UTF8String) == str1

# mutable object with a write property
str2 = "barbar"
mut = TestNSMutableString(@objc [NSMutableString stringWithUTF8String:str2::Ptr{UInt8}]::id{TestNSMutableString})

@test :length in propertynames(mut)
@test :UTF8String in propertynames(mut)
@test :string in propertynames(mut)
@test :NonExistingProperty ∉ propertynames(mut)

@test mut.length == length(str2)
@test unsafe_string(mut.UTF8String) == str2

mut.string = immut
@test mut.length == length(str1)
@test unsafe_string(mut.UTF8String) == str1

# mutable object using @autoproperty to generate a setter
queue = TestNSOperationQueue(@objc [NSOperationQueue new]::id{TestNSOperationQueue})
@test queue.name isa TestNSString
@test unsafe_string(queue.name.UTF8String) != str1
queue.name = immut
@test unsafe_string(queue.name.UTF8String) == str1
end

@testset "@objc blocks" begin
# create a dummy class we'll register our blocks with
# (no need to use @objcwrapper as we're not constructing an id{BlockWrapper})
Expand Down
Loading