From 68c77d2236e981b37f9329f5a1607dad1a206abe Mon Sep 17 00:00:00 2001 From: Iago Bonnici Date: Wed, 11 Sep 2024 10:42:58 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=A7=20Start=20fixing=20@method=20macro?= =?UTF-8?q?=20tests..?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Framework/macro_helpers.jl | 5 +- src/Framework/method_macro.jl | 41 +- src/Framework/methods.jl | 9 +- src/Framework/system.jl | 8 +- test/framework/04-conflicts_macro.jl | 4 +- test/framework/05-method_macro.jl | 587 ++++++++++++++++----------- test/framework/runtests.jl | 9 +- 7 files changed, 387 insertions(+), 276 deletions(-) diff --git a/src/Framework/macro_helpers.jl b/src/Framework/macro_helpers.jl index 670dbff07..ed6e2ac57 100644 --- a/src/Framework/macro_helpers.jl +++ b/src/Framework/macro_helpers.jl @@ -67,8 +67,9 @@ function to_value(mod, expression, context, error_out, type = nothing) ev = quote val = $ev val isa $type || $error_out( - "$($context): expression does not evaluate to a $($type): \ - $($(repr(expression))), but to a $(typeof(val)): $val.", + "$($context): expression does not evaluate to a '$($type)':\n\ + Expression: $($(repr(expression)))\n\ + Result: $val ::$(typeof(val))", ) val end diff --git a/src/Framework/method_macro.jl b/src/Framework/method_macro.jl index b71b981c5..c182d99e6 100644 --- a/src/Framework/method_macro.jl +++ b/src/Framework/method_macro.jl @@ -5,9 +5,6 @@ # # f(v::Value, ...) = # -# If no explicit receiver is found, -# then first argument is assumed to be the receiver provided it is untyped or `::Any`. -# # Then, the macro invokation goes like: # # @method f depends(components...) read_as(names...) # or write_as(names...) @@ -96,7 +93,7 @@ function method_macro(__module__, __source__, input...) # unless there is another programmatic way to add a method to a function in Julia? xp = input[1] fn_xp, ValueType_xp = nothing, nothing # (hepl JuliaLS) - @capture(xp, fn_xp_{ValueTypeXp_} | fn_xp_) + @capture(xp, fn_xp_{ValueType_xp_} | fn_xp_) fn_xp isa Symbol || fn_xp isa Expr && fn_xp.head == :. || perr("Not a method identifier or path: $(repr(fn_xp)).") @@ -105,7 +102,7 @@ function method_macro(__module__, __source__, input...) tovalue(ValueType_xp, "System value type", Type) push_res!(quote - ValueType = $ValueType_xp # Explicit or uninferred. + ValueType = $ValueType_xp # Explicit or inferred. fn = $(tovalue(fn_xp, "System method", Function)) end) @@ -135,11 +132,11 @@ function method_macro(__module__, __source__, input...) ValueType = system_value_type(C) else if !(C <: Component{ValueType}) - actual_V = system_value_type(C) + C_V = system_value_type(C) xerr("Depends section: system value type is supposed to be '$ValueType' \ based on the first macro argument, \ - but '$C' subtypes '$(Component{actual_V})' \ + but '$C' subtypes '$(Component{C_V})' \ and not '$(Component{ValueType})'.") end end @@ -161,11 +158,11 @@ function method_macro(__module__, __source__, input...) if Base.isidentifier(kw) if !(kw in (read_kw, write_kw)) perr("Invalid section keyword: $(repr(kw)). \ - Expected `$read_kw` or `$write_kw` or `depends`.") + Expected :$read_kw or :$write_kw or :depends.") end if !isnothing(proptype) - proptype == kw && perr("The `$kw` section is specified twice.") - perr("Cannot specify both `$proptype` section and `$kw`.") + proptype == kw && perr("The :$kw section is specified twice.") + perr("Cannot specify both :$proptype section and :$kw.") end for pname in propnames pname isa Symbol || @@ -179,7 +176,7 @@ function method_macro(__module__, __source__, input...) perr("Unexpected @method section. \ Expected `depends(..)`, `$read_kw(..)` or `$write_kw(..)`. \ - Got: $(repr(i)).") + Got instead: $(repr(i)).") end @@ -191,7 +188,7 @@ function method_macro(__module__, __source__, input...) push_res!( quote isnothing(ValueType) && xerr("The system value type cannot be inferred \ - when no dependencies are given. + when no dependencies are given.\n\ Consider making it explicit \ with the first macro argument: \ `$fn{MyValueType}`.") @@ -224,8 +221,9 @@ function method_macro(__module__, __source__, input...) values = Set() system_values = Set() systems_only = Set() - for (p, n) in zip(parms, names) + for (i, (p, n)) in enumerate(zip(parms, names)) p isa Core.TypeofVararg && continue + n == Symbol("#unused#") && (n = Symbol('#', i)) p <: ValueType && push!(values, n) p <: System{ValueType} && push!(system_values, n) p === System && push!(systems_only, n) @@ -234,15 +232,11 @@ function method_macro(__module__, __source__, input...) (what, set, type) -> xerr("Receiving several (possibly different) $what \ is not yet supported by the framework. \ - Here both :$(pop!(set)) and :$(pop!(set)) \ + Here both parameters :$(pop!(set)) and :$(pop!(set)) \ are of type $type.") - length(values) > 1 && xerr("system/values parameters", values, ValueType) - receiver = if isempty(values) - parms[1] === Any || continue - names[1] - else - pop!(values) - end + length(values) > 1 && severr("system/values parameters", values, ValueType) + isempty(values) && continue + receiver = pop!(values) sv = system_values length(sv) > 1 && severr("system hooks", sv, System{ValueType}) hook = if isempty(system_values) @@ -259,8 +253,7 @@ function method_macro(__module__, __source__, input...) isempty(to_wrap) && xerr("No suitable method has been found to mark $fn as a system method. \ Valid methods must have at least \ - one 'receiver' argument of type ::$ValueType \ - or a first ::Any argument to be implicitly considered as such.") + one 'receiver' argument of type ::$ValueType.") end, ) @@ -379,7 +372,7 @@ function method_macro(__module__, __source__, input...) # when the macro is called within @testset blocks # with LOCAL_MACROCALLS = true: # https://stackoverflow.com/a/55292662/3719101) - $dep = missing_dependency_for($($efn), $receiver) + $dep = first_missing_dependency_for($($efn), $receiver) if !isnothing($dep) $a = isabstracttype($dep) ? " a" : "" throw( diff --git a/src/Framework/methods.jl b/src/Framework/methods.jl index 0052a2402..257bf9e90 100644 --- a/src/Framework/methods.jl +++ b/src/Framework/methods.jl @@ -17,7 +17,7 @@ # - method!(v::Value, rhs) # # .. can optionally become properties of the system/value, -# in the sense of julia's `getproperty/set_property`. +# in the sense of julia's `getproperty/set_property!`. # # The properties also come in two styles: # @@ -33,7 +33,7 @@ missing_dependencies_for(fn::Type{<:Function}, s::System{V}) where {V} = !has_component(s, dep) end # Just pick the first one. Return nothing if dependencies are met. -function missing_dependency_for(fn::Type{<:Function}, s::System) +function first_missing_dependency_for(fn::Type{<:Function}, s::System) for dep in missing_dependencies_for(fn, s) return dep end @@ -43,7 +43,8 @@ end # Direct call with the functions themselves. depends(::Type{V}, fn::Function) where {V} = depends(V, typeof(fn)) missing_dependencies_for(fn::Function, s::System) = missing_dependencies_for(typeof(fn), s) -missing_dependency_for(fn::Function, s::System) = missing_dependency_for(typeof(fn), s) +first_missing_dependency_for(fn::Function, s::System) = + first_missing_dependency_for(typeof(fn), s) # Map wrapped system value and property name to the corresponding function. read_property(V::Type, ::Val{name}) where {name} = @@ -137,6 +138,6 @@ struct PropertyError{V} <: SystemException PropertyError(::Type{V}, s, m) where {V} = new{V}(s, m, PhantomData{V}()) end function Base.showerror(io::IO, e::PropertyError{V}) where {V} - println(io, "In property '$(e.name)' of '$V': $(e.message)") + println(io, "In property :$(e.name) of '$V': $(e.message)") end properr(V, n, m) = throw(PropertyError(V, n, m)) diff --git a/src/Framework/system.jl b/src/Framework/system.jl index 812dbd725..ab87489ce 100644 --- a/src/Framework/system.jl +++ b/src/Framework/system.jl @@ -72,7 +72,7 @@ function Base.getproperty(system::System{V}, p::Symbol) where {V} # Search property method. fn = read_property(V, Val(p)) # Check for required components availability. - miss = missing_dependency_for(fn, system) + miss = first_missing_dependency_for(fn, system) if !isnothing(miss) comp = isabstracttype(miss) ? "A component $miss" : "Component $miss" properr(V, p, "$comp is required to read this property.") @@ -87,7 +87,7 @@ function Base.setproperty!(system::System{V}, p::Symbol, rhs) where {V} # Search property method. fn = readwrite_property(V, Val(p)) # Check for required components availability. - miss = missing_dependency_for(fn, system) + miss = first_missing_dependency_for(fn, system) if !isnothing(miss) comp = isabstracttype(miss) ? "A component $miss" : "Component $miss" properr(V, p, "$comp is required to write to this property.") @@ -166,7 +166,7 @@ export properties # Yields (:propname, read, Option{write}) function properties(s::System{V}) where {V} imap(ifilter(properties(V)) do (_, read, _, _) - isnothing(missing_dependency_for(read, s)) + isnothing(first_missing_dependency_for(read, s)) end) do (name, read, write, _) (name, read, write) end @@ -177,7 +177,7 @@ end # Yields (:propname, read, Option{write}, iterator{missing_dependencies...}) function latent_properties(s::System{V}) where {V} imap(ifilter(properties(V)) do (_, read, _, _) - !isnothing(missing_dependency_for(read, s)) + !isnothing(first_missing_dependency_for(read, s)) end) do (name, read, write, deps) (name, read, write, ifilter(d -> !has_component(s, d), deps)) end diff --git a/test/framework/04-conflicts_macro.jl b/test/framework/04-conflicts_macro.jl index 7f0a636b8..30fa73b04 100644 --- a/test/framework/04-conflicts_macro.jl +++ b/test/framework/04-conflicts_macro.jl @@ -24,7 +24,7 @@ for letter in 'A':'Z' end) end -@testset "Invalid @conflicts macro invocations." begin +@testset "Declaring components @conflicts." begin #--------------------------------------------------------------------------------------- # Provide enough data for the declaration to be meaningful. @@ -275,7 +275,7 @@ using Test const S = System{Value} comps(s) = collect(components(s)) -@testset "Abstract component types conflicts." begin +@testset "Abstract component conflicts semantics." begin # Component type hierachy. # diff --git a/test/framework/05-method_macro.jl b/test/framework/05-method_macro.jl index 0e1c303df..4a927ecbf 100644 --- a/test/framework/05-method_macro.jl +++ b/test/framework/05-method_macro.jl @@ -18,7 +18,7 @@ export Value module Invocations using ..MethodMacro using EcologicalNetworksDynamics.Framework -using Main: @sysfails, @pmethfails, @xmethfails +using Main: @failswith, @sysfails, @pmethfails, @xmethfails using Test # Due to https://github.com/JuliaLang/julia/issues/51217, @@ -32,12 +32,12 @@ const S = Invocations # "Self"-module. twice(::Value) = () get_m_again(v::Value) = v.m set_m_again!(v::Value, m) = (v.m = m) -set_x!(v::Value, x) = nothing -forgot_value_type(v) = () +set_x!(::Value, x) = nothing +forgot_value_type(::Any) = () miss_parameter() = () -inconsistent_value_type(v::Int64) = () -inconsistent_later_dep(v::Int64) = () -wrong_depends(v) = () +inconsistent_value_type(::Int64) = () +inconsistent_later_dep(::Int64) = () +wrong_depends(::Any) = () wrong_read(::Value) = () wrong_write(::Value) = () extra_read_parameter(::Value, _) = () @@ -48,243 +48,358 @@ notasec(::Value) = () @testset "Invocation variations for @method macro." begin - # Cheat with the component system here, as we are only checking macro calls: - # the following dummy component is required by most subsequent methods, - # although not all of them make it explicit. - struct MZero <: Blueprint{Value} end - Framework.expand!(v, ::MZero) = (v._member = 0) - @component MZero - s = System{Value}(MZero()) - - get_m(v::Value) = v._member - set_m!(v::Value, m) = (v._member = m) - @method get_m read_as(m) - @method set_m! write_as(m) - # ====================================================================================== - # Valid invocations. - - #--------------------------------------------------------------------------------------- - simple_method(v::Value) = v.m - @method simple_method - - @test simple_method(s) == 0 - - #--------------------------------------------------------------------------------------- - in_block(v::Value) = v.m + 1 - @method begin - in_block - end - - @test in_block(s) == 1 - - #--------------------------------------------------------------------------------------- - struct Marker <: Blueprint{Value} end - @component Marker + # Typical regular use. - simple_method_with_deps(v) = v.m + 3 # (optional explicit type: inferred from deps) - @method simple_method_with_deps depends(Marker) + # One component that all subsequent tested methods depend on. + struct Unf_b <: Blueprint{Value} end + @blueprint Unf_b + @component Unf{Value} blueprints(b::Unf_b) + Framework.expand!(v, ::Unf_b) = (v._member = 0) + s = System{Value}(Unf.b()) + # Simple valid invocation. + get_m(v::Value) = v._member + set_m!(v::Value, m) = (v._member = m) + @method get_m read_as(m) depends(Unf) + @method set_m! write_as(m) depends(Unf) + + # Read/write like property. + @test get_m(s) == 0 + @test s.m == 0 + set_m!(s, 5) + @test get_m(s) == s.m == 5 + s.m = 8 + @test get_m(s) == s.m == 8 + + # Checked method issue correct errors when the required components are missing. + e = System{Value}() + @sysfails(get_m(e), Method(get_m), "Requires component $_Unf.") + @sysfails(set_m!(e, 0), Method(set_m!), "Requires component $_Unf.") + @sysfails(e.m, Property(m), "Component $_Unf is required to read this property.") @sysfails( - simple_method_with_deps(s), - Method(simple_method_with_deps), - "Requires component '$Marker'.", + (e.m = 0), + Property(m), + "Component $_Unf is required to write to this property." ) - @test simple_method_with_deps(s + Marker()) == 3 + # ====================================================================================== + # Variations. - #--------------------------------------------------------------------------------------- - deps_in_blocks(v) = v.m + 4 + # Block-syntax. + aoy(v::Value) = v.m @method begin - deps_in_blocks - depends(Marker) + aoy + read_as(aoy) + depends(Unf) end + @test aoy(s) == 8 - @sysfails(deps_in_blocks(s), Method(deps_in_blocks), "Requires component '$Marker'.",) - @test deps_in_blocks(s + Marker()) == 4 + # Explicit value type without dependencies. + cna(v::Value) = v.m + @method cna{Value} read_as(cna) + @test cna(s) == 8 - #--------------------------------------------------------------------------------------- - infer_from_the_method_with_one_argument() = () - infer_from_the_method_with_one_argument(v::Value) = v.m + 5 - @method infer_from_the_method_with_one_argument - - @test infer_from_the_method_with_one_argument(s) == 5 - - #--------------------------------------------------------------------------------------- - several_arguments(v::Value, b, c) = v.m + b * c - @method several_arguments - - @test several_arguments(s, 5, 10) == 50 - - #--------------------------------------------------------------------------------------- - keyword_arguments(v::Value, b, c; d = 1000) = v.m + b * c - d - @method keyword_arguments - - @test keyword_arguments(s, 5, 10) == -950 - @test keyword_arguments(s, 5, 10; d = 10_000) == -9950 - - #--------------------------------------------------------------------------------------- - explicit_empty_lists_1(::Value) = () - @method explicit_empty_lists_1 depends() read_as() - explicit_empty_lists_2(::Value, _) = () - @method explicit_empty_lists_2 write_as() - - # ====================================================================================== - # Invocations failures. - - # Guard against double specifications. - Framework.REVISING = false - @method S.twice + # Forgot to specify value type in the @method call. + dti(v::Value) = v.m @xmethfails( - (@method S.twice), - twice, - "Function '$twice' already marked as a method for '$System{$Value}'." + (@method dti read_as(dti)), + dti, + "The system value type cannot be inferred when no dependencies are given.\n\ + Consider making it explicit with the first macro argument: `$dti{MyValueType}`." ) - #--------------------------------------------------------------------------------------- - # Properties consistency. - - # Guard against properties overrides. - @sysfails( - (@method S.get_m_again read_as(m)), - Property(m), - "Readable property already exists.", - ) - @sysfails( - (@method S.set_m_again! write_as(m)), - Property(m), - "Writable property already exists.", - ) - - # Disallow write-only properties. - @sysfails( - (@method S.set_x! write_as(x)), - Property(x), - "Property cannot be set writable without having been set readable first.", - ) - - #--------------------------------------------------------------------------------------- - # Raw basic misuses. - - @pmethfails((@method()), ["Not enough macro input provided. Example usage:\n"],) - - @pmethfails((@method (4 + 5)), "Not a method identifier or path: :(4 + 5).",) - + # Forgot to specify a receiver. + kck(v) = v.m @xmethfails( - (@method S.forgot_value_type), - forgot_value_type, - "Without dependencies given, the system value type could not be inferred \ - from the first parameter type of '$forgot_value_type'. \ - Consider making it explicit.", + (@method kck{Value} read_as(kck)), + kck, + "No suitable method has been found to mark $kck as a system method. \ + Valid methods must have at least one 'receiver' argument of type ::$Value." ) + # Only methods with the right receivers are wrapped. + enm(a::Int, b::Int) = a - b + enm(a, v::Value, b) = v.m * (a - b) + enm(a, b, v::Value; shift = 0) = (a - b) / v.m + shift # Support kwargs. + @method enm depends(Unf) + @test enm(10, 4) == 6 # As-is. + @test enm(10, s, 4) == 8 * 6 # Overriden for the system. + @test enm(10, 4, s) == 6 / 8 + @test enm(10, 4, s; shift = 5) == 6 / 8 + 5 + @failswith(enm(s, 4), MethodError) # Not overriden. + # Checked on their receiver arguments. + @sysfails(enm(10, 4, e), Method(enm), "Requires component $_Unf.") + + # Forbid several receivers (yet). + ara(a, v::Value, b, w::Value) = v.m * (a - b) / w.m @xmethfails( - (@method S.miss_parameter), - miss_parameter, - "No method of '$miss_parameter' take at least one argument.", - ) - - #--------------------------------------------------------------------------------------- - # Depends section. - - @xmethfails(# ::Int64 # ::Value - (@method S.inconsistent_value_type depends(Marker)), - inconsistent_value_type, - "Depends section: system value type has been inferred to be '$Int64' \ - based on the first parameter type(s) of '$inconsistent_value_type', \ - but '$Marker' subtypes '$Blueprint{$Value}' and not '$Blueprint{$Int64}'.", + (@method ara depends(Unf)), + ara, + "Receiving several (possibly different) system/values parameters \ + is not yet supported by the framework. \ + Here both :w and :v are of type $Value." ) - struct MI <: Blueprint{Int64} end - @component MI - @xmethfails( - (@method S.inconsistent_later_dep depends(MI, Marker)), - inconsistent_later_dep, - "Depends section: '$Marker' does not subtype '$Blueprint{$Int64}', \ - but '$Blueprint{$Value}'.", - ) + # Allow unused receiver. + pum(a, ::Value) = a + 1 + @method pum depends(Unf) + pum(4, s) # HERE: this should work. + pum(4, e) # And this fail with a useful error message. + + # System hook. + received_hook = [] + function hyy(a::Int, v::Value, _s::System) + empty!(received_hook) + push!(received_hook, _s) + a + v.m + end + @method hyy depends(Unf) + @test hyy(5, s) == 5 + 8 # Can be called without the hook. + @test length(received_hook) == 1 + @test first(received_hook) === s # But it has been used transfered. + # Cannot ask for several hooks. + zmz(a::Int, s::System, v::Value, ::System) = a + v.m - whatever(s) @xmethfails( - (@method S.wrong_depends depends(4 + 5)), - wrong_depends, - "First dependency: expression does not evaluate to a Type: :(4 + 5), \ - but to a Int64: 9." + (@method zmz depends(Unf)), + zmz, + "Receiving several (possibly different) system hooks \ + is not yet supported by the framework. \ + Here both parameters :s and :#4 are of type System." ) - @xmethfails( - (@method S.wrong_depends depends(Int64)), - wrong_depends, - "First dependency: expression does not evaluate to a blueprint type, \ - but to 'Int64' (:Int64).", - ) + # #--------------------------------------------------------------------------------------- + # keyword_arguments(v::Value, b, c; d = 1000) = v.m + b * c - d + # @method keyword_arguments + + # @test keyword_arguments(s, 5, 10) == -950 + # @test keyword_arguments(s, 5, 10; d = 10_000) == -9950 + + # #--------------------------------------------------------------------------------------- + # explicit_empty_lists_1(::Value) = () + # @method explicit_empty_lists_1 depends() read_as() + # explicit_empty_lists_2(::Value, _) = () + # @method explicit_empty_lists_2 write_as() + + # # ====================================================================================== + # # Invocations failures. + + # # Guard against double specifications. + # Framework.REVISING = false + # @method S.twice + # @xmethfails( + # (@method S.twice), + # twice, + # "Function '$twice' already marked as a method for '$System{$Value}'." + # ) + + # #--------------------------------------------------------------------------------------- + # # Properties consistency. + + # # Guard against properties overrides. + # @sysfails( + # (@method S.get_m_again read_as(m)), + # Property(m), + # "Readable property already exists.", + # ) + # @sysfails( + # (@method S.set_m_again! write_as(m)), + # Property(m), + # "Writable property already exists.", + # ) + + # # Disallow write-only properties. + # @sysfails( + # (@method S.set_x! write_as(x)), + # Property(x), + # "Property cannot be set writable without having been set readable first.", + # ) + + # #--------------------------------------------------------------------------------------- + # # Raw basic misuses. + + # @pmethfails((@method()), ["Not enough macro input provided. Example usage:\n"],) + + # @pmethfails((@method (4 + 5)), "Not a method identifier or path: :(4 + 5).",) + + # @xmethfails( + # (@method S.forgot_value_type), + # forgot_value_type, + # "Without dependencies given, the system value type could not be inferred \ + # from the first parameter type of '$forgot_value_type'. \ + # Consider making it explicit.", + # ) + + # @xmethfails( + # (@method S.miss_parameter), + # miss_parameter, + # "No method of '$miss_parameter' take at least one argument.", + # ) + + # #--------------------------------------------------------------------------------------- + # # Depends section. + + # @xmethfails(# ::Int64 # ::Value + # (@method S.inconsistent_value_type depends(Marker)), + # inconsistent_value_type, + # "Depends section: system value type has been inferred to be '$Int64' \ + # based on the first parameter type(s) of '$inconsistent_value_type', \ + # but '$Marker' subtypes '$Blueprint{$Value}' and not '$Blueprint{$Int64}'.", + # ) + + # struct MI <: Blueprint{Int64} end + # @component MI + # @xmethfails( + # (@method S.inconsistent_later_dep depends(MI, Marker)), + # inconsistent_later_dep, + # "Depends section: '$Marker' does not subtype '$Blueprint{$Int64}', \ + # but '$Blueprint{$Value}'.", + # ) + + # @xmethfails( + # (@method S.wrong_depends depends(4 + 5)), + # wrong_depends, + # "First dependency: expression does not evaluate to a Type: :(4 + 5), \ + # but to a Int64: 9." + # ) + + # @xmethfails( + # (@method S.wrong_depends depends(Int64)), + # wrong_depends, + # "First dependency: expression does not evaluate to a blueprint type, \ + # but to 'Int64' (:Int64).", + # ) + + # #--------------------------------------------------------------------------------------- + # # Property section. + + # @pmethfails( + # (@method S.wrong_read read_as(4 + 5)), + # "Property name is not a simple identifier: :(4 + 5).", + # ) + + # @pmethfails( + # (@method S.wrong_write write_as(:v)), + # "Property name is not a simple identifier: :(:v).", + # ) + + # @xmethfails( + # (@method S.extra_read_parameter read_as(a)), + # extra_read_parameter, + # "The function '$extra_read_parameter' \ + # cannot be called with exactly 1 argument of type '$Value' \ + # as required to be set as a 'read' property." + # ) + + # @xmethfails( + # (@method S.miss_write_parameter write_as(a)), + # miss_write_parameter, + # "The function '$miss_write_parameter' \ + # cannot be called with exactly 2 arguments, \ + # the first one being of type '$Value', \ + # as required to be set as a 'write' property." + # ) + + # #--------------------------------------------------------------------------------------- + # # Guard against redundant sections. + + # @pmethfails( + # (@method S.redundant depends(Marker) depends(A)), + # "The `depends` section is specified twice.", + # ) + + # @pmethfails( + # (@method S.redundant read_as(A) read_as(B)), + # "The `read_as` section is specified twice.", + # ) + + # @pmethfails( + # (@method S.redundant write_as(A) write_as(B)), + # "The `write_as` section is specified twice.", + # ) + + # @pmethfails( + # (@method S.redundant write_as(A) read_as(B)), + # "Cannot specify both `write_as` section and `read_as`.", + # ) + + # #--------------------------------------------------------------------------------------- + # # Unexpected sections. + + # @pmethfails( + # (@method S.notasec (4 + 5)), + # "Unexpected @method section. \ + # Expected `depends(..)`, `read_as(..)` or `write_as(..)`. \ + # Got: :(4 + 5).", + # ) + + # @pmethfails( + # (@method S.notasec notasection(4 + 5)), + # "Invalid section keyword: :notasection. \ + # Expected `read_as` or `write_as` or `depends`.", + # ) #--------------------------------------------------------------------------------------- - # Property section. + # Macro input checks. - @pmethfails( - (@method S.wrong_read read_as(4 + 5)), - "Property name is not a simple identifier: :(4 + 5).", - ) + # Macro input evaluation. + oab(v::Value) = v.m + comp_xp() = Unf + value_xp() = Value + @method oab{value_xp()} read_as(oab) depends(comp_xp()) + @test oab(s) == 8 + @sysfails(oab(e), Method(oab), "Requires component $_Unf.") - @pmethfails( - (@method S.wrong_write write_as(:v)), - "Property name is not a simple identifier: :(:v).", - ) + @pmethfails((@method a + 5), "Not a method identifier or path: :(a + 5).") @xmethfails( - (@method S.extra_read_parameter read_as(a)), - extra_read_parameter, - "The function '$extra_read_parameter' \ - cannot be called with exactly 1 argument of type '$Value' \ - as required to be set as a 'read' property." + (@method oab{Undef}), + nothing, + "System value type: expression does not evaluate: :Undef. \ + (See error further down the exception stack.)" ) @xmethfails( - (@method S.miss_write_parameter write_as(a)), - miss_write_parameter, - "The function '$miss_write_parameter' \ - cannot be called with exactly 2 arguments, \ - the first one being of type '$Value', \ - as required to be set as a 'write' property." - ) - - #--------------------------------------------------------------------------------------- - # Guard against redundant sections. - - @pmethfails( - (@method S.redundant depends(Marker) depends(A)), - "The `depends` section is specified twice.", + (@method oab{4 + 5}), + nothing, + "System value type: expression does not evaluate to a 'Type':\n\ + Expression: :(4 + 5)\n\ + Result: 9 ::$Int" ) @pmethfails( - (@method S.redundant read_as(A) read_as(B)), - "The `read_as` section is specified twice.", + (@method oab{Value} 4 + 5), + "Unexpected @method section. \ + Expected `depends(..)`, `read_as(..)` or `write_as(..)`. \ + Got instead: :(4 + 5)." ) @pmethfails( - (@method S.redundant write_as(A) write_as(B)), - "The `write_as` section is specified twice.", + (@method oab{Value} notasection(oab)), + "Invalid section keyword: :notasection. \ + Expected :read_as or :write_as or :depends." ) @pmethfails( - (@method S.redundant write_as(A) read_as(B)), - "Cannot specify both `write_as` section and `read_as`.", + (@method oab{Value} read_as(4 + 5)), + "Property name is not a simple identifier: :(4 + 5)." ) - #--------------------------------------------------------------------------------------- - # Unexpected sections. - - @pmethfails( - (@method S.notasec (4 + 5)), - "Unexpected @method section. \ - Expected `depends(..)`, `read_as(..)` or `write_as(..)`. \ - Got: :(4 + 5).", + @xmethfails( + (@method oab{Value} read_as(oab) depends(4 + 5)), + oab, + "First dependency: expression does not evaluate to a component:\n\ + Expression: :(4 + 5)\n\ + Result: 9 ::$Int" ) - @pmethfails( - (@method S.notasec notasection(4 + 5)), - "Invalid section keyword: :notasection. \ - Expected `read_as` or `write_as` or `depends`.", + @xmethfails( + (@method oab{Value} read_as(oab) depends(Unf, 4 + 5)), + oab, + "Depends section: expression does not evaluate to a component for '$Value':\n\ + Expression: :(4 + 5)\n\ + Result: 9 ::$Int" ) end @@ -299,45 +414,45 @@ using Test @testset "Abstract component types dependencies." begin - # Component type hierachy. - # - # A - # ┌─┼─┐ - # B C D - # - abstract type A <: Blueprint{Value} end - struct B <: A end - struct C <: A end - struct D <: A end - Framework.expand!(v, ::B) = (v._member = 1) - Framework.expand!(v, ::C) = (v._member = 2) - Framework.expand!(v, ::D) = (v._member = 3) - @component B - @component C - @component D - - f(v, x) = x + v._member - get_prop(v) = v._member - set_prop!(v, x) = (v._member = x) - @method f depends(A) - @method get_prop depends(A) read_as(prop) - @method set_prop! depends(A) write_as(prop) - - s = System{Value}() - @sysfails(f(s, 5), Method(f), "Requires a component '$A'.") - @sysfails(s.prop, Property(prop), "A component '$A' is required to read this property.") - @sysfails( - (s.prop = 5), - Property(prop), - "A component '$A' is required to write to this property." - ) - - # Any subtype enables the method and properties. - add!(s, B()) - @test f(s, 5) == 6 - @test s.prop == 1 - s.prop = 5 - @test s.prop == 5 + # # Component type hierachy. + # # + # # A + # # ┌─┼─┐ + # # B C D + # # + # abstract type A <: Blueprint{Value} end + # struct B <: A end + # struct C <: A end + # struct D <: A end + # Framework.expand!(v, ::B) = (v._member = 1) + # Framework.expand!(v, ::C) = (v._member = 2) + # Framework.expand!(v, ::D) = (v._member = 3) + # @component B + # @component C + # @component D + + # f(v, x) = x + v._member + # get_prop(v) = v._member + # set_prop!(v, x) = (v._member = x) + # @method f depends(A) + # @method get_prop depends(A) read_as(prop) + # @method set_prop! depends(A) write_as(prop) + + # s = System{Value}() + # @sysfails(f(s, 5), Method(f), "Requires a component '$A'.") + # @sysfails(s.prop, Property(prop), "A component '$A' is required to read this property.") + # @sysfails( + # (s.prop = 5), + # Property(prop), + # "A component '$A' is required to write to this property." + # ) + + # # Any subtype enables the method and properties. + # add!(s, B()) + # @test f(s, 5) == 6 + # @test s.prop == 1 + # s.prop = 5 + # @test s.prop == 5 end end diff --git a/test/framework/runtests.jl b/test/framework/runtests.jl index 091c5cafe..4b9bbd255 100644 --- a/test/framework/runtests.jl +++ b/test/framework/runtests.jl @@ -8,10 +8,11 @@ Framework.LOCAL_MACROCALLS = true only = [ # HERE (2024-08-02): all main framework logic has been refactored, # now the tests need to pass again.. but I need to stop for a month or so :'( - "./01-regular_use.jl", - "./02-blueprint_macro.jl", - "./03-component_macro.jl", - "./04-conflicts_macro.jl", + # "./01-regular_use.jl", + # "./02-blueprint_macro.jl", + # "./03-component_macro.jl", + # "./04-conflicts_macro.jl", + "./05-method_macro.jl", ] # Unless some files are specified here, in which case only run these. if isempty(only) for (folder, _, files) in walkdir(dirname(@__FILE__))