diff --git a/README.md b/README.md index 1234f62..8156bfb 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,9 @@ Note that Julia 0.3 is only supported up to tag This attempts to reduce the complexity of [Traits.jl](https://github.com/mauro3/Traits.jl), but at the same time -staying compatible. On the upside, it also works for Julia-0.3. It -drops support for: +staying compatible (but I think that is not possible currently). On +the upside, it also works for Julia-0.3 (up to tag v0.0.1). It drops +support for: - Trait definition in terms of methods and constraints. Instead the user needs to assign types to traits manually. This removes the @@ -42,7 +43,8 @@ then add types to the traits with Functions which dispatch on traits are constructed like: ```julia -@traitfn f{X; Tr1{X}}(x::X) = 1 +@traitfn f{X; Tr1{X}}(x::X) # initialize +@traitfn f{X; Tr1{X}}(x::X) = 1 @traitfn f{X; !Tr1{X}}(x::X) = 2 ``` This means that a type `X` which is part of the trait `Tr1` will @@ -54,6 +56,7 @@ dispatch to the method returning `1`, otherwise 2. Similarly for `Tr2`: ```julia +@traitfn f{X,Y; Tr2{X,Y}}(x::X,y::Y,z) @traitfn f{X,Y; Tr2{X,Y}}(x::X,y::Y,z) = 1 @test f(5, "b", "a")==1 @test_throws MethodError f(5,5, "a")==2 @@ -63,13 +66,27 @@ Similarly for `Tr2`: Note that for one generic function, dispatch on traits can only work on one trait for a given signature. Continuing above example, this -does not work as one may expect: +does not work: ```julia @traitfn f{X; !Tr2{X,X}}(x::X) = 10 ``` -as this definition will just overwrite the definition `@traitfn f{X; -Tr1{X}}(x::X) = 1` from above. If you need to dispatch on several -traits, then you need Traits.jl. +and will result in a method which never gets called (without an error +being thrown). + +*Dispatch on several traits* + +It is also possible to dispatch on several traits: + +```julia +@traitfn f55{X, Y; TT1{X}, TT2{Y}}(x::X, y::Y) +@traitfn f55{X, Y; TT1{X}, TT2{Y}}(x::X, y::Y) = 1 +@traitfn f55{X, Y; !TT1{X}, TT2{Y}}(x::X, y::Y) = 2 +@traitfn f55{X, Y; TT1{X}, !TT2{Y}}(x::X, y::Y) = 3 +@traitfn f55{X, Y; !TT1{X}, !TT2{Y}}(x::X, y::Y) = 4 +``` +Note that all methods need to feature the same traits (possibly +negated) in the same order. Any method violating that will never be +called (without an error being thrown). ## Advanced features @@ -136,6 +153,14 @@ b2 = f(b) @assert b2==B(2) ``` +# TODO + +- [ ] catch more syntax errors +- [ ] make a debug mode which catches when wrong trait-methods are + defined +- [ ] implement trait-inheritance +- [ ] implement default argument functions +- [ ] implement keyword functions # References @@ -151,3 +176,9 @@ b2 = f(b) ([Jutho's idea](https://github.com/JuliaLang/julia/issues/10889#issuecomment-94317470))? In particular could it be used in such a way that it is compatible with the multiple inheritance used in Traits.jl? + *Now with multi-traits functions this may not be necessary anymore: + the intersection of two traits can divide the types into four groups.* +- For trait Collections, Intersections, and TUnion, should a Union be + used for the underlying storage as that sorts and purges its inputs? + This would rely on un-supported Union behaviour, however it might be + quite nice. diff --git a/src/SimpleTraits.jl b/src/SimpleTraits.jl index 0e04fe2..cd6d2b4 100644 --- a/src/SimpleTraits.jl +++ b/src/SimpleTraits.jl @@ -1,39 +1,96 @@ +# This adds a few convenience functions & macros around Holy Traits. module SimpleTraits const curmod = module_name(current_module()) -# This is basically just adding a few convenience functions & macros -# around Holy Traits. +export Trait, istrait, @traitdef, @traitimpl, @traitfn, Not, + IsAnything, IsNothing -export Trait, istrait, @traitdef, @traitimpl, @traitfn, Not - -# All traits are concrete subtypes of this trait. SUPER is not used -# but present to be compatible with Traits.jl. -## @doc """ -## `abstract Trait{SUPER}` +# General trait exception +type TraitException <: Exception + msg::AbstractString +end """ -All Traits are subtypes of abstract type Trait. (SUPER is not used -here but in Traits.jl) +All Traits are subtypes of the abstract type Trait. The parameter +SUPER contains the Intersection of the super traits for a particular +trait. """ -abstract Trait{SUPER} +abstract Trait{SUPER} # SUPER<:Intersection but that is not possible # a concrete Trait will look like ## immutable Tr1{X,Y} <: Trait end # where X and Y are the types involved in the trait. +# function Base.show{T<:Trait}(io::IO, Tr::Type{T}) +# invoke(show, Tuple{IO, DataType}, io, Tr) +# print(" (a Trait)\n") +# end """ -The set of all types not belonging to a trait is encoded by wrapping +The trait of all types not belonging to a trait is encoded by wrapping it with Not{}, e.g. Not{Tr1{X,Y}} """ -abstract Not{T<:Trait} <: Trait - +immutable Not{T<:Trait} <: Trait end # 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) """ -A trait is defined as full filled if this function is the identity function for that trait. -Otherwise it returns the trait wrapped in `Not`. +`Collection` is used internally when defining a trait-method +like so +``` +@traitfn f55{X, Y; TT1{X}, TT2{Y}}(x::X, y::Y) +@traitfn f55{X, Y; TT1{X}, TT2{Y}}(x::X, y::Y) = 1 +@traitfn f55{X, Y; !TT1{X}, TT2{Y}}(x::X, y::Y) = 2 +@traitfn f55{X, Y; !TT1{X}, !TT2{Y}}(x::X, y::Y) = 3 +``` + +It encodes the collection of traits `((TT1{X}, TT2{Y}),(!TT1{X}, +TT2{Y}),(TT1{X}, !TT2{Y}),(!TT1{X}, !TT2{Y}))`. Therefore, it is not +a single trait but kind of several traits. This is just used inside +trait-functions and should not be used directly. +""" +typealias Collection Tuple +# TODO: should `typealias Collection Union` ? + + +""" +Constructs the intersection of several traits. A type(-tuple) belongs +to a intersection if all its traits are fulfilled. +""" +immutable Intersection{S<:Tuple} <: Trait end +# TODO: should S<:Union? +# function Base.show{T<:Tuple}(io::IO, ti::Type{Collection{T}}) +# println(io, "Collection of:") +# for Tr in T.parameters +# print(io, " ") +# if Tr<:Collection +# show(io, Tr) +# else +# invoke(show, Tuple{IO, DataType}, io, Tr) +# end +# print(io, "\n") +# end +# end + + +## Trait Union: +# """ +# A new trait can be created from the union of several other +# (sub-)traits. A type(-tuple) belongs to this trait if at least one of its +# sub-traits are fulfilled. +# +# TODO: maybe implement this? +# """ +# Constructs the union of several traits. A type(-tuple) belongs +# to a union if at least one of its traits is fulfilled. +# """ +# immutable TUnion{S<:Tuple} <: Trait end + + + +""" +A trait is defined as full filled if this function is the identity +function for that trait. Otherwise it returns the trait wrapped in `Not`. Example: ``` @@ -42,21 +99,52 @@ trait(IsBits{Array}) # returns Not{IsBits{Array}} ``` Instead of using `@traitimpl` one can define a method for `trait` to -implement a trait. If this uses `@generated` functions it will be +implement a trait. If this uses `@generated` functions it should be in-lined away. For example the `IsBits` trait is defined by: ``` +@traitdef IsBits{X} +@generated trait{X}(::Type{IsBits{X}}) = isbits(X) ? :(IsBits{X}) : :(Not{IsBits{X}}) +``` """ trait{T<:Trait}(::Type{T}) = Not{T} trait{T<:Trait}(::Type{Not{T}}) = 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} -# -# Note due to invariance, this does probably not the right thing: -# trait(::Type{Tr1{Integer,FloatingPoint}}) = Tr1{Integer, FloatingPoint} +""" +Collections and Intersections use a generated method of the `trait` function to +evaluate whether a trait is fulfilled or not. If a new type is added +to a trait it can mean that this generated function is out of sync. +Use this macro to re-initialize it. (Triggers a warning) +""" +macro reset_trait_collections() + # TODO: + # - make specific to one trait-collection/trait-function + # - is it possible to make this cleaner? + out = esc(:out46785) # poor man's gensym +# TT = esc(:TT73840) # leads to strange error! + TT = esc(gensym()) + + TTT = esc(:TT73840) + quote + @generated function SimpleTraits.trait{$TT<:Collection}(::Type{$TT}) + $out = Any[] + for T in $TT.parameters + if !(T<:Trait) + error("Need a tuple of traits") + end + push!($out, trait(T)) + end + return :(Collection{$(out46785...)}) + end + @generated function SimpleTraits.trait{$TTT<:Collection}(::Type{Intersection{$TTT}}) # this fn relies on <:Collection + if trait($TTT)===$TTT + return :(Intersection{$TT73840}) + else + return :(Not{Intersection{$TT73840}}) + end + end + end +end +@reset_trait_collections # initialize it """ This function checks whether a trait is fulfilled by a specific @@ -67,10 +155,14 @@ 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 - # thus is redefine when traits are defined + # thus it is redefine when traits are defined + # with @traitimpl + """ -Used to define a trait. Traits, like types, are camel cased. -Often they start with `Is` or `Has`. +Used to define a trait. Style wise I advocate the following: + +- Traits, like types, are camel cased. +- Often they start with `Is` or `Has`. Examples: ``` @@ -79,7 +171,27 @@ Examples: ``` """ macro traitdef(tr) - :(immutable $(esc(tr)) <: Trait end) + if tr.head==:curly + :(immutable $(esc(tr)) <: Trait end) + elseif tr.head==:tuple || tr.head==:comparison # trait inheritance + if tr.head==:comparison + supert = Any[esc(tr.args[3])] + tr = tr.args[1] + else + supert = Any[esc(tr.args[1].args[3]), map(esc, tr.args[2:end])...] + tr = tr.args[1].args[1] + end + ## There are a few options to handle this: + ## Error: (I) + # return :(throw(TraitException("Sub-traiting is not supported"))) + ## Or proper supertrait (II) + :(immutable $(esc(tr)) <: Trait{Intersection{Tuple{$(supert...)}}} end) + ## Or alias (III) + # :(typealias $(esc(tr)) Collection{Tuple{$(supert...)}}) + else + throw(TraitException( + "Either define trait as `@traitdef Tr{...}` or with one or more super-traits `@traitdef Tr{...} <: Tr1, Tr2`")) + end end """ @@ -91,6 +203,8 @@ Example: @traitdef IsFast{X} @traitimpl IsFast{Array{Int,1}} ``` + +This errors if super-traits are not defined. """ macro traitimpl(tr) # makes @@ -107,6 +221,9 @@ macro traitimpl(tr) fnhead = :($curmod.trait{$(curly...)}($arg)) isfnhead = :($curmod.istrait{$(curly...)}($arg)) quote + $trname <: Intersection && error("Cannot use @traitimpl with Trait-Intersection: implement each intersected trait by hand.") + check_supertraits($(esc(tr))) + # TODO: allow option to implement all supertypes as well $fnhead = $trname{$(paras...)} $isfnhead = true # Add the istrait definition as otherwise # method-caching can be an issue. @@ -128,36 +245,99 @@ function traitfn(tfn) else hasmac = false end + # see whether we're initializing the function or not: + if tfn.head==:call + init = true + # add a dummy body + tfn = :($tfn=begin 1 end) + elseif tfn.head==:function || tfn.head==:(=) + init = false + else + error("Not recognized: $tfn") + end + fhead = tfn.args[1] - fbody = tfn.args[2] fname = fhead.args[1].args[1] args = insertdummy(fhead.args[2:end]) - typs = fhead.args[1].args[3:end] - trait = fhead.args[1].args[2].args[1] - if isnegated(trait) - trait = trait.args[2] - val = :(::Type{$curmod.Not{$trait}}) - else - val = :(::Type{$trait}) - end - if hasmac - fn = :(@dummy $fname{$(typs...)}($val, $(args...)) = $fbody) - fn.args[1] = mac # replace @dummy - else - fn = :($fname{$(typs...)}($val, $(args...)) = $fbody) + tpara = fhead.args[1].args[3:end] + # the trait dispatch wrapper + if isa(fhead.args[1].args[2], Symbol) + error("There are no trait-constraints, e.g. f{X; Tr{X}}(...)") end - quote - $fname{$(typs...)}($(args...)) = (Base.@_inline_meta(); $fname($curmod.trait($trait), $(striparg(args)...))) - $fn + traits = fhead.args[1].args[2].args[1:end] + trait_args = [isnegated(t) ? :($curmod.Not{$(t.args[2])}) : t for t in traits] + trait_args = length(trait_args)==1 ? trait_args[1] : :(Tuple{$(trait_args...)}) + # TODO: add test to throw on @traitfn f56{X,Y; !(T4{X,Y}, T{X})}(x::X, y::Y) + + trait_args_noNot = [isnegated(t) ? :($(t.args[2])) : t for t in traits] + trait_args_noNot = length(trait_args_noNot)==1 ? trait_args_noNot[1] : :(Tuple{$(trait_args_noNot...)}) + if init # the wrapper/trait-dispatch function: + return :($fname{$(tpara...)}($(args...)) = (Base.@_inline_meta(); $fname($curmod.trait($trait_args_noNot), $(strip_tpara(args)...)))) + # TODO: + # - return also logic functions returning a nice error + + else # the logic: + fbody = tfn.args[2] + if hasmac + logic = :(@dummy $fname{$(tpara...)}(::Type{$trait_args}, $(args...)) = $fbody) + logic.args[1] = mac # replace @dummy + else + logic = :($fname{$(tpara...)}(::Type{$trait_args}, $(args...)) = $fbody) + end + # TODO: + # - could a error be thrown if a logic is added for a trait which is not wrapped? + return logic end end """ -Defines a function dispatching on a trait: +Defines a function dispatching on a trait. + +First initialize it to let it know on which trait it dispatches +``` +@traitfn f{X,Y; Tr1{X,Y}}(x::X,y::Y) +``` +then add trait methods: ``` @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) = ... ``` + +CAUTION: trying to dispatch on trait not initialized will not work: +``` +@traitfn f{X; Tr2{X}}(x::X) = ... # this will never be called +``` + +However, this is all fine: +``` +@traitfn g{X; Tr1{X}}(x::X) +@traitfn g{X; Tr1{X}}(x::X) = ... +@traitfn g{X; !Tr1{X}}(x::X) = ... # ok, to do both the trait and its negation +@traitfn g{X; Tr2{X}}(x::X, y) # ok, as method signature is different to above +@traitfn g{X; Tr2{X}}(x::X, y) = ... +@traitfn g{X; !Tr2{X}}(x::X, y) = ... +``` + +Note, when updating a method, then re-initialize the trait function as +otherwise the old one is cached: +``` +@traitfn g{X; Tr1{X}}(x::X) +@traitfn g{X; Tr1{X}}(x::X) = new-logic +``` + +### Dispatching on several traits + +Is possible to dispatch on several traits using this syntax: +``` +@traitfn f55{X, Y; TT1{X}, TT2{Y}}(x::X, y::Y) +@traitfn f55{X, Y; TT1{X}, TT2{Y}}(x::X, y::Y) = 1 +@traitfn f55{X, Y; !TT1{X}, TT2{Y}}(x::X, y::Y) = 2 +@traitfn f55{X, Y; TT1{X}, !TT2{Y}}(x::X, y::Y) = 3 + +``` +*Note that all methods need to feature the same traits (possibly +negated) in the same order!* Any method violating that will never be +called (and no error is thrown!). """ macro traitfn(tfn) esc(traitfn(tfn)) @@ -167,18 +347,60 @@ end ## Helpers ###### -# true if :(!(Tr{x})) -isnegated(t::Expr) = t.head==:call +"""Returns the super traits""" +function getsuper{T<:Trait}(t::Type{T}) + S = t.super.parameters[1] + if isa(S,TypeVar) + return Base.Core.svec() + else + return S.parameters[1].parameters + end +end +getsuper{T<:Intersection}(t::Type{T}) = t.parameters[1].parameters + +""" +Checks whether all supertraits are fulfilled. If not, throws an error. +""" +check_supertraits(::Any) = error("Argument to `check_supertraits` is not a Trait.") +function check_supertraits{T<:Trait}(tr::Type{T}) + for ST in getsuper(tr) + if !istrait(ST) + throw(TraitException("Super trait $ST is not fulfilled. If it should be fulfilled, run `@traitimpl $ST` first.")) + end + end +end + + +# true if :(!(...)) +isnegated(t::Expr) = t.head==:call && t.args[1]==:! # [:(x::X)] -> [:x] -striparg(args::Vector) = Any[striparg(a) for a in args] -striparg(a::Symbol) = a -striparg(a::Expr) = a.args[1] +# also takes care of :... +strip_tpara(args::Vector) = Any[strip_tpara(a) for a in args] +strip_tpara(a::Symbol) = a +function strip_tpara(a::Expr) + if a.head==:(::) + return a.args[1] + elseif a.head==:... + return Expr(:..., strip_tpara(a.args[1])) + else + error("Cannot parse argument: $a") + end +end # insert dummy: ::X -> gensym()::X +# also takes care of :... insertdummy(args::Vector) = Any[insertdummy(a) for a in args] insertdummy(a::Symbol) = a -insertdummy(a::Expr) = (a.head==:(::) && length(a.args)==1) ? Expr(:(::), gensym(), a.args[1]) : a +function insertdummy(a::Expr) + if a.head==:(::) && length(a.args)==1 + return Expr(:(::), gensym(), a.args[1]) + elseif a.head==:... + return Expr(:..., insertdummy(a.args[1])) + else + return a + end +end # generates: X1, X2,... or x1, x2.... (just symbols not actual TypeVar) type GenerateTypeVars{CASE} @@ -192,6 +414,13 @@ Base.done(::GenerateTypeVars, state) = false # Extras #### +"Trait which contains all types" +@traitdef IsAnything{X} +@traitimpl IsAnything{Any} +"Trait which contains no types" +typealias IsNothing{X} Not{IsAnything{X}} +# TODO what about IsAnything{X,Y} ? + include("base-traits.jl") end # module diff --git a/src/base-traits.jl b/src/base-traits.jl index c3f2f29..f861061 100644 --- a/src/base-traits.jl +++ b/src/base-traits.jl @@ -1,16 +1,7 @@ module BaseTraits using SimpleTraits -export IsLeafType, IsBits, IsImmutable, IsContiguous, IsFastLinearIndex, - IsAnything, IsNothing, IsCallable - -"Trait which contains all types" -@traitdef IsAnything{X} -SimpleTraits.trait{X}(::Type{IsAnything{X}}) = IsAnything{X} - -"Trait which contains no types" -typealias IsNothing{X} Not{IsAnything{X}} - +export IsLeafType, IsBits, IsImmutable, IsContiguous, IsFastLinearIndex, IsCallable "Trait of all isbits-types" @traitdef IsBits{X} diff --git a/test/runtests.jl b/test/runtests.jl index edb8eb3..01e7fff 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,7 +1,13 @@ using SimpleTraits using Base.Test -trait = SimpleTraits.trait +ST = SimpleTraits +trait = ST.trait +Collection = ST.Collection +Intersection = ST.Intersection + +immutable A end +immutable B end # @test_throws MethodError trait(4) @test_throws ErrorException istrait(4) @@ -43,11 +49,18 @@ trait = SimpleTraits.trait @test trait(Tr2{Int, Float32})==Not{Tr2{Int, Float32}} # trait functions +@traitfn f{X; Tr1{X}}(x::X) @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 - +@test f(5.)== 2 +# try adding a trait which was not part of original init: +@traitdef Tr0{X} +@traitimpl Tr0{A} +@traitfn f{X; Tr0{X}}(x::X) = 99 +@test f(A())!=99 + +@traitfn f{X,Y; Tr2{X,Y}}(x::X,y::Y,z) @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 @@ -55,6 +68,7 @@ trait = SimpleTraits.trait @test f(5,5, "a")==2 # This will overwrite the definition def1 above +@traitfn f{X; !Tr2{X,X}}(x::X) @traitfn f{X; !Tr2{X,X}}(x::X) = 10 @traitfn f{X; Tr2{X,X}}(x::X) = 100 @test f(5)==10 @@ -63,32 +77,133 @@ trait = SimpleTraits.trait @test f(5.)==10 @test !(f(5)==100) # need to update method cache: +@traitfn f{X; !Tr2{X,X}}(x::X) @traitfn f{X; Tr2{X,X}}(x::X) = 100 @test f(5)==100 @test f(5.)==10 # VarArg -@traitfn g{X; Tr1{X}}(x::X, y...) = y -@test g(5, 7, 8)==((7,8),) -# @test g(5.0, 7, 8)==((7,8),) # hangs because of https://github.com/JuliaLang/julia/issues/13183 +@traitfn vara{X; Tr1{X}}(x::X, y...) +@traitfn vara{X; Tr1{X}}(x::X, y...) = y +@test vara(5, 7, 8)==(7,8) +# @test vara(5.0, 7, 8)==((7,8),) # hangs in lowering because of https://github.com/JuliaLang/julia/issues/13183 +@traitfn vara2{X; Tr1{X}}(x::X...) +@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...) +@traitfn vara3{X; Tr1{X}}(::X...) = X +@test vara3(5, 7, 8)==Int +@test_throws MethodError vara3(5, 7, 8.0) + # with macro +@traitfn @inbounds gg{X; Tr1{X}}(x::X) @traitfn @inbounds gg{X; Tr1{X}}(x::X) = x @test gg(5)==5 +@traitfn @generated ggg{X; Tr1{X}}(x::X) @traitfn @generated ggg{X; Tr1{X}}(x::X) = X<:AbstractArray ? :(x+1) : :(x) @test ggg(5)==5 @traitimpl Tr1{AbstractArray} @test ggg([5])==[6] # traitfn with Type +@traitfn ggt{X; Tr1{X}}(::Type{X}, y) @traitfn ggt{X; Tr1{X}}(::Type{X}, y) = (X,y) @test ggt(Array, 5)==(Array, 5) # traitfn with ::X +@traitfn gg27{X; Tr1{X}}(::X) @traitfn gg27{X; Tr1{X}}(::X) = X @test gg27([1])==Array{Int,1} +## Trait intersections and collections +@traitdef TT1{X} +@traitimpl TT1{A} +@traitdef TT2{Y} +@traitimpl TT2{B} + +# intersections +@test trait(Intersection{Tuple{TT1{A},TT2{B}}})== Intersection{Tuple{TT1{A}, TT2{B}}} +@test trait(Intersection{Tuple{TT1{A},TT2{A}}})==Not{Intersection{Tuple{TT1{A}, TT2{A}}}} +@test trait(Intersection{Tuple{TT1{B},TT2{B}}})==Not{Intersection{Tuple{TT1{B}, TT2{B}}}} +@test trait(Intersection{Tuple{TT1{B},TT2{A}}})==Not{Intersection{Tuple{TT1{B}, TT2{A}}}} + +# this gives collection combinations: +@test trait(Collection{TT1{A},TT2{B}})==Collection{ TT1{A} , TT2{B} } +@test trait(Collection{TT1{A},TT2{A}})==Collection{ TT1{A} , Not{TT2{A} }} +@test trait(Collection{TT1{B},TT2{B}})==Collection{Not{TT1{B}}, TT2{B} } +@test trait(Collection{TT1{B},TT2{A}})==Collection{Not{TT1{B}}, Not{TT2{A} }} + +@traitfn f55{X, Y; TT1{X}, TT2{Y}}(x::X, y::Y) +@traitfn f55{X, Y; TT1{X}, TT2{Y}}(x::X, y::Y) = 1 +@traitfn f55{X, Y; !TT1{X}, TT2{Y}}(x::X, y::Y) = 2 +@traitfn f55{X, Y; !TT1{X}, !TT2{Y}}(x::X, y::Y) = 3 + +@test f55(A(),B())==1 +@test f55(B(),B())==2 +@test f55(B(),A())==3 + +@traitfn f55{X, Y; !TT2{Y}, TT1{X}}(x::X, y::Y) = 4 # oops traits are in reverse order! +@test_throws MethodError f55(A(),A())==4 +@traitfn f55{X, Y; TT1{X}, !TT2{Y}}(x::X, y::Y) = 4 +@test f55(A(),A())==4 + +# this fails because the generated-function has already been created above: +@traitimpl TT2{A} +@test !(trait(Collection{TT1{A},TT2{A}})==Collection{ TT1{A} , TT2{A} }) +println("-- This warning is ok:") +ST.@reset_trait_collections +println("-- endof ok warning.") +@test trait(Collection{TT1{A},TT2{A}})==Collection{ TT1{A} , TT2{A} } + +@traitfn f55{X, Y; TT1{X}, TT2{Y}}(x::X, y::Y) # clear cached +@traitfn f55{X, Y; TT1{X}, TT2{Y}}(x::X, y::Y) = 1 +@traitfn f55{X, Y; TT1{X}, !TT2{Y}}(x::X, y::Y) = 4 +@test f55(A(),A())==1 + +# Intersections +typealias SI4{X,Y} Intersection{Tuple{TT1{X}, TT2{Y}}} # just an alias for the Collectionsection +X,Y = TypeVar(:XX, true), TypeVar(:YY,true) +@test SI4{X,Y}===Intersection{Tuple{TT1{X}, TT2{Y}}} +@test trait(SI4{A,B})===SI4{A,B} +@test trait(SI4{A,A})===SI4{A,A} # <-- some julia-issue here! +@test trait(SI4{B,B})===Not{SI4{B,B}} + +@traitfn f56{X,Y; SI4{X,Y}}(x::X, y::Y) # note that SI4 is similar to {TT1{X}, TT2{Y}} +@traitfn f56{X,Y; SI4{X,Y}}(x::X, y::Y) = 1 +@traitfn f56{X,Y; !SI4{X,Y}}(x::X, y::Y) = 2 + +@test f56(A(),B())==1 +@test f56(A(),A())==1 +@test f56(B(),B())==2 +@test f56(B(),A())==2 + +# Trait inheritance +@traitdef ST2{X,Y} <: TT1{X} +@traitimpl ST2{A,B} +@test istrait(ST2{A,B}) +@traitdef ST4{X,Y} <: TT1{X}, TT2{Y} +X,Y = TypeVar(:XX, true), TypeVar(:YY,true) +@test super(ST4{X,Y}).parameters[1]===Intersection{Tuple{TT1{X}, TT2{Y}}} +@traitimpl ST4{A,B} +@test istrait(ST4{A,B}) +@test_throws ST.TraitException @traitimpl ST4{Int,B} +@traitimpl TT1{Int} +@traitimpl ST4{Int,B} +@test istrait(ST4{Int,B}) + + +###### +# TODO +###### + +## Default arguments + +## Keyword + ###### # Other tests #####