diff --git a/src/Traits.jl b/src/Traits.jl index 6a682df..7d26a87 100644 --- a/src/Traits.jl +++ b/src/Traits.jl @@ -16,8 +16,17 @@ if !(VERSION>v"0.4-") error("Traits.jl needs Julia version 0.4.-") end -# Flags: by setting them in Main before using, they can be turned on -# or off. +## patches for bugs in base +include("base_fixes.jl") + +## common helper functions +include("helpers.jl") + +####### +# Flags +####### +# By setting them in Main before using, they can be turned on or off. +# TODO: update to use functions. if isdefined(Main, :Traits_check_return_types) println("Traits.jl: not using return types of @traitdef functions") flag_check_return_types = Main.Traits_check_return_types @@ -26,45 +35,61 @@ else end @doc "Flag to select whether return types in @traitdef's are checked" flag_check_return_types +####### +# Types +####### @doc """`abstract Trait{SUPER}` - All traits are direct decedents of abstract type Trait. The type parameter - SUPER of Trait is needed to specify super-traits (a tuple).""" -> + All traits are direct decedents of abstract type Trait. The type parameter + SUPER of Trait is needed to specify super-traits (a tuple).""" -> abstract Trait{SUPER} +# Type of methods field of concrete traits: +typealias FDict Dict{Union(Function,DataType),Function} + # A concrete trait type has the form ## Tr{X,Y,Z} <: Trait{(ST1{X,Y},ST2{Z})} # -# immutable Tr{X,Y,Z} <: Trait{(ST1{X,Y},ST2{Z})} -# methods -# Tr() = new(methods_made_in_macro) +# immutable Tr1{X1} <: Traits.Trait{()} +# methods::FDict +# constraints::Vector{Bool} +# assoctyps::Vector{Any} +# Tr1() = new(FDict(methods_defined), Bool[], []) # end # -# where methods holds the function signatures, like so: +# where methods field holds the function signatures, like so: # Dict{Function,Any} with 3 entries: -# next => ((Int64,Any),(Any...,)) -# done => ((Int64,Any),(Bool,)) -# start => ((Int64,),(Any...,)) +# start => _start(Int64) = (Any...,) +# next => _next(Int64,Any) = (Any...,) +# done => _done(Int64,Any) = Bool # used to dispatch to helper methods -immutable _TraitDispatch end +immutable _TraitDispatch end immutable _TraitStorage end -@doc """Type All is to denote that any type goes in type signatures in - @traitdef. This is a bit awkward: - - - method_exists(f, s) returns true if there is a method of f with - signature sig such that s<:sig. Thus All<->Union() - - Base.return_types works the other way around, there All<->Any - - See also https://github.com/JuliaLang/julia/issues/8974"""-> -abstract All +# @doc """Type All is to denote that any type goes in type signatures in +# @traitdef. This is a bit awkward: + +# - method_exists(f, s) returns true if there is a method of f with +# signature sig such that s<:sig. Thus All<->Union() +# - Base.return_types works the other way around, there All<->Any + +# See also https://github.com/JuliaLang/julia/issues/8974"""-> +# abstract All # General trait exception type TraitException <: Exception msg::String end +# A helper type used in istrait below +immutable _TestType{T} end + +######### +# istrait, one of the core functions +######### + +# Update after PR #10380 @doc """Tests whether a DataType is a trait. (But only istrait checks whether it's actually full-filled)""" -> istraittype(x) = false @@ -72,101 +97,99 @@ istraittype{T<:Trait}(x::Type{T}) = true istraittype(x::Tuple) = mapreduce(istraittype, &, x) @doc """Tests whether a set of types fulfill a trait. - A Trait Tr is defined for some parameters if: + A Trait Tr is defined for some parameters if: - - all the functions of a trait are defined for them - - all the trait constraints are fulfilled + - all the functions of a trait are defined for them + - all the trait constraints are fulfilled - Example: + Example: - `istrait(Tr{Int, Float64})` + `istrait(Tr{Int, Float64})` - or with a tuple of traits: + or with a tuple of traits: - `istrait( (Tr1{Int, Float64}, Tr2{Int}) )` - """ -> + `istrait( (Tr1{Int, Float64}, Tr2{Int}) )` + """ -> function istrait{T<:Trait}(Tr::Type{T}; verbose=false) + println_verb = verbose ? println : x->x + if !hasparameters(Tr) throw(TraitException("Trait $Tr has no type parameters.")) end # check supertraits !istrait(traitgetsuper(Tr); verbose=verbose) && return false - # check methods definitions + + # check instantiating + tr = nothing try - Tr() + tr = Tr() catch err - if verbose - println("""Could not instantiate instance for type encoding the trait $Tr. - Failed with error: $err""") - end + println_verb("""Could not instantiate instance for type encoding the trait $Tr. + This usually indicates that something is amiss with the @traitdef + or that one of the generic functions is not defined. + The error was: $err""") return false end - out = true + + # check constraints + if !all(tr.constraints) + println_verb("Not all constraints are satisfied for $T") + return false + end + # check call signature of methods: - for (meth,sig) in Tr().methods - # instead of: - ## checks = length(methods(meth, sig[1]))>0 - # Now using method_exists. But see bug - # https://github.com/JuliaLang/julia/issues/8959 - - sigg = map(x->x===All ? Union() : x, sig[1]) - if isa(meth, Function) - if !method_exists(meth, sigg) # I think this does the right thing. - if verbose - println("Method $meth with call signature $(sig[1]) not defined for $T") + for (gf,_gf) in tr.methods # loop over all generic functions in traitdef + # if isa(gf, DataType) && gf in traitgetpara(Tr) + # error("asdf") + # end + for tm in methods(_gf) # loop over all methods defined for each function in traitdef + checks = false + for fm in methods(gf, NTuple{length(tm.sig),Any}) # only loop over methods which have + # the right number of arguments + if isfitting(tm, fm, verbose=verbose) + checks = true + break end - out = false end - elseif isa(meth, DataType) # a constructor, presumably. - # But discard the catch all to convert, i.e. this means - # method_exists(call, (Type{meth}, sigg...))==true for all types - chatch_all = methods(call, (Type{Array},)) - if methods(call, tuple(Type{meth}, sigg...))==chatch_all - if verbose - println("Datatype constructor $meth with call signature $sigg not defined for trait $T") - end - out = false + if !checks # if check==false no fitting method was found + println_verb("""No method of the generic function/call-overloaded $gf matched the + trait specification: $tm""") + return false end - else - throw(TraitException("Trait $Tr has something funny in its method dictionary: $meth.")) end end - # check return-type - if flag_check_return_types && out # only check if all methods were defined - for (meth,sig) in Tr().methods - # replace All in sig[1] with Any - sigg = map(x->x===All ? Any : x, sig[1]) - tmp = Base.return_types(meth, sigg) - if length(tmp)==0 - rettype = [] - out = false - if verbose - println("Method `$meth` with signature $sigg->$(sig[2]) has an empty return signature!") + + # check return-type. Specifed return type tret and return-type of + # the methods frets should fret<:tret. This is backwards to + # argument types... + if flag_check_return_types + for (gf,_gf) in tr.methods + for tm in methods(_gf) # loop over all methods defined for each function in traitdef + @show tret_typ = Base.return_types(_gf, tm.sig) # trait-defined return type + if length(tret_typ)!=1 + throw(TraitException("Querying the return type of the trait-method $tm did not return exactly one return type: $tret_typ")) end - else#if length(tmp)==1 - rettype = tmp[1] - if !(rettype<:sig[2]) - out = false - if verbose - println("Method `$meth` with signature $sigg->$(sig[2]) has wrong return type: $rettype") + tret_typ = tret_typ[1] + @show fret_typ = Base.return_types(gf, tm.sig) + # at least one of the return types need to be a subtype of tret_typ + checks = false + for fr in fret_typ + if fr<:tret_typ + checks = true end end - # else - # out = false - # if verbose - # println("Method `$meth` with signature $sigg->$(sig[2]) has more than one return type!") - # end + if !checks + println_verb("""For function $gf: no return types found which are subtypes of the specified return type: + $tret_typ + List of found return types: + $fret_typ + """) + return false + end end end end - # check constraints - if !all(Tr().constraints) - if verbose - println("Not all constraints are satisfied for $T") - end - return false - end - return out + return true end # check a tuple of traits against a signature function istrait(Trs::Tuple; verbose=false) @@ -176,12 +199,216 @@ function istrait(Trs::Tuple; verbose=false) return true end +## Helpers for istrait +immutable FakeMethod + sig::(Any...,) + tvars::(Any...,) + va::Bool +end +@doc """isfitting checks whether a method `tm` specified in the trait definition + is fulfilled by a method `fm` of the corresponding generic function. The + core function called by istraits. + + Checks that tm.sig<:fm.sig and that the parametric constraints on + fm and tm are equal. Lets call this relation tm<<:fm. + + So, summarizing, for a trait-signature to be satisfied (fitting) the following + condition need to hold: + A) `tsig<:sig` for just the types themselves (sans parametric constraints) + B) The parametric constraints on `sig` and `tsig` need to be equal. + + Examples, left trait-method, right implementation-method: + {T<:Real, S}(a::T, b::Array{T,1}, c::S, d::S) <<: {T<:Number, S}(a::T, b::AbstractArray{T,1}, c::S, d::S) + -> true + + {T<:Integer}(T, T, Integer) <<: {T<:Integer}(T, T, T) + -> false as parametric constraints are not equal + """ -> +function isfitting(tmm::Method, fm::Method; verbose=false) # tm=trait-method, fm=function-method + println_verb = verbose ? println : x->x + + # Make a "copy" of tmm as it may get updated: + tm = FakeMethod(tmm.sig, tmm.tvars, tmm.va) + + # Special casing for call-overloading. + if fm.func.code.name==:call && tmm.func.code.name!=:call # true if only fm is call-overloaded + # prepend ::Type{...} to signature + tm = FakeMethod(tuple(fm.sig[1], tm.sig...), tm.tvars, tm.va) + # check whether there are method parameters too: + fmtvars = isa(fm.tvars,TypeVar) ? (fm.tvars,) : fm.tvars # make sure it's a tuple of TypeVars + for ftv in fmtvars + flocs = find_tvar(fm.sig, ftv) + if flocs[1] # yep, has a constraint like call{T}(::Type{Array{T}},...) + if sum(flocs)==1 + tm = FakeMethod(tm.sig, tuple(ftv, tm.tvars...) , tm.va) + else + println_verb("This check is not implemented, returning false.") + # Note that less than none of the 1000 methods + # of call in Base end up here. + return false + end + end + end + # There is a strange bug which is prevented by this never + # executing @show. I'll try and investigate this in branch + # m3/heisenbug + # if length(tm.sig)==-10 + # @show tm + # error("This is not possible") + # end + end + + + # No Vararg methods implement yet + if tm.va || fm.va + # runtests.jl flag: varag_not_supported_bug + # + # What do varargs mean? + println_verb("Vararg methods not currently supported. Returning false.") + return false + end + ## Check condition A: + # If there are no type-vars then just compare the signatures: + if tm.tvars==() + if !(fm.tvars==()) + # If there are parameter constraints affecting more than + # one argument, then return false. + fmtvars = isa(fm.tvars,TypeVar) ? (fm.tvars,) : fm.tvars + for ftv in fmtvars + if sum(find_tvar(fm.sig, ftv))>1 + println_verb("Reason fail: no tvars-constraints in trait-method but in function-method.") + return false + end + end + end + println_verb("Reason fail/pass: no tvars in trait-method. Result: $(tm.sig<:fm.sig)") + return tm.sig<:fm.sig + end + # If !(tm.sig<:fm.sig) then tm<<:fm is false + # but the converse is not true: + if !(tm.sig<:fm.sig) + println_verb("""Reason fail: !(tm.sig<:fm.sig) + tm.sig = $(tm.sig) + fm.sig = $(fm.sig)""") + return false + end + # False if there are not the same number of arguments: (I don't + # think this test is necessary as it is tested above.) + if length(tm.sig)!=length(fm.sig)! + println_verb("Reason fail: wrong length") + return false + end + # Getting to here means that that condition (A) is fulfilled. + + ## Check condition B: + # If there is only one argument then we're done as parametric + # constraints play no role: + if length(tm.sig)==1 + println_verb("Reason pass: length(tm.sig)==1") + return true + end + + # Strategy: go through constraints on trait-method and check + # whether they are fulfilled in function-method. + tmtvars = isa(tm.tvars,Tuple) ? tm.tvars : (tm.tvars,) + tvars = isa(tm.tvars,TypeVar) ? (tm.tvars,) : tm.tvars + for tv in tvars + # find all occurrences in the signature + locs = find_tvar(tm.sig, tv) + if !any(locs) + error("Bad: the type variable should feature in at least on location.") + end + # Find the tvar in fm which corresponds to tv. + ftvs = Any[] + fmtvars = isa(fm.tvars,TypeVar) ? (fm.tvars,) : fm.tvars # make sure it's a tuple of TypeVar + for ftv in fmtvars + flocs = find_tvar(fm.sig, ftv) + if all(flocs[find(locs)]) + push!(ftvs,ftv) + end + end + if length(ftvs)==0 + # TODO this should pass (bug traitdef_bug1): + # g01 => _g01{T<:X}(::T, ::T) = T() + # g01(::Int, ::Int) = Int + #@test istrait(Tr01{Int}, verbose=true) + + println_verb("Reason fail: parametric constraints on function method not as severe as on trait-method.") + return false + end + if length(ftvs)>1 + error("""Not supported if two or more TypeVar appear in the same arguments. + Example f{K,V}(::Dict{K,V}, ::Dict{V,K})""") + end + + # Check that they constrain the same thing in each argument. + # E.g. this should fail: {K,V}(::Dict{K,V}, T) <<: {T}(::Dict{V,K}, T). + # Do this by substituting a concrete type into the respective + # TypeVars and check that arg(tv')<:arg(ftv') + for i in find(locs) + targ = subs_tvar(tv, tm.sig[i], _TestType{i}) + farg = subs_tvar(ftvs[1], fm.sig[i], _TestType{i}) + if !(targ<:farg) + println_verb("Reason fail: parametric constraints on args $(tm.sig[i]) and $(fm.sig[i]) on different TypeVar locations!") + return false + end + end + end + + println_verb("Reason pass: all checks passed") + return true +end + +# helpers for isfitting +function subs_tvar{T<:_TestType}(tv::TypeVar, arg::DataType, TestT::Type{T}) + # Substitute `TestT` for a particular TypeVar `tv` in an argument `arg`. + # + # Example: + # Array{I<:Int64,N} -> Array{_TestType{23},N} + if isleaftype(arg) || length(arg.parameters)==0 # concrete type or abstract type with no parameters + return arg + else # It's a parameterized type: do substitution on all parameters: + pa = [ subs_tvar(tv, arg.parameters[i], TestT) for i=1:length(arg.parameters) ] + typ = deparameterize_type(arg) + return typ{pa...} + end +end +subs_tvar{T<:_TestType}(tv::TypeVar, arg::TypeVar, TestT::Type{T}) = tv===arg ? TestT : arg # note === this it essential! +subs_tvar{T<:_TestType}(tv::TypeVar, arg, TestT::Type{T}) = arg # for anything else + +# find_tvar finds index of arguments in a function signature `sig` where a +# particular TypeVar `tv` features. Example: +# +# find_tvar( (T, Int, Array{T}) -> [true, false, true] +function find_tvar(sig::Tuple, tv) + ns = length(sig) + out = falses(ns) + for i = 1:ns + out[i] = any(find_tvar(sig[i], tv)) + end + return out +end +find_tvar(sig::TypeVar, tv) = sig===tv ? [true] : [false] # note ===, this it essential! +function find_tvar(sig::DataType, tv) + isleaftype(sig) && return [false] + ns = length(sig.parameters) + out = false + for i=1:ns + out = out || any(find_tvar(sig.parameters[i], tv)) + end + return [out] +end +find_tvar(sig, tv) = [false] + +###################### +# Sub and supertraits: +###################### @doc """Returns the super traits""" -> traitgetsuper{T<:Trait}(t::Type{T}) = t.super.parameters[1]::Tuple traitgetpara{T<:Trait}(t::Type{T}) = t.parameters @doc """Checks whether a trait, or a tuple of them, is a subtrait of - the second argument.""" -> + the second argument.""" -> function issubtrait{T1<:Trait,T2<:Trait}(t1::Type{T1}, t2::Type{T2}) if t1==t2 return true @@ -218,12 +445,6 @@ function issubtrait(t1::Tuple, t2::Tuple) return checks end -## patches for bugs in base -include("base_fixes.jl") - -## common helper functions -include("helpers.jl") - ## Trait definition include("traitdef.jl") diff --git a/src/base_fixes.jl b/src/base_fixes.jl index ed09bc5..6979665 100644 --- a/src/base_fixes.jl +++ b/src/base_fixes.jl @@ -11,3 +11,8 @@ function Base.func_for_method(m::Method, tt, env) end end println(" endof ok-warning.") + + +# eltype for dicts +Base.eltype{K}(::Type{Associative{K}}) = (K,Any) +Base.eltype(::Type{Associative}) = (Any,Any) diff --git a/src/commontraits.jl b/src/commontraits.jl index b99fe01..26787a0 100644 --- a/src/commontraits.jl +++ b/src/commontraits.jl @@ -56,19 +56,19 @@ end @traitdef Assoc{X} <: Indexable{X} begin K,V = eltype(X) - + # note, ObjectId dict is not part of this interface - haskey(X, All) - get(X, All, All) - get(Function, X, All) - get!(X, All, All) - get!(Function, X, All) - getkey(X, All, All) - delete!(X, All) -> X - pop!(X, All) - pop!(X, All, All) - merge(X, All...) -> X - merge!(X, All...) + haskey(X, Any) + get(X, Any, Any) + get(Function, X, Any) + get!(X, Any, Any) + get!(Function, X, Any) + getkey(X, Any, Any) + delete!(X, Any) -> X + pop!(X, Any) + pop!(X, Any, Any) + # merge(X, Any...) -> X + # merge!(X, Any...) # provieds # keys(X) -> Base.KeyIterator # values(X) -> Base.ValueIterator diff --git a/src/traitdef.jl b/src/traitdef.jl index d0e87a5..5d1a478 100644 --- a/src/traitdef.jl +++ b/src/traitdef.jl @@ -6,6 +6,7 @@ # # It looks like # @traitdef Cmp{X,Y} <: Eq{X,Y} begin +# T = eltype(X) # associated type # isless(x,y) -> Bool # @constraints begin # X==Y @@ -95,7 +96,7 @@ function parsebody(body::Expr) isassoc(ex::Expr) = ex.head==:(=) # associated types isconstraints(ex::Expr) = ex.head==:macrocall # constraints - outfns = Expr(:dict) + outfns = :(Traits.FDict()) constr = :(Bool[]) assoc = quote end for ln in Lines(body) @@ -132,23 +133,33 @@ function parseconstraints!(constr, block) end function parsefnstypes!(outfns, ln) + # parse one line containing a function definition function parsefn(def) # Parse to get function signature. # parses f(X,Y), f{X <:T}(X,Y) and X+Y - tvars = Any[] - if isa(def.args[1], Symbol) # f(X,Y) + # into f and _f(...) + + # getsymbol = gensym + getsymbol(fn) = symbol("__"*string(fn)) + + _fn = deepcopy(def) + if isa(def.args[1], Symbol) # f(X,Y) or X+Y fn = def.args[1] + _fn.args[1] = getsymbol(fn) elseif def.args[1].head==:curly # f{X}(X,Y) fn = def.args[1].args[1] - # get - tvars = def.args[1].args[2:end] + _fn.args[1].args[1] = getsymbol(fn) else throw(TraitException( - "Something went wrong parsing the trait definition body with line:\n$ln")) + "Something went wrong parsing the trait function definition:\n$fn")) end - argtype = :() - append!(argtype.args, def.args[2:end]) - return fn, argtype, tvars + # transform X->::X + for i=2:length(_fn.args) + @show _fn.args[i] + _fn.args[i] = :(::$(_fn.args[i])) + end + @show fn, _fn + return fn, _fn end function parseret!(rettype, ln) # parse to get return types @@ -156,11 +167,10 @@ function parsefnstypes!(outfns, ln) ln = ln.args[end] end tmp = rettype.args - rettype.args = Any[] - push!(rettype.args, ln.args[end]) + rettype.args = Any[] # per-pend + push!(rettype.args, :($(ln.args[end])())) # e.g. Bool(), the () is for return_types to work append!(rettype.args, tmp) end - rettype = :() tuplereturn = false @@ -168,13 +178,15 @@ function parsefnstypes!(outfns, ln) tuplereturn = true # several ret-types: # f1(X,Y) -> X,Y - append!(rettype.args, ln.args[2:end]) + for r in ln.args[2:end] + push!(rettype.args, :($r())) + end ln = ln.args[1] end if ln.head==:(->) # f1(X,Y) -> x parseret!(rettype, ln) - fn, argtype, tvars = parsefn(ln.args[1]) + fn, _fn = parsefn(ln.args[1]) elseif ln.head==:call # either f1(X,Y) or X + Y -> Z if isa(ln.args[end], Expr) && ln.args[end].head==:(->) # X + Y -> Z def = Expr(:call) @@ -187,37 +199,22 @@ function parsefnstypes!(outfns, ln) parseret!(rettype, ln) else # f1(X,Y) def = ln - rettype = :(Any,) + rettype = :(Any(),) end - fn, argtype, tvars = parsefn(def) + fn, _fn = parsefn(def) else throw(TraitException( "Something went wrong parsing the trait definition body with line:\n$ln")) end - # replace types with constraints by TypeVars - tmp = Any[] - for t in tvars - if isa(t,Symbol) - #error("Having a ") - push!(tmp,t) - else - push!(tmp,t.args[1]) - end - end - # trans = Dict(zip([t.args[1] for t in tvars], tvars)) # this will error if there is a type-var without constraints! - trans = Dict(zip(tmp,tvars)) - translate!(argtype.args, trans) - tvar2tvar!(argtype.args) - subt2tvar!(rettype.args) - translate!(rettype.args, trans) - tvar2tvar!(rettype.args) # if return is not a tuple, ditch the tuple if !tuplereturn rettype = rettype.args[1] end - push!(outfns.args, :($fn => ($argtype, $rettype))) + # make _fn + _fn = :($_fn = $rettype) + push!(outfns.args, :($fn => $_fn)) end # 3) piece it together @@ -274,7 +271,7 @@ macro traitdef(head, body) # make sure a generic function of all associated types exisits traitbody = quote - methods::Dict{Union(Function,DataType), Tuple} + methods::Traits.FDict constraints::Vector{Bool} assoctyps::Vector{Any} function $((name))() diff --git a/test/manual-traitdef.jl b/test/manual-traitdef.jl index 06bafb6..5e21a6c 100644 --- a/test/manual-traitdef.jl +++ b/test/manual-traitdef.jl @@ -5,21 +5,23 @@ # the types: @test istrait( () ) - immutable Tr1{X1} <: Traits.Trait{()} - methods - constraints - Tr1() = new(Dict(), []) + methods::Traits.FDict + constraints::Vector{Bool} + assoctyps::Vector{Any} + Tr1() = new(Traits.FDict(), Bool[], []) end immutable Tr2{X1,X2} <: Traits.Trait{()} - methods - constraints - Tr2() = new(Dict(), []) + methods::Traits.FDict + constraints::Vector{Bool} + assoctyps::Vector{Any} + Tr2() = new(Traits.FDict(), Bool[], []) end immutable Tr3{X1,X2} <: Traits.Trait{(Tr1{X1}, Tr2{X1,X2})} - methods - constraints - Tr3() = new(Dict(), []) + methods::Traits.FDict + constraints::Vector{Bool} + assoctyps::Vector{Any} + Tr3() = new(Traits.FDict(), Bool[], []) end @test istraittype(Tr1) @@ -32,35 +34,40 @@ end @test traitgetsuper(Tr3{A1,A2})==(Tr1{A1},Tr2{A1,A2}) # any type is part of a unconstrained trait: -@test istrait(Tr1{Int}) +@test istrait(Tr1{Int}, verbose=true) @test istrait(Tr2{DataType,Int}) @test istrait(Tr3{String,DataType}) @test_throws TraitException istrait(Tr3{:a,7}) # maybe this should error? immutable D1{X1} <: Traits.Trait{()} - methods - constraints + methods::Traits.FDict + constraints::Vector{Bool} + assoctyps::Vector{Any} function D1() - new(Dict( - sin => ((X1,), Float64), - cos => ((X1,), Float64), + new(Traits.FDict( + sin => _sin(::X1) = Float64(), # note 1: _sin could be any symbol; + # note 2: Float64() would throw an error but works with return_types + cos => _cos(::X1) = Float64() ), + Bool[], [] ) end end -@test istrait(D1{Int}) +@test istrait(D1{Int}, verbose=true) @test !istrait(D1{String}) immutable D2{X1,X2} <: Traits.Trait{(D1{X1}, D1{X2})} - methods - constraints + methods::Traits.FDict + constraints::Vector{Bool} + assoctyps::Vector{Any} function D2() - new(Dict( - (+) => ((X1, X2), Any), - (-) => ((X1, X2), Any) + new(Traits.FDict( + (+) => _plus(::X1, ::X2) = Any(), + (-) => _minus(::X1, ::X2) = Any() ), + Bool[], [] ) end @@ -70,32 +77,35 @@ end @test !istrait(D2{Int, String}) immutable D3{X1} <: Traits.Trait{()} - methods - constraints + methods::Traits.FDict + constraints::Vector{Bool} + assoctyps::Vector{Any} function D3() - new(Dict( - getkey => ((X1,Any,Any), Any), - get! => ((X1, Any, Any), Any) + new(Traits.FDict( + getkey => _getkey(::X1,::Any,::Any) = Any(), + get! => _get!(::X1, ::Any, ::Any) = Any() ), + Bool[], [] ) end end immutable D4{X1,X2} <: Traits.Trait{()} # like D2 but without supertraits - methods - constraints + methods::Traits.FDict + constraints::Vector{Bool} + assoctyps::Vector{Any} function D4() - new(Dict( - (+) => ((X1, X2), Any), - (-) => ((X1, X2), Any) + new(Traits.FDict( + (+) => _plus(::X1, ::X2) = Any(), + (-) => _minus(::X1, ::X2) = Any() ), + Bool[], [] ) end end - @test istrait(D3{Dict{Int,Int}}) @test !istrait(D3{Int}) @@ -106,18 +116,17 @@ end ### adding other constraints immutable CTr1{X1,X2} <: Traits.Trait{()} - methods::Dict - constraints::Array{Bool,1} # constraints are an array of functions - # which need to evaluate to true. Their - # signature is f(X,Y) = ... - + methods::Traits.FDict + constraints::Vector{Bool} + assoctyps::Vector{Any} function CTr1() - new(Dict( - (+) => ((X1, X2), Any), + new(Traits.FDict( + (+) => _plus(::X1, ::X2) = Any(), ), Bool[ X1==X2 - ] + ], + [] ) end end @@ -128,18 +137,16 @@ end ### adding other associated types immutable CTrAs{X1,X2} <: Traits.Trait{()} - methods::Dict - constraints::Array{Bool,1} # constraints are an array of functions - # which need to evaluate to true. Their - # signature is f(X,Y) = ... - assoctyps::Array{TypeVar,1} + methods::Traits.FDict + constraints::Vector{Bool} + assoctyps::Vector{Any} function CTrAs() R = promote_type(X1, X2) D = (X1,X2)<:(Integer,Integer) ? Float64 : promote_type(X1, X2) - assoctyps = [TypeVar(:R, R), TypeVar(:D, D)] - new(Dict( - (+) => ((X1, X2), R), - (/) => ((X1, X2), D), + assoctyps = Any[TypeVar(:R, R), TypeVar(:D, D)] + new(Traits.FDict( + (+) => _plus(::X1, ::X2) = Any(), + (-) => _minus(::X1, ::X2) = Any() ), Bool[], assoctyps @@ -151,3 +158,83 @@ end # @test istrait(CTrAs{Integer, Integer}) # doesn't work because return type of /(Integer, Integer)==Any @test istrait(CTrAs{Int, Int}) @test !istrait(CTrAs{Int, String}) + +# parametric methods + +# @traitdef Tr01{X} begin +# g01{T<:X}(T, T) -> T +# end +immutable Tr01{X} <: Traits.Trait{()} + methods::Traits.FDict + constraints::Vector{Bool} + assoctyps::Vector{Any} + function Tr01() + new(Traits.FDict( + g01 => _g01{T<:X}(::T, ::T) = T() + ), + Bool[], + [] + ) + end +end + + +g01(::Int, ::Int) = Int +if concrete_type_bug + @test !istrait(Tr01{Int}) # == true as constraints Int isleaftype +else + @test istrait(Tr01{Int}) # == true as constraints Int isleaftype +end +@test !istrait(Tr01{Integer}) +g01{I<:Integer}(::I, ::I) = I +@test istrait(Tr01{Integer}) # == true + +# @traitdef Tr02{X} begin +# g02{T<:X}(T, T) -> T +# end +immutable Tr02{X} <: Traits.Trait{()} + methods::Traits.FDict + constraints::Vector{Bool} + assoctyps::Vector{Any} + function Tr02() + new(Traits.FDict( + g02 => _g02{T<:X}(::T, ::T) = T() + ), + Bool[], + [] + ) + end +end + +g02{I<:Integer}(::I, ::I) = Integer +# By using Base.return_types it is not possible to figure out whether +# the returned value is constrained or not by I: +if function_types_bug1 + @test istrait(Tr02{Integer}) + # or throw an error/warning here saying parametric return types + # are only supported for leaftypes +else + @test !istrait(Tr02{Integer}) # if function types get implemented this should be possible to catch +end +@test istrait(Tr02{Int}) # == true + +# @traitdef Tr03{X} begin +# g03{T<:X}(T, Vector{T}) +# end +immutable Tr03{X} <: Traits.Trait{()} + methods::Traits.FDict + constraints::Vector{Bool} + assoctyps::Vector{Any} + function Tr03() + new(Traits.FDict( + g03 => _g03{T<:X}(::T, ::Vector{T}) = T() + ), + Bool[], + [] + ) + end +end + +g03{I<:Integer}(::I, ::Vector{I}) = 1 +@test istrait(Tr03{Integer}) +@test istrait(Tr03{Int}) diff --git a/test/manual-traitimpl.jl b/test/manual-traitimpl.jl index f2d2ec1..234135f 100644 --- a/test/manual-traitimpl.jl +++ b/test/manual-traitimpl.jl @@ -72,9 +72,9 @@ length(tmp)==length(implfns) || error("Duplicate method definition(s)") # check right number of defs length(D2{T1,T2}().methods)==length(implfns) || error("Not right number of method definitions") # check that the signature of fns agrees with D2{T1,T2}().methods -for (fn,sig) in D2{T1,T2}().methods +for (fn,_fn) in D2{T1,T2}().methods # for now just check length - if length(sig)!=length(get_fnsig(implfns[fn])) + if length(_fn.env.defs.sig)!=length(get_fnsig(implfns[fn])) error("""Method definition: $fn $sig does not match implementation: diff --git a/test/perf/perf.jl b/test/perf/perf.jl index 1c16a0e..4a9194b 100644 --- a/test/perf/perf.jl +++ b/test/perf/perf.jl @@ -48,8 +48,8 @@ function compare_code_native(f1, f2, types, fraction_range=[-Inf,Inf]) out end - df1 = prune_native(Base._dump_function(f1, types, true, false)) - df2 = prune_native(Base._dump_function(f2, types, true, false)) + df1 = prune_native(Base._dump_function(f1, types, true, false, true)) + df2 = prune_native(Base._dump_function(f2, types, true, false, true)) rel_diff = abs(length(df1)-length(df2))/length(df2) if !(fraction_range[1]<=rel_diff<=fraction_range[2]) println("""Warning: length of code native of $(f1.env.name) and $(f2.env.name) differ by $rel_diff, diff --git a/test/runtests.jl b/test/runtests.jl index dc21e58..0e51ae7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,19 +1,33 @@ # tests using Base.Test using Traits +## BUG flags: set to false once fixed to activate tests +# Julia issues: +method_exists_bug1 = false # see https://github.com/JuliaLang/julia/issues/8959 +method_exists_bug2 = false # see https://github.com/JuliaLang/julia/issues/9043 and https://github.com/mauro3/Traits.jl/issues/2 +function_types_bug1 = true # set to false if function types get implemented in Julia +# Traits.jl issues: +dispatch_bug1 = true # in traitdispatch.jl +traitdef_bug1 = true +varag_not_supported_bug = true +concrete_type_bug = true - +# src/Traits.jl tests type A1 end type A2 end - @test !istraittype(A1) -## BUG flags: set to false once fixed to activate tests -# Julia issues: -method_exists_bug1 = true # see https://github.com/JuliaLang/julia/issues/8959 -method_exists_bug2 = true # see https://github.com/JuliaLang/julia/issues/9043 and https://github.com/mauro3/Traits.jl/issues/2 -# Traits.jl issues: -dispatch_bug1 = true # in traitdispatch.jl +# istrait helper function: +@test Traits.subs_tvar(TypeVar(:I), Array{TypeVar(:I,Int64)}, Traits._TestType{1})==Array{TypeVar(:I,Int64)} +@test Traits.subs_tvar(TypeVar(:I,Int64), Array{TypeVar(:I,Int64)}, Traits._TestType{1})==Array{Traits._TestType{1}} +@test Traits.subs_tvar(TypeVar(:T), Array, Traits._TestType{1})==Array{Traits._TestType{1}} # this is kinda bad +f8576{T}(a::Array, b::T) = T +other_T = f8576.env.defs.tvars +@test Traits.subs_tvar(other_T, Array, Traits._TestType{1})==Array # f8576.env.defs.tvars looks like TypeVar(:T) but is different! + +@test Traits.find_tvar( (Array, ), TypeVar(:T))==[true] +@test Traits.find_tvar( (Array, ), other_T)==[false] +@test Traits.find_tvar( (Int, Array, ), TypeVar(:T))==[false,true] # manual implementations include("manual-traitdef.jl") diff --git a/test/traitdef.jl b/test/traitdef.jl index 172a90f..af90534 100644 --- a/test/traitdef.jl +++ b/test/traitdef.jl @@ -2,9 +2,15 @@ td = :(@traitdef Cr20{X} begin length(X) end) -a,b = Traits.parsebody(td.args[end]) -@test a==Expr(:dict, :(length=>((X,),Any))) +a,b,c = Traits.parsebody(td.args[end]) +# a is not hard to test because of the random gensym +@test a.head==:call +@test a.args[1]==:(Traits.FDict) +@test a.args[2].head==:(=>) +@test a.args[2].args[1] == :length +@test a.args[2].args[2].args[2] == :(Any()) @test b==:(Bool[]) +@test c.args[1]==:(assoctyps = Any[]) td0 = :(@traitdef Cr20{X} begin length(X) @@ -14,7 +20,6 @@ td0 = :(@traitdef Cr20{X} begin end end) a,b = Traits.parsebody(td0.args[end]) -@test a==Expr(:dict, :(length=>((X,),Any))) @test b==:(Bool[(string(X.name))[1] == 'I']) td1 = :(@traitdef Cr20{X} begin @@ -25,7 +30,6 @@ td1 = :(@traitdef Cr20{X} begin end end) a,b = Traits.parsebody(td1.args[end]) -@test a==Expr(:dict, :(length=>((X,),Int))) @test b==:(Bool[(string(X.name))[1] == 'I']) td2 = :(@traitdef Cr20{X,Y} begin @@ -38,9 +42,6 @@ td2 = :(@traitdef Cr20{X,Y} begin end end) a,b,c = Traits.parsebody(td2.args[end]) -@test a==Expr(:dict, :((+) => ((X,Y),(Int,Float64))), - :((-) => ((X,Y),Int)), - :((/) => ((X,Y),Int))) @test b==:(Bool[(string(X.name))[1] == 'I']) @test c.head==:block @@ -48,20 +49,19 @@ td3 = :(@traitdef Cr20{X,Y} begin fn(X) -> Type{X} end) a,b,c = Traits.parsebody(td3.args[end]) -@test a==Expr(:dict, :((fn) => ((X,),Type{X}))) -td4 = :(@traitdef Cr20{X} begin - fn{Y<:II}(X,Y) -> Type{X} - fn76{K<:FloatingPoint, I<:Integer}(X, Vector{I}, Vector{K}) -> I -end) -a,b,c = Traits.parsebody(td4.args[end]) -v = :(TypeVar(symbol("Y"),II)) -t = :(TypeVar(symbol("I"),Integer)) -k = :(TypeVar(symbol("K"),FloatingPoint)) +# td4 = :(@traitdef Cr20{X} begin +# fn{Y<:II}(X,Y) -> Type{X} +# fn76{K<:FloatingPoint, I<:Integer}(X, Vector{I}, Vector{K}) -> I +# end) +# a,b,c = Traits.parsebody(td4.args[end]) +# v = :(TypeVar(symbol("Y"),II)) +# t = :(TypeVar(symbol("I"),Integer)) +# k = :(TypeVar(symbol("K"),FloatingPoint)) -@test a==Expr(:dict, :(fn=>((X,$v),Type{X})), - :(fn76=>((X,Vector{$t},Vector{$k}),$t)) - ) +# @test a==Expr(:dict, :(fn=>((X,$v),Type{X})), +# :(fn76=>((X,Vector{$t},Vector{$k}),$t)) +# ) ## test making traits @@ -76,15 +76,15 @@ end @test !istrait(Cmp{Int,String}) -coll = [Vector{Int}, Dict{Int,Int}, Set{Int}] +coll = [Vector, Vector{Int}, Dict{Int}, Dict{Int,Int}, Set{Int}] iter = [Traits.GenerateTypeVars{:upcase}, Int] #todo: add String, if method_exists_bug1 - assoc = [] #todo add again: Dict{Int,Int}] # , ObjectIdDict] + dicts = [] #todo add again: Dict{Int,Int}] # , ObjectIdDict] else - assoc = [Array{Int,2}, Dict{Int,Int}, StepRange{Int,Int}] + dicts = [Dict{Int}, Dict{Int,Int}] # Dict does not work, ObjectIdDict does not fulfill the trait end index = [Array{Int,2}, StepRange{Int,Int}] - +c =1 for c in coll @test istrait(Collection{c}, verbose=true) @test istrait(Iter{c}, verbose=true) @@ -98,7 +98,7 @@ for c in iter @test istrait(Iter{c}, verbose=true) end -for c in assoc +for c in dicts @test istrait(Assoc{c}, verbose=true) end @@ -119,6 +119,12 @@ for a1 in arith end ## test trait definition +@traitdef FF{X} begin + f948576() +end +@test !istrait(FF{Int}) +f948576() = 1 +@test istrait(FF{Int}) @traitdef Tr20{X} begin length(X) -> Bool @@ -157,19 +163,15 @@ end @test issubtrait(Tr13, Tr20) @test issubtrait((Tr21,), (Tr20,)) -@test issubtrait((Tr21,Tr11), (Tr20,Tr10)) +@test issubtrait((Tr21,Tr11), (Tr20,Tr10)) +@test !issubtrait((Tr21,Tr11), (Tr10,Tr20)) # todo: this should be true, as order shouldn't matter @test issubtrait((Tr11,Tr21), (Tr10,Tr20)) -@test !issubtrait((Tr21,Tr11), (Tr10,Tr20)) # todo: this should be true, I think @test !issubtrait(Tr21{Int}, Tr20{Float64}) @test !issubtrait((Tr21{Int},), (Tr20{Float64},)) -#--> need to be able to do this in terms of type variables. - -# test functions parameterized on non-trait parameters. This isn't currently working: -# https://github.com/mauro3/Traits.jl/issues/2 -# https://github.com/JuliaLang/julia/issues/9043 - +# Test functions parameterized on non-trait parameters. +### @traitdef Pr0{X} begin fn75{Y <: Integer}(X, Y) -> Y end @@ -181,9 +183,6 @@ else end @test !istrait(Pr0{Int8}) -fn75(x::UInt8, y::Int8) = y+x -@test !istrait(Pr0{UInt8}) # this works, not because only for y::Int8 not for all Integers - @traitdef Pr1{X} begin fn76{I<:Integer}(X, Vector{I}) -> I end @@ -193,20 +192,35 @@ if method_exists_bug2 else @test istrait(Pr1{UInt8}) end -@test !istrait(Pr1{UInt8}) @traitdef Pr2{X} begin fn77{Y<:Number}(X,Y,Y) -> Y # fn77{Y}(X) end fn77(a::Array,b::Int, c::Float64) = a[1] -if method_exists_bug2 - @test !istrait(Pr2{Array}) +@test !istrait(Pr2{Array}) +fn77{Y<:Real}(a::Array,b::Y, c::Y) = a[1] +@test !istrait(Pr2{Array}) +fn77{Y<:Number}(a::Array,b::Y, c::Y) = a[1] +@test istrait(Pr2{Array}) + + +@traitdef Pr3{X} begin + fn78{X}(X,X) +end +fn78(b::Int, c::Int) = b +if concrete_type_bug + @test !istrait(Pr3{Int}) # this should not fail! else - @test istrait(Pr2{Array}) + @test istrait(Pr3{Int}) # this should not fail! end -# test constraints +fn78(b::Real, c::Real) = b +@test !istrait(Pr3{Real}) +fn78{T}(b::T, c::T) = b +@test istrait(Pr3{Real}) +# Test constraints +### @traitdef Cr20{X} begin length(X) -> Any @@ -215,8 +229,6 @@ end end end -@test Cr20{Int}().methods==Dict(length => ((Int,),Any)) - @test !istrait(Cr20{Float32}) @test istrait(Cr20{Int}) @@ -309,32 +321,53 @@ AssocIsBits{T3484675{Int,4.5,:a}}() D() -> D end type A4758 end +type A4759 + a +end @test istrait(TT45{A4758}) +@test !istrait(TT45{A4759}) @test istrait(TT45{Dict{Int,Int}}) @test istrait(TT45{Set{Int}}) @test !istrait(TT45{Int}) @test !istrait(TT45{Array{Int,1}}) -# This is the trait for datatypes with Array like constructors: -@traitdef TT46{Ar} begin - T = Type{eltype(Ar)} - Arnp = deparameterize_type(Ar) # Array stripped of type parameters - - #Arnp(T, Int64) -> Ar - Arnp(T, Int...) -> Ar # see issue #8 & https://github.com/JuliaLang/julia/issues/10642 - @constraints begin - length(Ar.parameters)>1 # need at least two parameters to be array-like, right? + + +if !varag_not_supported_bug + @traitdef TT44{D} begin + # + Array(Type{D},Any) end + @test istrait(TT44{A4758}) + @test istrait(TT44{A4759}) + @test istrait(TT44{Dict{Int,Int}}) + @test istrait(TT44{Set{Int}}) + @test istrait(TT44{Int}) + @test istrait(TT44{Array{Int,1}}) + + + # This is the trait for datatypes with Array like constructors: + @traitdef TT46{Ar} begin + T = Type{eltype(Ar)} + Arnp = deparameterize_type(Ar) # Array stripped of type parameters + + #Arnp(T, Int64) -> Ar + Arnp(T, Int...) -> Ar # see issue #8 & https://github.com/JuliaLang/julia/issues/10642 + @constraints begin + length(Ar.parameters)>1 # need at least two parameters to be array-like, right? + end + end + @test !istrait(TT46{A4758}) + if Traits.flag_check_return_types + @test !istrait(TT46{Dict{Int,Int}}) + else + @test istrait(TT46{Dict{Int,Int}}, verbose=true) # this is a false positive + end + # @test istrait(TT46{Set{Int}}, verbose=true) this actually works, but not as expected and gives a deprecation warning + @test !istrait(TT46{Int}) + @test istrait(TT46{Array{Int,1}}, verbose=true) + # @test istrait(TT46{Array{Int}}, verbose=true) # this does not pass currently because of https://github.com/JuliaLang/julia/issues/10642 + @test istrait(TT46{Array}, verbose=true) end -@test !istrait(TT46{A4758}) -if Traits.flag_check_return_types - @test !istrait(TT46{Dict{Int,Int}}) -else - @test istrait(TT46{Dict{Int,Int}}, verbose=true) # this is a false positive -end -# @test istrait(TT46{Set{Int}}, verbose=true) this actually works, but not as expected and gives a deprecation warning -@test !istrait(TT46{Int}) -@test istrait(TT46{Array{Int,1}}, verbose=true) -# @test istrait(TT46{Array{Int}}, verbose=true) # this does not pass currently because of https://github.com/JuliaLang/julia/issues/10642 -@test istrait(TT46{Array}, verbose=true) +