diff --git a/.travis.yml b/.travis.yml index fb8791b..e55dcb5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ os: - linux - osx julia: - - 0.6 - nightly # matrix: # allow_failures: diff --git a/NEWS.md b/NEWS.md index de70052..4fabc5b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,8 @@ +# 2018-02-19 + +Transitioned to where-function syntax. Dropped Julia 0.6 support, +with the last release supporting it v0.5.2. + # 2017-11-23 Dropped Julia 0.5 support, last release supporting it is v0.5.1 diff --git a/README.md b/README.md index aa5c03b..e827f4d 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ [![Build Status](https://ci.appveyor.com/api/projects/status/github/mauro3/SimpleTraits.jl?branch=master&svg=true)](https://ci.appveyor.com/project/mauro3/simpletraits-jl/branch/master) [NEWS](NEWS.md) -[![SimpleTraits](http://pkg.julialang.org/badges/SimpleTraits_0.5.svg)](http://pkg.julialang.org/?pkg=SimpleTraits) [![SimpleTraits](http://pkg.julialang.org/badges/SimpleTraits_0.6.svg)](http://pkg.julialang.org/?pkg=SimpleTraits) +[![SimpleTraits](http://pkg.julialang.org/badges/SimpleTraits_0.7.svg)](http://pkg.julialang.org/detail/SimpleTraits) This package provides a macro-based implementation of traits, using [Tim Holy's trait trick](https://github.com/JuliaLang/julia/issues/2345#issuecomment-54537633). @@ -78,15 +78,15 @@ Notes: It can be checked whether a type belongs to a trait with `istrait`: ```julia -using Base.Test +using Test @test istrait(IsNice{Int}) @test !istrait(BelongTogether{Int,Int}) # only BelongTogether{Int,String} was added above ``` Functions which dispatch on traits are constructed like: ```julia -@traitfn f{X; IsNice{X}}(x::X) = "Very nice!" -@traitfn f{X; !IsNice{X}}(x::X) = "Not so nice!" +@traitfn f(x::X) where {X; IsNice{X}} = "Very nice!" +@traitfn f(x::X) where {X; !IsNice{X}} = "Not so nice!" ``` This means that a type `X` which is part of the trait `IsNice` will dispatch to the method returning `"Very nice!"`, otherwise to the one @@ -101,11 +101,11 @@ function. Thus there is no extra mental gymnastics required for a Similarly for `BelongTogether` which has two parameters: ```julia -@traitfn f{X,Y; BelongTogether{X,Y}}(x::X,y::Y) = "$x and $y forever!" +@traitfn f(x::X,y::Y) where {X,Y; BelongTogether{X,Y}} = "$x and $y forever!" @test f(5, "b")=="5 and b forever!" @test_throws MethodError f(5, 5) -@traitfn f{X,Y; !BelongTogether{X,Y}}(x::X,y::Y) = "$x and $y cannot stand each other!" +@traitfn f(x::X,y::Y) where {X,Y; !BelongTogether{X,Y}} = "$x and $y cannot stand each other!" @test f(5, 5)=="5 and 5 cannot stand each other!" ``` @@ -172,8 +172,8 @@ Example: @traitdef Tr{X} fn(x::Integer) = 1 # a normal method -@traitfn fn{X<:AbstractFloat; Tr{X}}(x::X) = 2 -@traitfn fn{X<:AbstractFloat; !Tr{X}}(x::X) = 3 +@traitfn fn(x::X) where {X<:AbstractFloat; Tr{X}} = 2 +@traitfn fn(x::X) where {X<:AbstractFloat; !Tr{X}} = 3 @traitimpl Tr{Float32} @traitimpl Tr{Int} # this does not impact dispatch of `fn` @@ -188,7 +188,7 @@ one trait. Continuing above example, this *does not work* as one may expect: ```julia @traitdef Tr2{X} -@traitfn fn{X<:AbstractFloat; Tr2{X}}(x::X) = 4 +@traitfn fn(x::X) where {X<:AbstractFloat; Tr2{X}} = 4 @traitimpl Tr2{Float16} fn(Float16(5)) # -> 4; dispatch through traits @@ -196,7 +196,7 @@ fn(Float32(5)) # -> MethodError; method defined in previous example # was overwritten above ``` This last definition of `fn` just overwrites the definition `@traitfn -f{X; Tr{X}}(x::X) = 2` from above. +f(x::X) where {X; Tr{X}} = 2` from above. If you need to dispatch on several traits in a single trait-method, then you're out of luck. But please voice your grievance over in pull @@ -327,7 +327,7 @@ Julia 0.5 one could use a generated function but not anymore in Julia 0.6.) Note also that trait functions can be generated functions: ```julia -@traitfn @generated fg{X; IsNice{X}}(x::X) = (println(x); :x) +@traitfn @generated fg(x::X) where {X; IsNice{X}} = (println(x); :x) ``` # Innards @@ -346,16 +346,16 @@ julia> macroexpand(:(@traitimpl Tr{Int})) SimpleTraits.trait{X1 <: Int}(::Type{Tr{X1}}) = Tr{X1} SimpleTraits.istrait{X1 <: Int}(::Type{Tr{X1}}) = true # for convenience, really -julia> macroexpand(:(@traitfn g{X; Tr{X}}(x::X)= x+1)) +julia> macroexpand(:(@traitfn g(x::X) where {X; Tr{X}}= x+1)) -@inline g{X}(x::X) = g(trait(Tr{X}), x) # this is Tim's trick, using above grouping-function -g{X}(::Type{Tr{X}},x::X) = x + 1 # this is the logic +@inline g(x::X) where {X} = g(trait(Tr{X}), x) # this is Tim's trick, using above grouping-function +g(::Type{Tr{X}},x::X) where {X} = x + 1 # this is the logic -julia> macroexpand(:(@traitfn g{X; !Tr{X}}(x::X)= x+1000)) +julia> macroexpand(:(@traitfn g(x::X) where {X; !Tr{X}}= x+1000)) # the trait dispatch helper function needn't be defined twice, # only the logic: -g{X}(::Type{ Not{Tr{X}} }, x::X) = x + 1000 +g(::Type{ Not{Tr{X}} }, x::X) where {X} = x + 1000 ``` For a detailed explanation of how Tim's trick works, see @@ -372,8 +372,8 @@ definitions. Example, dispatch on whether an argument is immutable or not: ```julia -@traitfn f{X; IsImmutable{X}}(x::X) = X(x.fld+1) # make a new instance -@traitfn f{X; !IsImmutable{X}}(x::X) = (x.fld += 1; x) # update in-place +@traitfn f(x::X) where {X; IsImmutable{X}} = X(x.fld+1) # make a new instance +@traitfn f(x::X) where {X; !IsImmutable{X}} = (x.fld += 1; x) # update in-place # use mutable struct A; fld end diff --git a/REQUIRE b/REQUIRE index 2110260..d4b1e2d 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,3 +1,2 @@ -julia 0.6 -Compat 0.26.0 +julia 0.7 MacroTools 0.3.2 diff --git a/appveyor.yml b/appveyor.yml index 2c5a5c5..4ddedc1 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,7 +1,5 @@ environment: matrix: - - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/0.6/julia-0.6-latest-win32.exe" - - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/0.6/julia-0.6-latest-win64.exe" - JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x86/julia-latest-win32.exe" - JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x64/julia-latest-win64.exe" diff --git a/src/SimpleTraits.jl b/src/SimpleTraits.jl index 4c06bd0..f243c65 100644 --- a/src/SimpleTraits.jl +++ b/src/SimpleTraits.jl @@ -2,8 +2,9 @@ __precompile__() module SimpleTraits using MacroTools -using Compat -const curmod = module_name(@__MODULE__) +const curmod = nameof(@__MODULE__) + +import InteractiveUtils # This is basically just adding a few convenience functions & macros # around Holy Traits. @@ -41,9 +42,9 @@ it with Not{}, e.g. Not{Tr1{X,Y}} Not # Helper to strip an even number of Not{}s off: Not{Not{T}}->T -stripNot{T<:Trait}(::Type{T}) = T -stripNot{T<:Trait}(::Type{Not{T}}) = Not{T} -stripNot{T<:Trait}(::Type{Not{Not{T}}}) = stripNot(T) +stripNot(::Type{T}) where {T<:Trait} = T +stripNot(::Type{Not{T}}) where {T<:Trait} = Not{T} +stripNot(::Type{Not{Not{T}}}) where {T<:Trait} = stripNot(T) """ A trait is defined as full-filled if this function is the identity @@ -61,14 +62,14 @@ Usually this function is defined when using the `@traitimpl` macro. However, instead of using `@traitimpl` one can define a method for `trait` to implement a trait, see the README. """ -trait{T<:Trait}(::Type{T}) = Not{T} -trait{T<:Trait}(::Type{Not{T}}) = trait(T) +trait(::Type{T}) where {T<:Trait} = Not{T} +trait(::Type{Not{T}}) where {T<:Trait} = trait(T) ## Under the hood, a trait is then implemented for specific types by ## defining: # trait(::Type{Tr1{Int,Float64}}) = Tr1{Int,Float64} # or -# trait{I<:Integer,F<:FloatingPoint}(::Type{Tr1{I,F}}) = Tr1{I,F} +# trait(::Type{Tr1{I,F}}) where {I<:Integer,F<:FloatingPoint} = Tr1{I,F} # # Note due to invariance, this does probably not the right thing: # trait(::Type{Tr1{Integer,FloatingPoint}}) = Tr1{Integer, FloatingPoint} @@ -81,7 +82,7 @@ istrait(Tr1{Int,Float64}) => return true or false ``` """ istrait(::Any) = error("Argument is not a Trait.") -istrait{T<:Trait}(tr::Type{T}) = trait(tr)==stripNot(tr) ? true : false # Problem, this can run into issue #265 +istrait(tr::Type{T}) where {T<:Trait} = trait(tr)==stripNot(tr) ? true : false # Problem, this can run into issue #265 # thus is redefine when traits are defined """ @@ -146,8 +147,8 @@ macro traitimpl(tr) push!(paras, esc(v)) end arg = :(::Type{$trname{$(paras...)}}) - fnhead = :($curmod.trait{$(curly...)}($arg)) - isfnhead = :($curmod.istrait{$(curly...)}($arg)) + fnhead = :($curmod.trait($arg) where {$(curly...)}) + isfnhead = :($curmod.istrait($arg) where {$(curly...)}) if !negated return quote $fnhead = $trname{$(paras...)} @@ -169,8 +170,8 @@ macro traitimpl(tr) fn = Expr(:call, GlobalRef(SimpleTraits, :!), fn) end return esc(quote - function SimpleTraits.trait{$(P1...)}(::Type{$Tr{$(P1...)}}) - return $fn($(P2...)) ? $Tr{$(P1...)} : Not{$Tr{$(P1...)}} + function SimpleTraits.trait(::Type{$Tr{$(P1...)}}) where {$(P1...)} + return $fn($(P2...)) ? $Tr{$(P1...)} : Not{$Tr{$(P1...)}} end nothing end) @@ -180,16 +181,16 @@ macro traitimpl(tr) end # Defining a function dispatching on the trait (or not) -# @traitfn f{X,Y; Tr1{X,Y}}(x::X,y::Y) = ... -# @traitfn f{X,Y; !Tr1{X,Y}}(x::X,y::Y) = ... # which is just sugar for: -# @traitfn f{X,Y; Not{Tr1{X,Y}}}(x::X,y::Y) = ... +# @traitfn f(x::X,y::Y) where {X,Y; Tr1{X,Y}} = ... +# @traitfn f(x::X,y::Y) where {X,Y; !Tr1{X,Y}} = ... # which is just sugar for: +# @traitfn f(x::X,y::Y) where {X,Y; Not{Tr1{X,Y}}} = ... dispatch_cache = Dict() # to ensure that the trait-dispatch function is defined only once per pair let global traitfn - function traitfn(tfn, cur_module) + function traitfn(tfn, macro_module) # Need - # f{X,Y}(x::X,Y::Y) = f(trait(Tr1{X,Y}), x, y) + # f(x::X,Y::Y) where {X,Y} = f(trait(Tr1{X,Y}), x, y) # f(::False, x, y)= ... if tfn.head==:macrocall hasmac = true @@ -211,34 +212,61 @@ let # trait: expression of the trait # trait0: expression of the trait without any gensym'ed symbols # + # oldfn_syntax: set to true if the old parametric syntax is used + # # (The variables without gensym'ed symbols are mostly used for the key of the dispatch cache) fhead = tfn.args[1] fbody = tfn.args[2] - fname, paras, args0, kwargs = @match fhead begin - f_{paras__}(args0__;kwargs__) => (f,paras,args0,kwargs) - f_(args0__; kwargs__) => (f,[],args0,kwargs) - f_{paras__}(args0__) => (f,paras,args0,[]) - f_(args0__) => (f,[],args0,[]) + # TODO 1.0: remove + out = @match fhead begin + f_{paras__}(args0__;kwargs__) => (f,paras,args0,kwargs,true) + f_(args0__; kwargs__) => (f,[],args0,kwargs,false) + f_{paras__}(args0__) => (f,paras,args0,[],true) + f_(args0__) => (f,[],args0,[],false) + f_(args0__; kwargs__) where paras__ => (f,paras,args0,kwargs,false) + f_(args0__) where paras__ => (f,paras,args0,[],false) end + if out==nothing + error("Could not parse function-head: $fhead. Note that several `where` are not supported.") + end + fname, paras, args0, kwargs, oldfn_syntax = out haskwargs = length(kwargs)>0 - if length(paras)>0 && isa(paras[1],Expr) && paras[1].head==:parameters - # this is a Traits.jl style function - trait = paras[1].args[1] + # extract parameters and traits from paras and/or args0 + + if length(paras)==0 + maybe_traitor = true + else + if paras[1] isa Expr && paras[1].head==:parameters + maybe_traitor = false + length(paras)<2 && error("Cannot parse function parameters: $para") + typs = paras[2:end] + trait = paras[1].args[1] + elseif paras[1] isa Expr && paras[1].head==:bracescat + maybe_traitor = false + length(paras)!=1 && error("Cannot parse function parameters: $para") + typs = paras[1].args[1:1] + trait = paras[1].args[2] + else + maybe_traitor = true + # the processing happens below + end + end + if !maybe_traitor trait0 = trait # without gensym'ed types, here identical - typs = paras[2:end] typs0 = typs # without gensym'ed types, here identical args1 = insertdummy(args0) - else - # This is a Traitor.jl style function. Change it into a Traits.jl function. + else # This is a Traitor.jl style function (or invalid). + # Change paras & args0 into a Traits.jl function. # Find the traitor: typs0 = deepcopy(paras) # without gensym'ed types typs = paras out = nothing i = 0 # index of function argument with Traitor trait vararg = false - for (i,a) in enumerate(args0) + for outer i in eachindex(args0) + a = args0[i] vararg = a.head==:... if vararg a = a.args[1] @@ -290,34 +318,64 @@ let else pushloc = poploc = nothing end - # create the function containing the logic + # create the function containing the logic. Do it separately if old-school parametric functions + # (TODO: delete the oldfn_syntax branches in Julia 1.0 (or 0.7 already?)) retsym = gensym() if hasmac - fn = :(@dummy $fname{$(typs...)}($val, $(strip_kw(args1)...); $(kwargs...)) = ($pushloc; $retsym = $fbody; $poploc; $retsym)) + fn = if oldfn_syntax + :(@dummy $fname{$(typs...)}($val, $(strip_kw(args1)...); $(kwargs...)) = ($pushloc; $retsym = $fbody; $poploc; $retsym)) + else + :(@dummy $fname($val, $(strip_kw(args1)...); $(kwargs...)) where {$(typs...)} = ($pushloc; $retsym = $fbody; $poploc; $retsym)) + end fn.args[1] = mac # replace @dummy else - fn = :($fname{$(typs...)}($val, $(strip_kw(args1)...); $(kwargs...)) = ($pushloc; $retsym = $fbody; $poploc; $retsym)) + fn = if oldfn_syntax + :($fname{$(typs...)}($val, $(strip_kw(args1)...); $(kwargs...)) = ($pushloc; $retsym = $fbody; $poploc; $retsym)) + else + :($fname($val, $(strip_kw(args1)...); $(kwargs...)) where {$(typs...)} = ($pushloc; $retsym = $fbody; $poploc; $retsym)) + end end # Create the trait dispatch function ex = fn - key = (cur_module, fname, typs0, strip_kw(args0), trait0_opposite) + key = (macro_module, fname, typs0, strip_kw(args0), trait0_opposite) if !(key ∈ keys(dispatch_cache)) # define trait dispatch function if !haskwargs - ex = quote - $fname{$(typs...)}($(args1...)) = (Base.@_inline_meta(); $fname($curmod.trait($trait), - $(strip_tpara(strip_kw(args1))...) - ) - ) - $ex + ex = if oldfn_syntax + quote + $fname{$(typs...)}($(args1...)) = (Base.@_inline_meta(); $fname($curmod.trait($trait), + $(strip_tpara(strip_kw(args1))...) + ) + ) + $ex + end + else + quote + $fname($(args1...)) where {$(typs...)} = (Base.@_inline_meta(); $fname($curmod.trait($trait), + $(strip_tpara(strip_kw(args1))...) + ) + ) + $ex + end end else - ex = quote - $fname{$(typs...)}($(args1...);kwargs...) = (Base.@_inline_meta(); $fname($curmod.trait($trait), - $(strip_tpara(strip_kw(args1))...); - kwargs... - ) - ) - $ex + ex = if oldfn_syntax + quote + $fname{$(typs...)}($(args1...);kwargs...) = (Base.@_inline_meta(); $fname($curmod.trait($trait), + $(strip_tpara(strip_kw(args1))...); + kwargs... + ) + ) + $ex + end + else + quote + $fname($(args1...);kwargs...) where {$(typs...)} = (Base.@_inline_meta(); $fname($curmod.trait($trait), + $(strip_tpara(strip_kw(args1))...); + kwargs... + ) + ) + $ex + end end end dispatch_cache[key] = (haskwargs, args0) @@ -344,21 +402,17 @@ end Defines a function dispatching on a trait. Examples: ```julia -@traitfn f{X; Tr1{X}}(x::X,y) = ... -@traitfn f{X; !Tr1{X}}(x::X,y) = ... +@traitfn f(x::X,y) where {X; Tr1{X}} = ... +@traitfn f(x::X,y) where {X; !Tr1{X}} = ... -@traitfn f{X,Y; Tr2{X,Y}}(x::X,y::Y) = ... -@traitfn f{X,Y; !Tr2{X,Y}}(x::X,y::Y) = ... +@traitfn f(x::X,y::Y) where {X,Y; Tr2{X,Y}} = ... +@traitfn f(x::X,y::Y) where {X,Y; !Tr2{X,Y}} = ... ``` -Note that the second example is just syntax sugar for `@traitfn f{X,Y; Not{Tr1{X,Y}}}(x::X,y::Y) = ...`. +Note that the second example is just syntax sugar for `@traitfn f(x::X,y::Y) where {X,Y; Not{Tr1{X,Y}}} = ...`. """ macro traitfn(tfn) - @static if isdefined(Base, Symbol("@__MODULE__")) - esc(traitfn(tfn, __module__)) - else - esc(traitfn(tfn, current_module())) - end + esc(traitfn(tfn, __module__)) end ###### @@ -503,7 +557,7 @@ end "Returns number of llvm-IR lines for a call of function `fn` with argument types `args`" function llvm_lines(fn, args) io = IOBuffer() - Base.code_llvm(io, fn, args) + InteractiveUtils.code_llvm(io, fn, args) #Base.code_native(io, fn, args) count(c->c=='\n', String(take!(copy(io)))) end diff --git a/src/base-traits.jl b/src/base-traits.jl index 7825124..0cc02a4 100644 --- a/src/base-traits.jl +++ b/src/base-traits.jl @@ -68,8 +68,8 @@ Base.@deprecate_binding IsFastLinearIndex IsIndexLinear "Trait of all iterator types" @traitdef IsIterator{X} -@generated function SimpleTraits.trait{X}(::Type{IsIterator{X}}) - method_exists(start, Tuple{X}) ? :(IsIterator{X}) : :(Not{IsIterator{X}}) +@generated function SimpleTraits.trait(::Type{IsIterator{X}}) where {X} + hasmethod(start, Tuple{X}) ? :(IsIterator{X}) : :(Not{IsIterator{X}}) end end # module diff --git a/test/backtraces.jl b/test/backtraces.jl index 1b5cda3..5bc069a 100644 --- a/test/backtraces.jl +++ b/test/backtraces.jl @@ -3,8 +3,8 @@ @traitimpl BacktraceTr{Integer} -@traitfn foo{X; BacktraceTr{X}}(A::X) = backtrace() -@traitfn foo{X; !BacktraceTr{X}}(A::X) = backtrace() +@traitfn foo(A::X) where {X; BacktraceTr{X}} = backtrace() +@traitfn foo(A::X) where {X; !BacktraceTr{X}} = backtrace() ###### END: LINE NUMBER SENSITIVITY ########## # (the tests below depend on the particular line numbers in the code above) diff --git a/test/base-traits-inference.jl b/test/base-traits-inference.jl index 4addbad..51ba999 100644 --- a/test/base-traits-inference.jl +++ b/test/base-traits-inference.jl @@ -5,7 +5,7 @@ # Dict with base-traits to check using value[1] as type and value[2] # as number of lines allowed in llvm code cutoff = 5 -basetrs = [VERSION:Int : :IsConcrete=>:Int, +basetrs = [:IsConcrete=>:Int, :IsBits=>:Int, :IsImmutable=>:Int, :IsContiguous=>:(SubArray{Int64,1,Array{Int64,1},Tuple{Array{Int64,1}},false}), diff --git a/test/base-traits.jl b/test/base-traits.jl index 87445bc..8fae39e 100644 --- a/test/base-traits.jl +++ b/test/base-traits.jl @@ -1,5 +1,4 @@ using SimpleTraits.BaseTraits -using Compat: view @test istrait(IsAnything{Any}) @test istrait(IsAnything{Union{}}) diff --git a/test/runtests-oldfn-syntax.jl b/test/runtests-oldfn-syntax.jl new file mode 100644 index 0000000..57e9bc5 --- /dev/null +++ b/test/runtests-oldfn-syntax.jl @@ -0,0 +1,246 @@ +using SimpleTraits +using Test + +const trait = SimpleTraits.trait + +# @test_throws MethodError trait(4) +@test_throws ErrorException istrait(4) + +# definition & adding +@traitdef Tr1{X} +@test trait(Tr1{Int})==Not{Tr1{Int}} +@test !istrait(Tr1{Int}) +@traitimpl Tr1{Integer} +@test trait(Tr1{Int})==Tr1{Int} +@test istrait(Tr1{Int}) +@test trait(Tr1{Bool})==Tr1{Bool} +@test trait(Tr1{AbstractString})==Not{Tr1{AbstractString}} +@test !istrait(Tr1{AbstractString}) + +# Logic. trait(Tr) returns the same trait Tr if it is fulfilled and +# Not{Tr} otherwise. This is a bit confusing. +@test trait(Tr1{AbstractString})==Not{Tr1{AbstractString}} +@test istrait(Tr1{AbstractString})==false +@test trait(Not{Tr1{AbstractString}})==Not{Tr1{AbstractString}} +@test istrait(Not{Tr1{AbstractString}})==true +@test trait(Not{Not{Tr1{AbstractString}}})==Not{Tr1{AbstractString}} +@test istrait(Not{Not{Tr1{AbstractString}}})==false +@test trait(Not{Not{Not{Tr1{AbstractString}}}})==Not{Tr1{AbstractString}} +@test istrait(Not{Not{Not{Tr1{AbstractString}}}})==true + +@test trait(Not{Tr1{Integer}})==Tr1{Integer} +@test istrait(Not{Tr1{Integer}})==false +@test trait(Not{Not{Tr1{Integer}}})==Tr1{Integer} +@test istrait(Not{Not{Tr1{Integer}}})==true +@test trait(Not{Not{Not{Tr1{Integer}}}})==Tr1{Integer} +@test istrait(Not{Not{Not{Tr1{Integer}}}})==false + + +@traitdef Tr2{X,Y} +@test trait(Tr2{Int,AbstractFloat})==Not{Tr2{Int,AbstractFloat}} +@traitimpl Tr2{Integer, Float64} +@test trait(Tr2{Int, Float64})==Tr2{Int, Float64} +@test trait(Tr2{Int, Float32})==Not{Tr2{Int, Float32}} + +# issue 9 +abstract type A9 end +struct B9<:A9 end +struct C9<:A9 end +@traitdef Tr9{X} +@traitimpl Tr9{A9} +@traitimpl Not{Tr9{B9}} +@traitimpl !Tr9{C9} +@test istrait(Tr9{A9})==true +@test istrait(Tr9{B9})==false +@test istrait(Tr9{C9})==false + +# issue #15 is tested through the base-traits.jl tests. + +################# +# Trait functions +################# +# functions with `t` postfix are the same as previous ones but using Traitor syntax +@traitfn f{X; Tr1{X}}(x::X) = 1 # def 1 +@traitfn f{X; !Tr1{X}}(x::X) = 2 +@test f(5)==1 +@test f(5.)==2 + +@traitfn ft(x::::Tr1) = 1 # def 1 +@traitfn ft(x::::(!Tr1)) = 2 +@test ft(5)==1 +@test ft(5.)==2 + + +@traitfn f{X,Y; Tr2{X,Y}}(x::X,y::Y,z) = 1 +@test f(5,5., "a")==1 +@test_throws MethodError f(5,5, "a")==2 +@traitfn f{X,Y; !Tr2{X,Y}}(x::X,y::Y,z) = 2 +@test f(5,5, "a")==2 +# Note, two argument traits have no Traitor style syntax + +# This will overwrite the definition def1 above + +@traitfn f{X; !Tr2{X,X}}(x::X) = 10 +@traitfn f{X; Tr2{X,X}}(x::X) = 100 +@test f(5)==10 +@test f(5.)==10 +@traitimpl Tr2{Integer, Integer} +@test f(5.)==10 +@test_broken f(5)==100 +@test f(5.)==10 + +# VarArg +@traitfn vara{X; Tr1{X}}(x::X, y...) = y +@traitfn vara{X; !Tr1{X}}(x::X, y...) = x +@test vara(5, 7, 8)==(7,8) +@test vara(5.0, 7, 8)==5.0 +@traitfn vara2{X; Tr1{X}}(x::X...) = x +@test vara2(5, 7, 8)==(5, 7, 8) +@test_throws MethodError vara2(5, 7, 8.0) + +@traitfn vara3{X; Tr1{X}}(::X...) = X +@test vara3(5, 7, 8)==Int +@test_throws MethodError vara3(5, 7, 8.0) + + +@traitfn varat(x::::Tr1, y...) = y +@traitfn varat(x::::(!Tr1), y...) = x +@test varat(5, 7, 8)==(7,8) +@test varat(5.0, 7, 8)==5.0 +@traitfn vara2t(x::::Tr1...) = x +@test vara2t(5, 7, 8)==(5, 7, 8) +@test_throws MethodError vara2t(5, 7, 8.0) + +@traitfn vara3t{X}(::X::Tr1...) = X +@test vara3t(5, 7, 8)==Int +@test_throws MethodError vara3t(5, 7, 8.0) + +# kwargs (issue 27) +@traitfn kwfn1(x::::Tr1; k=1) = x+k +@traitfn kwfn1(x::::(!Tr1); k=2) = x-k +@test kwfn1(5)==6 +@test kwfn1(5.0)==3.0 +@test kwfn1(5,k=2)==7 +@test kwfn1(5.0,k=3)==2.0 + +@traitfn kwfn2(x::::Tr1, y...; k=1) = x+y[1]+k +@traitfn kwfn2(x::::(!Tr1), y...; k::Int=2) = x+y[1]-k +@test kwfn2(5,5)==11 +@test kwfn2(5.0,5)==8.0 +@test kwfn2(5,5,k=2)==12 +@test kwfn2(5.0,5,k=3)==7.0 +@test_throws TypeError kwfn2(5.0,5,k="sadf") + +@traitfn kwfn3(x::::Tr1, y...; kws...) = x+y[1]+length(kws) +@traitfn kwfn3(x::::(!Tr1), y...; kws...) = x+y[1]-length(kws) +@test kwfn3(5,5)==10 +@test kwfn3(5.0,5)==10 +@test kwfn3(5,5,k=2)==11 +@test kwfn3(5.0,5,k=3)==9 +@test kwfn3(5.0,5,k=3,kk=9)==8 + +@traitfn kwfn4(x::::Tr1, y...; kws...) = x+y[1]+length(kws) +@test_throws ErrorException @traitfn kwfn4(x::::(!Tr1), y...) = x+y[1]-length(kws) +@traitfn kwfn5(x::::Tr1, y...) = x+y[1]+length(kws) +@test_throws ErrorException @traitfn kwfn5(x::::(!Tr1), y...; k=1) = x+y[1]-length(kws) + +# Default args, issue #32 +#### +@traitfn defargs1(x::::Tr1, y=2) = x+y +@traitfn defargs1(x::::(!Tr1), y=2) = x-y +@test defargs1(1,3)==4 +@test defargs1(1)==3 +@test defargs1(1.0,4)==-3 +@test defargs1(1.0)==-1 + +@traitfn defargs2(x::::Tr1, y=2) = x+y +@test_throws ErrorException @traitfn defargs2(x::::(!Tr1), y=3) = x-y +@traitfn defargs3(x::::Tr1, y=2) = x+y +@test_throws ErrorException @traitfn defargs3(x::::(!Tr1), y) = x-y + + +@traitfn defargs4(x::::Tr1, y=2; k=1) = x+y+k +@traitfn defargs4(x::::(!Tr1), y=2; k=2) = x-y+k +@test defargs4(1,3)==5 +@test defargs4(1)==4 +@test defargs4(1.0,4)==-1 +@test defargs4(1.0)==1 +@test defargs4(1.0, k=10)==9 + +@traitfn defargs5(x::::Tr1, y=2, z...; k=1) = (x+y+k,z) +@traitfn defargs5(x::::(!Tr1), y=2, z...; k=2) = (x-y+k,z) +@test defargs5(1,3)==(5,()) +@test defargs5(1,3,4,5)==(5,(4,5)) +@test defargs5(1)==(4,()) +@test defargs5(1.0,4)==(-1,()) +@test defargs5(1.0,k=10)==(9,()) + +@traitfn defargs6{X; Tr1{X}}(x::X=1, y=2) = x+y +@traitfn defargs6{X; !Tr1{X}}(x::X=1, y=2) = x-y +@test defargs6()==3 +@test defargs6(1,3)==4 +@test defargs6(1)==3 +@test defargs6(1.0,4)==-3 +@test defargs6(1.0)==-1 +# above does not work with Traitor syntax +@test_broken SimpleTraits.traitfn(:(defargs6a(x::::Tr1=1, y=2) = x+y)) + + +# traitfn with macro +@traitfn @inbounds gg{X; Tr1{X}}(x::X) = x +@test gg(5)==5 +@traitfn @generated ggg{X; Tr1{X}}(x::X) = X<:AbstractArray ? :(x+x) : :(x) +@test ggg(5)==5 +@traitimpl Tr1{AbstractArray} +@test ggg([5])==[10] + +@traitfn @inbounds ggt(x::::Tr1) = x +@test ggt(5)==5 +@traitfn @generated gggt{X}(x::X::Tr1) = X<:AbstractArray ? :(x+x) : :(x) +@test gggt(5)==5 +@test gggt([5])==[10] + + +# traitfn with Type +@traitfn ggt{X; Tr1{X}}(::Type{X}, y) = (X,y) +@test ggt(Array, 5)==(Array, 5) +# no equivalent with Traitor syntax + +# traitfn with ::X +@traitfn gg27{X; Tr1{X}}(::X) = X +@test gg27([1])==Array{Int,1} + +@traitfn gg27t{X}(::X::Tr1) = X +@test gg27t([1])==Array{Int,1} + +## +@traitfn f11{T<:Number; Tr1{Dict{T}}}(x::Dict{T}) = 1 +@traitfn f11{T<:Number; !Tr1{Dict{T}}}(x::Dict{T}) = 2 +@traitimpl Tr1{Dict{Int}} +@test f11(Dict(1=>1))==1 +@test f11(Dict(5.5=>1))==2 + +@traitfn f11t{T<:Number}(x::Dict{T}::Tr1) = 1 +@traitfn f11t{T<:Number}(x::Dict{T}::(!Tr1)) = 2 +@test f11t(Dict(1=>1))==1 +@test f11t(Dict(5.5=>1))==2 + +## +@traitfn f12t(::::Tr1) = 1 +@traitfn f12t(::::(!Tr1)) = 2 +@test f12t(1)==1 +@test f12t(5.5)==2 + + +### +# @traitimpl Tr{X} <- istr(X) +@traitdef TrArrow1{X} +isarrow(X) = eltype(X)<:Integer ? true : false +@traitimpl TrArrow1{X} <- isarrow(X) +@test istrait(TrArrow1{Vector{Int}}) +@test !istrait(TrArrow1{Vector{Float64}}) + +@traitdef TrArrow2{X} +@traitimpl Not{TrArrow2{X}} <- isarrow(X) +@test !istrait(TrArrow2{Vector{Int}}) +@test istrait(TrArrow2{Vector{Float64}}) diff --git a/test/runtests.jl b/test/runtests.jl index 5f885cf..b495dd2 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,5 +1,5 @@ using SimpleTraits -using Base.Test +using Test const trait = SimpleTraits.trait @@ -60,8 +60,8 @@ struct C9<:A9 end # Trait functions ################# # functions with `t` postfix are the same as previous ones but using Traitor syntax -@traitfn f{X; Tr1{X}}(x::X) = 1 # def 1 -@traitfn f{X; !Tr1{X}}(x::X) = 2 +@traitfn f(x::X) where {X; Tr1{X}} = 1 # def 1 +@traitfn f(x::X) where {X; !Tr1{X}} = 2 @test f(5)==1 @test f(5.)==2 @@ -71,17 +71,17 @@ struct C9<:A9 end @test ft(5.)==2 -@traitfn f{X,Y; Tr2{X,Y}}(x::X,y::Y,z) = 1 +@traitfn f(x::X,y::Y,z) where {X,Y; Tr2{X,Y}} = 1 @test f(5,5., "a")==1 @test_throws MethodError f(5,5, "a")==2 -@traitfn f{X,Y; !Tr2{X,Y}}(x::X,y::Y,z) = 2 +@traitfn f(x::X,y::Y,z) where {X,Y; !Tr2{X,Y}} = 2 @test f(5,5, "a")==2 # Note, two argument traits have no Traitor style syntax # This will overwrite the definition def1 above -@traitfn f{X; !Tr2{X,X}}(x::X) = 10 -@traitfn f{X; Tr2{X,X}}(x::X) = 100 +@traitfn f(x::X) where {X; !Tr2{X,X}} = 10 +@traitfn f(x::X) where {X; Tr2{X,X}} = 100 @test f(5)==10 @test f(5.)==10 @traitimpl Tr2{Integer, Integer} @@ -90,15 +90,15 @@ struct C9<:A9 end @test f(5.)==10 # VarArg -@traitfn vara{X; Tr1{X}}(x::X, y...) = y -@traitfn vara{X; !Tr1{X}}(x::X, y...) = x +@traitfn vara(x::X, y...) where {X; Tr1{X}} = y +@traitfn vara(x::X, y...) where {X; !Tr1{X}} = x @test vara(5, 7, 8)==(7,8) @test vara(5.0, 7, 8)==5.0 -@traitfn vara2{X; Tr1{X}}(x::X...) = x +@traitfn vara2(x::X...) where {X; Tr1{X}} = x @test vara2(5, 7, 8)==(5, 7, 8) @test_throws MethodError vara2(5, 7, 8.0) -@traitfn vara3{X; Tr1{X}}(::X...) = X +@traitfn vara3(::X...) where {X; Tr1{X}} = X @test vara3(5, 7, 8)==Int @test_throws MethodError vara3(5, 7, 8.0) @@ -111,7 +111,7 @@ struct C9<:A9 end @test vara2t(5, 7, 8)==(5, 7, 8) @test_throws MethodError vara2t(5, 7, 8.0) -@traitfn vara3t{X}(::X::Tr1...) = X +@traitfn vara3t(::X::Tr1...) where {X} = X @test vara3t(5, 7, 8)==Int @test_throws MethodError vara3t(5, 7, 8.0) @@ -175,8 +175,8 @@ struct C9<:A9 end @test defargs5(1.0,4)==(-1,()) @test defargs5(1.0,k=10)==(9,()) -@traitfn defargs6{X; Tr1{X}}(x::X=1, y=2) = x+y -@traitfn defargs6{X; !Tr1{X}}(x::X=1, y=2) = x-y +@traitfn defargs6(x::X=1, y=2) where {X; Tr1{X}} = x+y +@traitfn defargs6(x::X=1, y=2) where {X; !Tr1{X}} = x-y @test defargs6()==3 @test defargs6(1,3)==4 @test defargs6(1)==3 @@ -187,41 +187,41 @@ struct C9<:A9 end # traitfn with macro -@traitfn @inbounds gg{X; Tr1{X}}(x::X) = x +@traitfn @inbounds gg(x::X) where {X; Tr1{X}} = x @test gg(5)==5 -@traitfn @generated ggg{X; Tr1{X}}(x::X) = X<:AbstractArray ? :(x+x) : :(x) +@traitfn @generated ggg(x::X) where {X; Tr1{X}} = X<:AbstractArray ? :(x+x) : :(x) @test ggg(5)==5 @traitimpl Tr1{AbstractArray} @test ggg([5])==[10] @traitfn @inbounds ggt(x::::Tr1) = x @test ggt(5)==5 -@traitfn @generated gggt{X}(x::X::Tr1) = X<:AbstractArray ? :(x+x) : :(x) +@traitfn @generated gggt(x::X::Tr1) where {X} = X<:AbstractArray ? :(x+x) : :(x) @test gggt(5)==5 @test gggt([5])==[10] # traitfn with Type -@traitfn ggt{X; Tr1{X}}(::Type{X}, y) = (X,y) +@traitfn ggt(::Type{X}, y) where {X; Tr1{X}} = (X,y) @test ggt(Array, 5)==(Array, 5) # no equivalent with Traitor syntax # traitfn with ::X -@traitfn gg27{X; Tr1{X}}(::X) = X +@traitfn gg27(::X) where {X; Tr1{X}} = X @test gg27([1])==Array{Int,1} -@traitfn gg27t{X}(::X::Tr1) = X +@traitfn gg27t(::X::Tr1) where {X} = X @test gg27t([1])==Array{Int,1} ## -@traitfn f11{T<:Number; Tr1{Dict{T}}}(x::Dict{T}) = 1 -@traitfn f11{T<:Number; !Tr1{Dict{T}}}(x::Dict{T}) = 2 +@traitfn f11(x::Dict{T}) where {T<:Number; Tr1{Dict{T}}} = 1 +@traitfn f11(x::Dict{T}) where {T<:Number; !Tr1{Dict{T}}} = 2 @traitimpl Tr1{Dict{Int}} @test f11(Dict(1=>1))==1 @test f11(Dict(5.5=>1))==2 -@traitfn f11t{T<:Number}(x::Dict{T}::Tr1) = 1 -@traitfn f11t{T<:Number}(x::Dict{T}::(!Tr1)) = 2 +@traitfn f11t(x::Dict{T}::Tr1) where {T<:Number} = 1 +@traitfn f11t(x::Dict{T}::(!Tr1)) where {T<:Number} = 2 @test f11t(Dict(1=>1))==1 @test f11t(Dict(5.5=>1))==2 @@ -263,3 +263,12 @@ end include("base-traits.jl") include("base-traits-inference.jl") include("backtraces.jl") + +##### +# Old function syntax +#### +# throws lots of deprecation warnings! +# TODO remove with Julia 0.7 +module OldFnSyntax +include("runtests-oldfn-syntax.jl") +end