From bff687be7376d03ed569618c4085cc1df9769d98 Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Thu, 9 Apr 2015 21:09:22 +0200 Subject: [PATCH 01/24] prototyping in manual-traitdef-v2.jl --- test/manual-traitdef-v2.jl | 402 +++++++++++++++++++++++++++++++++++++ 1 file changed, 402 insertions(+) create mode 100644 test/manual-traitdef-v2.jl diff --git a/test/manual-traitdef-v2.jl b/test/manual-traitdef-v2.jl new file mode 100644 index 0000000..10e8ab2 --- /dev/null +++ b/test/manual-traitdef-v2.jl @@ -0,0 +1,402 @@ +# # This uses methods to encode the methods... + + +# @traitdef Pr2{X} begin +# fn77{Y<:Number}(X,Y,Y) -> Y +# end + + +# using Traits +# immutable Pr2{X} <: Traits.Trait{()} # /home/mauro/.julia/v0.4/Traits/src/traitdef.jl, line 277: +# methods::Dict{Union(Function,DataType),Function} # line 278: +# constraints::Vector{Bool} # line 279: +# assoctyps::Vector{Any} # line 280: +# function Pr2() # /home/mauro/.julia/v0.4/Traits/src/traitdef.jl, line 281: +# assoctyps = Any[] +# _fn77{Y<:Number}(::X,::Y,::Y) = Y +# _fn77{Y<:Number}(::Type{X},::Type{Y},::Type{Y}) = Y # arithmetic on types: https://github.com/JuliaLang/julia/issues/8027#issuecomment-52519612 +# new(Dict( +# fn77 => _fn77 +# ), +# Bool[], +# assoctyps) +# end +# end + +# fn77(a::Array,b::Int, c::Float64) = a[1] # this is no good +# p2 = Pr2{Array}() + +# # # checks full-filled +# # function istrait_{T<:Traits.Trait}(t::Type{T}) +# # t = t() +# # check_meths = methods(t.methods[fn77], (Array,Any...)) # how to construct (Array,Any...)? +# # # assume just one for now (but https://github.com/mauro3/Traits.jl/issues/8) +# # cm = collect(check_meths)[1] +# # checks = false +# # @show cm.sig +# # for m in methods(fn77, (Array,Any...)) +# # @show m.sig +# # if m.sig==cm.sig # too restrictive and m.sig<:cm.sig is too permissive +# # checks = true +# # break +# # end +# # end +# # checks +# # end +# # @show istrait_(Pr2{Array}) + +# # # add good method +# # fn77(a::Array,b::Int, c::Int) = a[1] # this is good +# # @show istrait_(Pr2{Array}) # does not work...: +# # # julia> istrait_(Pr2{Array}) # does not work...: +# # # cm.sig = (Array{T,N},Y<:Number,Y<:Number) +# # # m.sig = (Array{T,N},Int64,Float64) +# # # m.sig = (Array{T,N},Int64,Int64) +# # # false + +# ## # checks full-filled +# function istrait_v2{T<:Traits.Trait}(t::Type{T}) +# # Need to check for each method mt in t.methods whether at least +# # one m.sig<:mt.sig, where m runs over all methods of the generic +# # function in question. + +# X = t.parameters[1] +# t = t() +# # assume just one for now (but https://github.com/mauro3/Traits.jl/issues/8) + + +# check_meths = methods(t.methods[fn77], (X,Any...)) # how to construct (Array,Any...)? +# # assume just one method for now (but https://github.com/mauro3/Traits.jl/issues/8) +# cm = collect(check_meths)[1] + +# checks = false +# ret_typ = () +# for m in methods(fn77) +# @show m.sig +# if m.sig==cm.sig # too restrictive and m.sig<:cm.sig is too permissive +# checks = true +# break +# end + +# try +# @show applicable(t.methods[fn77], m.sig...) +# @show ret_typ = t.methods[fn77](m.sig...) +# checks = true +# break +# catch err +# @show err +# end +# end +# return checks, ret_typ +# end +# @show istrait_v2(Pr2{Array}) + +# # add good method +# fn77(a::Array,b::Int, c::Int) = a[1] # this is good +# istrait_v2(Pr2{Array}) + +# # try parametric method +# fn77{T<:Number}(a::String, b::T, c::T) = 5 +# istrait_v2(Pr2{String}) +# istrait_v2(Pr2{ASCIIString}) # doesn't work because of typevar + +# fn77{S<:Integer}(a::S, b::Int, c::Int) = 5 +# istrait_v2(Pr2{Integer}) # doesn't work because of typevar + + +# fn77{S<:Integer}(a::S, b::Int, c::Int) = 5 +# istrait_v2(Pr2{Integer}) # doesn't work because of typevar + +# # so what to do? + + + +######## another try + +using Traits + +immutable TestType{T} end +# helpers for isfitting +function subs_tvar(tv::TypeVar, arg, TestT) + # Substitute a particular TypeVar in an argument with a test-type. + # Example: + # Array{I<:Int64,N} -> Array{TestType{23},N} + # println(" ") + # @show (tv, arg, TestT) + # @show isa(arg, DataType) + # @show isleaftype(arg) + # @show isa(arg, DataType) && ( isleaftype(arg) || length(arg.parameters)==0 ) + # @show isa(arg,TypeVar) + if isa(arg, DataType) && (isleaftype(arg) || length(arg.parameters)==0) # concrete type or abstract type with no parameters + return arg + elseif isa(arg,TypeVar) + if tv===arg # note === this it essential! + return TestT # replace + else + return arg + end + 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 +@assert subs_tvar(TypeVar(:I), Array{TypeVar(:I,Int64)}, TestType{1})==Array{TypeVar(:I,Int64)} +@assert subs_tvar(TypeVar(:I,Int64), Array{TypeVar(:I,Int64)}, TestType{1})==Array{TestType{1}} +@assert subs_tvar(TypeVar(:T), Array, TestType{1})==Array{TestType{1}} # this is kinda bad +f8576{T}(a::Array, b::T) = T +other_T = f8576.env.defs.tvars +@assert subs_tvar(other_T, Array, TestType{1})==Array # f8576.env.defs.tvars looks like TypeVar(:T) but is different! + +function find_tvar(sig::Tuple, tv) + # Finds index of arguments in a function signature where a + # particular TypeVar features. Example: + # + # find_tvar( (T, Int, Array{T}) -> [true, false, true] + @show "tuple", sig, 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) = ( @show "TypeVar", sig, tv; sig===tv ? [true] : [false]) # note === this it essential! +function find_tvar(sig::DataType, tv) + @show "DataType", sig, 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] + +@assert find_tvar( (Array, ), TypeVar(:T))==[true] +@assert find_tvar( (Array, ), other_T)==[false] +@assert find_tvar( (Int, Array, ), TypeVar(:T))==[false,true] + +## # checks full-filled +function isfitting(tm::Method, fm::Method; verbose=false) # tm=trait-method, fm=function-method + # Checks tm.sig<:fm.sig and that the parametric constraints on fm + # are 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 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 + + printfn = verbose ? println : x->x + + # No Vararg methods + if tm.va || fm.va + warning("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 + printfn("Reason fail: no tvars-constraints in trait-method but in function-method.") + return false + end + end + end + printfn("Reason fail/pass: no tvars in trait-method") + 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) + printfn("Reason fail: !(tm.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)! + printfn("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 + printfn("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. It's ok if ftv + # constrains more arguments than tv! + ftvs = Any[] + fmtvars = isa(fm.tvars,TypeVar) ? (fm.tvars,) : fm.tvars + for ftv in fmtvars + flocs = find_tvar(fm.sig, ftv) + if all(flocs[find(locs)]) + push!(ftvs,ftv) + end + end + if ftvs==Any[] + printfn("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) + printfn("Reason fail: parametric constraints on args $(tm.sig[i]) and $(fm.sig[i]) on different TypeVar locations!") + return false + end + end + end + + printfn("Reason pass: all checks passed") + return true +end + + +function istrait_v3{T<:Traits.Trait}(Tr::Type{T}; verbose=false) + # Need to check for each method mt in t.methods whether at least + # one m.sig<:mt.sig, where m runs over all methods of the generic + # function in question. + t = 1 + try + t = Tr() + catch err + println("Error occured when instatiating type: $err") + return false + end + ret_typ = () + + # check method signature + checks = true + for (gf,_gf) in t.methods # loop over all generic functions in traitdef + @show gf + checks = false + 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 + end + if !checks + return false + end + end + end + return checks +end + + + +# @traitdef Pr2{X} begin +# fn77{Y<:Number}(X,Y,Y) -> Y +# end + +immutable Pr2{X} <: Traits.Trait{()} # /home/mauro/.julia/v0.4/Traits/src/traitdef.jl, line 277: + methods::Dict{Union(Function,DataType),Function} # line 278: + constraints::Vector{Bool} # line 279: + assoctyps::Vector{Any} # line 280: + function Pr2() # /home/mauro/.julia/v0.4/Traits/src/traitdef.jl, line 281: + assoctyps = Any[] + _fn77{Y<:Number}(::X,::Y,::Y) = Y # note, this will make separate generic functions for different X, which is good. + # _fn77{Y<:Number}(::Type{X},::Type{Y},::Type{Y}) = Y + #_fn77 = (X,Y,Z) -> + new(Dict( + fn77 => _fn77 + ), + Bool[], + assoctyps) + end +end + +@show istrait_v3(Pr2{Array}) + +fn77(a::Array,b::Int, c::Int) = a[1] # this is no good, not general enough +@assert !istrait_v3(Pr2{Array}) + +# try parametric method +fn77{T<:Number}(a::String, b::T, c::T) = 5 +@assert istrait_v3(Pr2{String}) +@assert istrait_v3(Pr2{ASCIIString}) + +fn77{S<:Integer}(a::S, b::Int, c::Int) = 5 # this is no good, not general enough +@assert !istrait_v3(Pr2{Integer}) + +fn77{S<:Integer, N}(a::S, b::N, c::N) = 5 +@assert istrait_v3(Pr2{Integer}) + +# ############ +# # Test cases for later +# ############ +# using Base.Test + +# bug_ret_type1 = true + +# @traitdef Tr01{X} begin +# g01{T<:X}(T, T) -> T +# end +# g01(::Int, ::Int) = Int +# @test istrait(Tr01{Int}) # == true +# @test_throws 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 +# 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 bug_ret_type1 +# @test istrait(Tr02{Integer}) +# # or throw an error/warning here saying parametric return types +# # are only supported for leaftypes +# else +# @test_throws 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 +# g03{I<:Integer}(::I, ::Vector{I}) = 1 +# @test istrait(Tr03{Integer}) +# @test istrait(Tr03{Int}) From 514ae742632d3e65cff78cd9e936e224e72bf0ad Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Thu, 9 Apr 2015 22:07:24 +0200 Subject: [PATCH 02/24] Updated Traits.jl and manual-traitdef.jl tests. Of course, the tests after manual-traitdef.jl are failing --- src/Traits.jl | 397 ++++++++++++++++++++++++++++++---------- test/manual-traitdef.jl | 107 ++++++----- test/runtests.jl | 24 ++- 3 files changed, 377 insertions(+), 151 deletions(-) diff --git a/src/Traits.jl b/src/Traits.jl index 6a682df..c6cca25 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,129 @@ 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 + 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 $gf matched the + trait specification: $tm""") + return false end - else - throw(TraitException("Trait $Tr has something funny in its method dictionary: $meth.")) end end + + # for (meth,sig) in tr + # # 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. + # println_verb("Method $meth with call signature $(sig[1]) not defined for $T") + # checks = 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 + # println_verb("Datatype constructor $meth with call signature $sigg not defined for trait $T") + # checks = false + # end + # else + # throw(TraitException("Trait $Tr contains a funny entry 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!") + 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) + 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) + for fr in fret_typ + if !(fr<:tret_typ) + println_verb("") + return false end end - # else - # out = false - # if verbose - # println("Method `$meth` with signature $sigg->$(sig[2]) has more than one return type!") + # # 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 = [] + # checks = false + # println_verb("Method `$meth` with signature $sigg->$(sig[2]) has an empty return signature!") + # else#if length(tmp)==1 + # rettype = tmp[1] + # if !(rettype<:sig[2]) + # checks = false + # println_verb("Method `$meth` with signature $sigg->$(sig[2]) has wrong return type: $rettype") # end + # # else + # # checks = false + # # if verbose + # # println("Method `$meth` with signature $sigg->$(sig[2]) has more than one return type!") + # # end + # 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 +229,174 @@ function istrait(Trs::Tuple; verbose=false) return true end +## Helpers for istrait +@doc """isfitting checks whether a method `tm` specified in the trait definition + is fulfilled by a method `fm` of the corresponding generic function. One of the + core functions of 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 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(tm::Method, fm::Method; verbose=false) # tm=trait-method, fm=function-method + println_verb = verbose ? println : x->x + + # No Vararg methods implement yet + if tm.va || fm.va + 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") + 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)") + 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. It's ok if ftv + # constrains more arguments than tv! + ftvs = Any[] + fmtvars = isa(fm.tvars,TypeVar) ? (fm.tvars,) : fm.tvars + for ftv in fmtvars + flocs = find_tvar(fm.sig, ftv) + if all(flocs[find(locs)]) + push!(ftvs,ftv) + end + end + if ftvs==Any[] + 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, TestT::Type{T}) + # Substitute `TestT` for a particular TypeVar `tv` in an argument `arg`. + # + # Example: + # Array{I<:Int64,N} -> Array{_TestType{23},N} + if isa(arg, DataType) && (isleaftype(arg) || length(arg.parameters)==0) # concrete type or abstract type with no parameters + return arg + elseif isa(arg,TypeVar) + if tv===arg # note === this it essential! + return TestT # replace + else + return arg + end + 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 + +# 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 +433,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/test/manual-traitdef.jl b/test/manual-traitdef.jl index 06bafb6..11a68f8 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 diff --git a/test/runtests.jl b/test/runtests.jl index dc21e58..45edb05 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,13 +1,6 @@ # tests using Base.Test using Traits - - -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 @@ -15,6 +8,23 @@ method_exists_bug2 = true # see https://github.com/JuliaLang/julia/issues/9043 a # Traits.jl issues: dispatch_bug1 = true # in traitdispatch.jl +# src/Traits.jl tests +type A1 end +type A2 end +@test !istraittype(A1) + +# 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") include("manual-traitimpl.jl") From 974e69e53143a3db292fbb062ce564e6d79ef920 Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Fri, 10 Apr 2015 13:13:33 +0200 Subject: [PATCH 03/24] New tests for parametric methods in manual-traitdef.jl working --- src/Traits.jl | 30 ++++++++-------- test/manual-traitdef.jl | 80 +++++++++++++++++++++++++++++++++++++++++ test/runtests.jl | 4 ++- 3 files changed, 99 insertions(+), 15 deletions(-) diff --git a/src/Traits.jl b/src/Traits.jl index c6cca25..8d0cea3 100644 --- a/src/Traits.jl +++ b/src/Traits.jl @@ -277,7 +277,9 @@ function isfitting(tm::Method, fm::Method; verbose=false) # tm=trait-method, fm= # 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)") + 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 @@ -306,17 +308,21 @@ function isfitting(tm::Method, fm::Method; verbose=false) # tm=trait-method, fm= 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. It's ok if ftv - # constrains more arguments than tv! + # Find the tvar in fm which corresponds to tv. ftvs = Any[] - fmtvars = isa(fm.tvars,TypeVar) ? (fm.tvars,) : fm.tvars + 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 ftvs==Any[] + 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 @@ -344,25 +350,21 @@ function isfitting(tm::Method, fm::Method; verbose=false) # tm=trait-method, fm= end # helpers for isfitting -function subs_tvar{T<:_TestType}(tv::TypeVar, arg, TestT::Type{T}) +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 isa(arg, DataType) && (isleaftype(arg) || length(arg.parameters)==0) # concrete type or abstract type with no parameters + if isleaftype(arg) || length(arg.parameters)==0 # concrete type or abstract type with no parameters return arg - elseif isa(arg,TypeVar) - if tv===arg # note === this it essential! - return TestT # replace - else - return arg - end - else # It's a parameterized type do substitution on all parameters: + 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: diff --git a/test/manual-traitdef.jl b/test/manual-traitdef.jl index 11a68f8..ca79be1 100644 --- a/test/manual-traitdef.jl +++ b/test/manual-traitdef.jl @@ -158,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 traitdef_bug1 + @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/runtests.jl b/test/runtests.jl index 45edb05..8f06293 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -5,8 +5,10 @@ using Traits # 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 +function_types_bug1 = true # set to false if function types get implemented in Julia # Traits.jl issues: -dispatch_bug1 = true # in traitdispatch.jl +dispatch_bug1 = true # in traitdispatch.jl +traitdef_bug1 = true # src/Traits.jl tests type A1 end From 6df1715320af1a0e675ca4ffb6633e33a2bc543e Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Fri, 10 Apr 2015 14:26:47 +0200 Subject: [PATCH 04/24] manual-*.jl all passing --- test/manual-traitimpl.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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: From da717c8cac0e98d4acf5ff6581d54cec4bf9ef6f Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Fri, 10 Apr 2015 16:00:39 +0200 Subject: [PATCH 05/24] Updated src/traitdef.jl. Almost all tests passing. --- src/Traits.jl | 64 +++++++----------------------- src/traitdef.jl | 63 +++++++++++++---------------- test/runtests.jl | 3 +- test/traitdef.jl | 101 ++++++++++++++++++++++++----------------------- 4 files changed, 96 insertions(+), 135 deletions(-) diff --git a/src/Traits.jl b/src/Traits.jl index 8d0cea3..18de6da 100644 --- a/src/Traits.jl +++ b/src/Traits.jl @@ -156,66 +156,31 @@ function istrait{T<:Trait}(Tr::Type{T}; verbose=false) end end - # for (meth,sig) in tr - # # 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. - # println_verb("Method $meth with call signature $(sig[1]) not defined for $T") - # checks = 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 - # println_verb("Datatype constructor $meth with call signature $sigg not defined for trait $T") - # checks = false - # end - # else - # throw(TraitException("Trait $Tr contains a funny entry in its method dictionary: $meth.")) - # end - # end - # check return-type 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) + @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 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) - println_verb("") - return false + if fr<:tret_typ + checks = true end end - # # 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 = [] - # checks = false - # println_verb("Method `$meth` with signature $sigg->$(sig[2]) has an empty return signature!") - # else#if length(tmp)==1 - # rettype = tmp[1] - # if !(rettype<:sig[2]) - # checks = false - # println_verb("Method `$meth` with signature $sigg->$(sig[2]) has wrong return type: $rettype") - # end - # # else - # # checks = false - # # if verbose - # # println("Method `$meth` with signature $sigg->$(sig[2]) has more than one return type!") - # # end - # end + if !checks + println_verb("""No return types found which are subtypes of the specified return type: + $tret_typ + found: + $fret_typ + """) + return false + end end end end @@ -254,6 +219,7 @@ function isfitting(tm::Method, fm::Method; verbose=false) # tm=trait-method, fm= # No Vararg methods implement yet if tm.va || fm.va + # runtests.jl flag: varag_not_supported_bug println_verb("Vararg methods not currently supported. Returning false.") return false end @@ -271,7 +237,7 @@ function isfitting(tm::Method, fm::Method; verbose=false) # tm=trait-method, fm= end end end - println_verb("Reason fail/pass: no tvars in trait-method") + 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 diff --git a/src/traitdef.jl b/src/traitdef.jl index d0e87a5..b04655d 100644 --- a/src/traitdef.jl +++ b/src/traitdef.jl @@ -95,7 +95,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 +132,30 @@ 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(...) + + _fn = deepcopy(def) + if isa(def.args[1], Symbol) # f(X,Y) or X+Y fn = def.args[1] + _fn.args[1] = gensym(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] = gensym(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 +163,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 +174,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 +195,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 +267,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/runtests.jl b/test/runtests.jl index 8f06293..eb45c89 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -4,11 +4,12 @@ using Traits ## 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 +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 # src/Traits.jl tests type A1 end diff --git a/test/traitdef.jl b/test/traitdef.jl index 172a90f..49818bd 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 @@ -181,9 +181,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,18 +190,18 @@ 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}) -else - @test istrait(Pr2{Array}) -end +@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}) + # test constraints @traitdef Cr20{X} begin @@ -215,8 +212,6 @@ end end end -@test Cr20{Int}().methods==Dict(length => ((Int,),Any)) - @test !istrait(Cr20{Float32}) @test istrait(Cr20{Int}) @@ -309,32 +304,38 @@ 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 + # 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) From 54d899e7a0b089be69fdfdfb9144f9abf10f9252 Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Fri, 10 Apr 2015 16:39:19 +0200 Subject: [PATCH 06/24] All tests passing except a few: which are marked in test/traitdef.jl with: varag_not_supported_bug = true constructors_not_supported_bug = true --- src/Traits.jl | 9 ++++++++ test/runtests.jl | 1 + test/traitdef.jl | 57 ++++++++++++++++++++++++++++++++---------------- 3 files changed, 48 insertions(+), 19 deletions(-) diff --git a/src/Traits.jl b/src/Traits.jl index 18de6da..7f8054d 100644 --- a/src/Traits.jl +++ b/src/Traits.jl @@ -139,6 +139,9 @@ function istrait{T<:Trait}(Tr::Type{T}; verbose=false) # check call signature of methods: 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 @@ -217,6 +220,12 @@ end function isfitting(tm::Method, fm::Method; verbose=false) # tm=trait-method, fm=function-method println_verb = verbose ? println : x->x + # special casing for call-overloading: + if fm.func.code.name==:call && tm.func.code.name!=:call + # make a call-like method + error("Constructors not supported yet.") + end + # No Vararg methods implement yet if tm.va || fm.va # runtests.jl flag: varag_not_supported_bug diff --git a/test/runtests.jl b/test/runtests.jl index eb45c89..8bdc127 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -10,6 +10,7 @@ function_types_bug1 = true # set to false if function types get implemented in J dispatch_bug1 = true # in traitdispatch.jl traitdef_bug1 = true varag_not_supported_bug = true +constructors_not_supported_bug = true # src/Traits.jl tests type A1 end diff --git a/test/traitdef.jl b/test/traitdef.jl index 49818bd..4ed459f 100644 --- a/test/traitdef.jl +++ b/test/traitdef.jl @@ -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 @@ -297,25 +303,36 @@ AssocIsBits{T3484675{Int,4.5,:a}}() #### # DataType constructors #### - -@traitdef TT45{D} begin - # This trait contains all datatypes which have a constructor with - # no arguments. - 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}}) - -if varag_not_supported_bug +if !constructors_not_supported_bug + @traitdef TT45{D} begin + # This trait contains all datatypes which have a constructor with + # no arguments. + 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}}) + + @traitdef TT44{D} begin + # + Array(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}}) + +if !varag_not_supported_bug # This is the trait for datatypes with Array like constructors: @traitdef TT46{Ar} begin T = Type{eltype(Ar)} @@ -339,3 +356,5 @@ if varag_not_supported_bug # @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 + + end # !constructors_not_supported_bug From d762137f29443817eb7b8d110af5e9cc35d555d5 Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Fri, 10 Apr 2015 17:17:21 +0200 Subject: [PATCH 07/24] Mostly running now. These two bugs do not matter anymore as the new tests do not rely on method_exists: 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 --- src/Traits.jl | 10 ++++++---- src/base_fixes.jl | 5 +++++ src/commontraits.jl | 22 +++++++++++----------- src/traitdef.jl | 7 +++++-- test/runtests.jl | 2 +- test/traitdef.jl | 8 ++++---- 6 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/Traits.jl b/src/Traits.jl index 7f8054d..6cf7de1 100644 --- a/src/Traits.jl +++ b/src/Traits.jl @@ -159,8 +159,10 @@ function istrait{T<:Trait}(Tr::Type{T}; verbose=false) end end - # check return-type - if flag_check_return_types + # 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 @@ -177,9 +179,9 @@ function istrait{T<:Trait}(Tr::Type{T}; verbose=false) end end if !checks - println_verb("""No return types found which are subtypes of the specified return type: + println_verb("""For function $gf: no return types found which are subtypes of the specified return type: $tret_typ - found: + List of found return types: $fret_typ """) return false 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..133f02e 100644 --- a/src/commontraits.jl +++ b/src/commontraits.jl @@ -58,17 +58,17 @@ end 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 b04655d..c5a212b 100644 --- a/src/traitdef.jl +++ b/src/traitdef.jl @@ -138,13 +138,16 @@ function parsefnstypes!(outfns, ln) # parses f(X,Y), f{X <:T}(X,Y) and 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] = gensym(fn) + _fn.args[1] = getsymbol(fn) elseif def.args[1].head==:curly # f{X}(X,Y) fn = def.args[1].args[1] - _fn.args[1].args[1] = gensym(fn) + _fn.args[1].args[1] = getsymbol(fn) else throw(TraitException( "Something went wrong parsing the trait function definition:\n$fn")) diff --git a/test/runtests.jl b/test/runtests.jl index 8bdc127..b8362ae 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,7 +3,7 @@ using Base.Test using Traits ## 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_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: diff --git a/test/traitdef.jl b/test/traitdef.jl index 4ed459f..74ffd97 100644 --- a/test/traitdef.jl +++ b/test/traitdef.jl @@ -79,12 +79,12 @@ end coll = [Vector{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 From bed1d08c3d0a8f44818c129f6b8f8e3aa32bf506 Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Fri, 10 Apr 2015 17:45:40 +0200 Subject: [PATCH 08/24] added test case for another bug --- test/runtests.jl | 1 + test/traitdef.jl | 33 ++++++++++++++++++++++----------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index b8362ae..d4edc44 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -11,6 +11,7 @@ dispatch_bug1 = true # in traitdispatch.jl traitdef_bug1 = true varag_not_supported_bug = true constructors_not_supported_bug = true +concrete_type_bug = true # src/Traits.jl tests type A1 end diff --git a/test/traitdef.jl b/test/traitdef.jl index 74ffd97..48aacb0 100644 --- a/test/traitdef.jl +++ b/test/traitdef.jl @@ -163,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 @@ -208,8 +204,23 @@ fn77{Y<:Real}(a::Array,b::Y, c::Y) = a[1] fn77{Y<:Number}(a::Array,b::Y, c::Y) = a[1] @test istrait(Pr2{Array}) -# test constraints +@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(Pr3{Int}) # this should not fail! +end +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 @@ -355,6 +366,6 @@ if !varag_not_supported_bug @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 + end - end # !constructors_not_supported_bug +end # !constructors_not_supported_bug From 995e56e9a4a81972b5efa8ba14298c8e0d35608f Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Fri, 10 Apr 2015 22:48:48 +0200 Subject: [PATCH 09/24] Squashed constructors_not_supported_bug at least for 990 out of 1000 cases. --- src/Traits.jl | 81 ++++++-- src/traitdef.jl | 1 + test/manual-traitdef-v2.jl | 402 ------------------------------------- test/manual-traitdef.jl | 2 +- test/runtests.jl | 1 - test/traitdef.jl | 46 +++-- 6 files changed, 91 insertions(+), 442 deletions(-) delete mode 100644 test/manual-traitdef-v2.jl diff --git a/src/Traits.jl b/src/Traits.jl index 6cf7de1..1a6c7f9 100644 --- a/src/Traits.jl +++ b/src/Traits.jl @@ -152,7 +152,7 @@ function istrait{T<:Trait}(Tr::Type{T}; verbose=false) end end if !checks # if check==false no fitting method was found - println_verb("""No method of the generic function $gf matched the + println_verb("""No method of the generic function/call-overloaded $gf matched the trait specification: $tm""") return false end @@ -201,8 +201,8 @@ end ## Helpers for istrait @doc """isfitting checks whether a method `tm` specified in the trait definition - is fulfilled by a method `fm` of the corresponding generic function. One of the - core functions of istraits. + 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. @@ -210,7 +210,7 @@ end 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 constraints on `sig` and `tsig` need to be equal. + 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) @@ -221,18 +221,51 @@ end """ -> function isfitting(tm::Method, fm::Method; verbose=false) # tm=trait-method, fm=function-method println_verb = verbose ? println : x->x - + iscall_overload(tm,fm) = fm.func.code.name==:call && tm.func.code.name!=:call + # special casing for call-overloading: - if fm.func.code.name==:call && tm.func.code.name!=:call - # make a call-like method - error("Constructors not supported yet.") + if iscall_overload(tm,fm) + @show " " + @show fm + # This is a bad hack: alter the signature of the trait-method + # to include the ::Type{...}! It needs to be undone, to undo + # it only use the @goto instead of return! GOTO, nice! (maybe + # I should use a macro...) + + # store old values to be restored at the end of the goto: + old_tmsig = tm.sig + old_tmtvars = tm.tvars + tm.sig = tuple(fm.sig[1], tm.sig...) + @show fm.sig + @show tm.sig + # check whether there are parameters too! + if fm.tvars!=() + fmtvars = isa(fm.tvars,TypeVar) ? (fm.tvars,) : fm.tvars # make sure it's a tuple of TypeVar + for ftv in fmtvars + @show flocs = find_tvar(fm.sig, ftv) + if flocs[1] # yep, has a constraint like call{T}(::Type{Array{T}},...) + if sum(flocs)==1 + @show tm.tvars = tuple(ftv, tm.tvars...) + else + println_verb("This check is not implemented, returning false.") + # less than 10 of the 1000 methods of call in + # Base end here. However, it does include the + # catch all: call{T}(::Type{T},args...) at + # base.jl:38 + @goto RETURN_FALSE + end + end + end + 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 + @goto RETURN_FALSE end ## Check condition A: # If there are no type-vars then just compare the signatures: @@ -244,12 +277,16 @@ function isfitting(tm::Method, fm::Method; verbose=false) # tm=trait-method, fm= 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 + @goto 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 + if tm.sig<:fm.sig + @goto RETURN_TRUE + else + @goto RETURN_FALSE + end end # If !(tm.sig<:fm.sig) then tm<<:fm is false # but the converse is not true: @@ -257,13 +294,13 @@ function isfitting(tm::Method, fm::Method; verbose=false) # tm=trait-method, fm= println_verb("""Reason fail: !(tm.sig<:fm.sig) tm.sig = $(tm.sig) fm.sig = $(fm.sig)""") - return false + @goto 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 + @goto RETURN_FALSE end # Getting to here means that that condition (A) is fulfilled. @@ -272,7 +309,7 @@ function isfitting(tm::Method, fm::Method; verbose=false) # tm=trait-method, fm= # constraints play no role: if length(tm.sig)==1 println_verb("Reason pass: length(tm.sig)==1") - return true + @goto RETURN_TRUE end # Strategy: go through constraints on trait-method and check @@ -301,7 +338,7 @@ function isfitting(tm::Method, fm::Method; verbose=false) # tm=trait-method, fm= #@test istrait(Tr01{Int}, verbose=true) println_verb("Reason fail: parametric constraints on function method not as severe as on trait-method.") - return false + @goto RETURN_FALSE end if length(ftvs)>1 error("""Not supported if two or more TypeVar appear in the same arguments. @@ -317,13 +354,25 @@ function isfitting(tm::Method, fm::Method; verbose=false) # tm=trait-method, fm= 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 + @goto RETURN_FALSE end end end println_verb("Reason pass: all checks passed") + # only return here so it can be tidied up + @label RETURN_TRUE + if iscall_overload(tm,fm) + tm.sig = old_tmsig + tm.tvars = old_tmtvars + end return true + @label RETURN_FALSE + if iscall_overload(tm,fm) + tm.sig = old_tmsig + tm.tvars = old_tmtvars + end + return false end # helpers for isfitting diff --git a/src/traitdef.jl b/src/traitdef.jl index c5a212b..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 diff --git a/test/manual-traitdef-v2.jl b/test/manual-traitdef-v2.jl deleted file mode 100644 index 10e8ab2..0000000 --- a/test/manual-traitdef-v2.jl +++ /dev/null @@ -1,402 +0,0 @@ -# # This uses methods to encode the methods... - - -# @traitdef Pr2{X} begin -# fn77{Y<:Number}(X,Y,Y) -> Y -# end - - -# using Traits -# immutable Pr2{X} <: Traits.Trait{()} # /home/mauro/.julia/v0.4/Traits/src/traitdef.jl, line 277: -# methods::Dict{Union(Function,DataType),Function} # line 278: -# constraints::Vector{Bool} # line 279: -# assoctyps::Vector{Any} # line 280: -# function Pr2() # /home/mauro/.julia/v0.4/Traits/src/traitdef.jl, line 281: -# assoctyps = Any[] -# _fn77{Y<:Number}(::X,::Y,::Y) = Y -# _fn77{Y<:Number}(::Type{X},::Type{Y},::Type{Y}) = Y # arithmetic on types: https://github.com/JuliaLang/julia/issues/8027#issuecomment-52519612 -# new(Dict( -# fn77 => _fn77 -# ), -# Bool[], -# assoctyps) -# end -# end - -# fn77(a::Array,b::Int, c::Float64) = a[1] # this is no good -# p2 = Pr2{Array}() - -# # # checks full-filled -# # function istrait_{T<:Traits.Trait}(t::Type{T}) -# # t = t() -# # check_meths = methods(t.methods[fn77], (Array,Any...)) # how to construct (Array,Any...)? -# # # assume just one for now (but https://github.com/mauro3/Traits.jl/issues/8) -# # cm = collect(check_meths)[1] -# # checks = false -# # @show cm.sig -# # for m in methods(fn77, (Array,Any...)) -# # @show m.sig -# # if m.sig==cm.sig # too restrictive and m.sig<:cm.sig is too permissive -# # checks = true -# # break -# # end -# # end -# # checks -# # end -# # @show istrait_(Pr2{Array}) - -# # # add good method -# # fn77(a::Array,b::Int, c::Int) = a[1] # this is good -# # @show istrait_(Pr2{Array}) # does not work...: -# # # julia> istrait_(Pr2{Array}) # does not work...: -# # # cm.sig = (Array{T,N},Y<:Number,Y<:Number) -# # # m.sig = (Array{T,N},Int64,Float64) -# # # m.sig = (Array{T,N},Int64,Int64) -# # # false - -# ## # checks full-filled -# function istrait_v2{T<:Traits.Trait}(t::Type{T}) -# # Need to check for each method mt in t.methods whether at least -# # one m.sig<:mt.sig, where m runs over all methods of the generic -# # function in question. - -# X = t.parameters[1] -# t = t() -# # assume just one for now (but https://github.com/mauro3/Traits.jl/issues/8) - - -# check_meths = methods(t.methods[fn77], (X,Any...)) # how to construct (Array,Any...)? -# # assume just one method for now (but https://github.com/mauro3/Traits.jl/issues/8) -# cm = collect(check_meths)[1] - -# checks = false -# ret_typ = () -# for m in methods(fn77) -# @show m.sig -# if m.sig==cm.sig # too restrictive and m.sig<:cm.sig is too permissive -# checks = true -# break -# end - -# try -# @show applicable(t.methods[fn77], m.sig...) -# @show ret_typ = t.methods[fn77](m.sig...) -# checks = true -# break -# catch err -# @show err -# end -# end -# return checks, ret_typ -# end -# @show istrait_v2(Pr2{Array}) - -# # add good method -# fn77(a::Array,b::Int, c::Int) = a[1] # this is good -# istrait_v2(Pr2{Array}) - -# # try parametric method -# fn77{T<:Number}(a::String, b::T, c::T) = 5 -# istrait_v2(Pr2{String}) -# istrait_v2(Pr2{ASCIIString}) # doesn't work because of typevar - -# fn77{S<:Integer}(a::S, b::Int, c::Int) = 5 -# istrait_v2(Pr2{Integer}) # doesn't work because of typevar - - -# fn77{S<:Integer}(a::S, b::Int, c::Int) = 5 -# istrait_v2(Pr2{Integer}) # doesn't work because of typevar - -# # so what to do? - - - -######## another try - -using Traits - -immutable TestType{T} end -# helpers for isfitting -function subs_tvar(tv::TypeVar, arg, TestT) - # Substitute a particular TypeVar in an argument with a test-type. - # Example: - # Array{I<:Int64,N} -> Array{TestType{23},N} - # println(" ") - # @show (tv, arg, TestT) - # @show isa(arg, DataType) - # @show isleaftype(arg) - # @show isa(arg, DataType) && ( isleaftype(arg) || length(arg.parameters)==0 ) - # @show isa(arg,TypeVar) - if isa(arg, DataType) && (isleaftype(arg) || length(arg.parameters)==0) # concrete type or abstract type with no parameters - return arg - elseif isa(arg,TypeVar) - if tv===arg # note === this it essential! - return TestT # replace - else - return arg - end - 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 -@assert subs_tvar(TypeVar(:I), Array{TypeVar(:I,Int64)}, TestType{1})==Array{TypeVar(:I,Int64)} -@assert subs_tvar(TypeVar(:I,Int64), Array{TypeVar(:I,Int64)}, TestType{1})==Array{TestType{1}} -@assert subs_tvar(TypeVar(:T), Array, TestType{1})==Array{TestType{1}} # this is kinda bad -f8576{T}(a::Array, b::T) = T -other_T = f8576.env.defs.tvars -@assert subs_tvar(other_T, Array, TestType{1})==Array # f8576.env.defs.tvars looks like TypeVar(:T) but is different! - -function find_tvar(sig::Tuple, tv) - # Finds index of arguments in a function signature where a - # particular TypeVar features. Example: - # - # find_tvar( (T, Int, Array{T}) -> [true, false, true] - @show "tuple", sig, 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) = ( @show "TypeVar", sig, tv; sig===tv ? [true] : [false]) # note === this it essential! -function find_tvar(sig::DataType, tv) - @show "DataType", sig, 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] - -@assert find_tvar( (Array, ), TypeVar(:T))==[true] -@assert find_tvar( (Array, ), other_T)==[false] -@assert find_tvar( (Int, Array, ), TypeVar(:T))==[false,true] - -## # checks full-filled -function isfitting(tm::Method, fm::Method; verbose=false) # tm=trait-method, fm=function-method - # Checks tm.sig<:fm.sig and that the parametric constraints on fm - # are 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 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 - - printfn = verbose ? println : x->x - - # No Vararg methods - if tm.va || fm.va - warning("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 - printfn("Reason fail: no tvars-constraints in trait-method but in function-method.") - return false - end - end - end - printfn("Reason fail/pass: no tvars in trait-method") - 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) - printfn("Reason fail: !(tm.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)! - printfn("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 - printfn("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. It's ok if ftv - # constrains more arguments than tv! - ftvs = Any[] - fmtvars = isa(fm.tvars,TypeVar) ? (fm.tvars,) : fm.tvars - for ftv in fmtvars - flocs = find_tvar(fm.sig, ftv) - if all(flocs[find(locs)]) - push!(ftvs,ftv) - end - end - if ftvs==Any[] - printfn("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) - printfn("Reason fail: parametric constraints on args $(tm.sig[i]) and $(fm.sig[i]) on different TypeVar locations!") - return false - end - end - end - - printfn("Reason pass: all checks passed") - return true -end - - -function istrait_v3{T<:Traits.Trait}(Tr::Type{T}; verbose=false) - # Need to check for each method mt in t.methods whether at least - # one m.sig<:mt.sig, where m runs over all methods of the generic - # function in question. - t = 1 - try - t = Tr() - catch err - println("Error occured when instatiating type: $err") - return false - end - ret_typ = () - - # check method signature - checks = true - for (gf,_gf) in t.methods # loop over all generic functions in traitdef - @show gf - checks = false - 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 - end - if !checks - return false - end - end - end - return checks -end - - - -# @traitdef Pr2{X} begin -# fn77{Y<:Number}(X,Y,Y) -> Y -# end - -immutable Pr2{X} <: Traits.Trait{()} # /home/mauro/.julia/v0.4/Traits/src/traitdef.jl, line 277: - methods::Dict{Union(Function,DataType),Function} # line 278: - constraints::Vector{Bool} # line 279: - assoctyps::Vector{Any} # line 280: - function Pr2() # /home/mauro/.julia/v0.4/Traits/src/traitdef.jl, line 281: - assoctyps = Any[] - _fn77{Y<:Number}(::X,::Y,::Y) = Y # note, this will make separate generic functions for different X, which is good. - # _fn77{Y<:Number}(::Type{X},::Type{Y},::Type{Y}) = Y - #_fn77 = (X,Y,Z) -> - new(Dict( - fn77 => _fn77 - ), - Bool[], - assoctyps) - end -end - -@show istrait_v3(Pr2{Array}) - -fn77(a::Array,b::Int, c::Int) = a[1] # this is no good, not general enough -@assert !istrait_v3(Pr2{Array}) - -# try parametric method -fn77{T<:Number}(a::String, b::T, c::T) = 5 -@assert istrait_v3(Pr2{String}) -@assert istrait_v3(Pr2{ASCIIString}) - -fn77{S<:Integer}(a::S, b::Int, c::Int) = 5 # this is no good, not general enough -@assert !istrait_v3(Pr2{Integer}) - -fn77{S<:Integer, N}(a::S, b::N, c::N) = 5 -@assert istrait_v3(Pr2{Integer}) - -# ############ -# # Test cases for later -# ############ -# using Base.Test - -# bug_ret_type1 = true - -# @traitdef Tr01{X} begin -# g01{T<:X}(T, T) -> T -# end -# g01(::Int, ::Int) = Int -# @test istrait(Tr01{Int}) # == true -# @test_throws 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 -# 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 bug_ret_type1 -# @test istrait(Tr02{Integer}) -# # or throw an error/warning here saying parametric return types -# # are only supported for leaftypes -# else -# @test_throws 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 -# g03{I<:Integer}(::I, ::Vector{I}) = 1 -# @test istrait(Tr03{Integer}) -# @test istrait(Tr03{Int}) diff --git a/test/manual-traitdef.jl b/test/manual-traitdef.jl index ca79be1..5e21a6c 100644 --- a/test/manual-traitdef.jl +++ b/test/manual-traitdef.jl @@ -180,7 +180,7 @@ end g01(::Int, ::Int) = Int -if traitdef_bug1 +if concrete_type_bug @test !istrait(Tr01{Int}) # == true as constraints Int isleaftype else @test istrait(Tr01{Int}) # == true as constraints Int isleaftype diff --git a/test/runtests.jl b/test/runtests.jl index d4edc44..0e51ae7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -10,7 +10,6 @@ function_types_bug1 = true # set to false if function types get implemented in J dispatch_bug1 = true # in traitdispatch.jl traitdef_bug1 = true varag_not_supported_bug = true -constructors_not_supported_bug = true concrete_type_bug = true # src/Traits.jl tests diff --git a/test/traitdef.jl b/test/traitdef.jl index 48aacb0..ca88fac 100644 --- a/test/traitdef.jl +++ b/test/traitdef.jl @@ -314,27 +314,30 @@ AssocIsBits{T3484675{Int,4.5,:a}}() #### # DataType constructors #### -if !constructors_not_supported_bug - @traitdef TT45{D} begin - # This trait contains all datatypes which have a constructor with - # no arguments. - 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}}) - + +@traitdef TT45{D} begin + # This trait contains all datatypes which have a constructor with + # no arguments. + 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}}) + + + +if !varag_not_supported_bug @traitdef TT44{D} begin # - Array(D,Any) + Array(Type{D},Any) end @test istrait(TT44{A4758}) @test istrait(TT44{A4759}) @@ -343,7 +346,7 @@ if !constructors_not_supported_bug @test istrait(TT44{Int}) @test istrait(TT44{Array{Int,1}}) -if !varag_not_supported_bug + # This is the trait for datatypes with Array like constructors: @traitdef TT46{Ar} begin T = Type{eltype(Ar)} @@ -366,6 +369,5 @@ if !varag_not_supported_bug @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 +end -end # !constructors_not_supported_bug From 580267d55aa0b24c1f1bb9779cf707da89d977a4 Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Sat, 11 Apr 2015 12:58:51 +0200 Subject: [PATCH 10/24] Super strange bug happening here: On Version 0.4.0-dev+4133 running julia -L runtests.jl results in: Could not instantiate instance for type encoding the trait Traits.Collection{Dict{Int64,V}}. This usually indicates that something is amiss with the @traitdef or that one of the generic functions is not defined. The error was: ArgumentError("invalid type for argument #s73 in method definition for __eltype at null:0") ERROR: LoadError: LoadError: test failed: istrait(Assoc{c},verbose=true) in expression: istrait(Assoc{c},verbose=true) in error at error.jl:19 in default_handler at test.jl:27 in do_test at test.jl:50 in anonymous at no file:102 in include at ./boot.jl:250 in include_from_node1 at ./loading.jl:129 in include at ./boot.jl:250 in include_from_node1 at ./loading.jl:129 in reload_path at ./loading.jl:153 in _require at ./loading.jl:68 in require at ./loading.jl:51 in process_options at ./client.jl:260 in _start at ./client.jl:401 while loading .julia/v0.4/Traits/test/traitdef.jl, in expression starting on line 101 while loading .julia/v0.4/Traits/test/runtests.jl, in expression starting on line 39 However, it goes away with a @show somewhere in isfitting. Super strange bug. --- src/Traits.jl | 88 +++++++++++++++++++-------------------------- src/commontraits.jl | 3 +- 2 files changed, 39 insertions(+), 52 deletions(-) diff --git a/src/Traits.jl b/src/Traits.jl index 1a6c7f9..3fa8f3b 100644 --- a/src/Traits.jl +++ b/src/Traits.jl @@ -200,6 +200,11 @@ function istrait(Trs::Tuple; verbose=false) end ## Helpers for istrait +type FakeMethod + sig + tvars + va +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. @@ -221,42 +226,39 @@ end """ -> function isfitting(tm::Method, fm::Method; verbose=false) # tm=trait-method, fm=function-method println_verb = verbose ? println : x->x + println_verb = verbose ? x->x : x->x iscall_overload(tm,fm) = fm.func.code.name==:call && tm.func.code.name!=:call - - # special casing for call-overloading: + + # Special casing for call-overloading. This is a bit of a hack: + # make a new method tm which includes the include the ::Type{...}. if iscall_overload(tm,fm) - @show " " - @show fm - # This is a bad hack: alter the signature of the trait-method - # to include the ::Type{...}! It needs to be undone, to undo - # it only use the @goto instead of return! GOTO, nice! (maybe - # I should use a macro...) + # Make a throw-away method: + tmold = tm + tm = FakeMethod(tm.sig, tm.tvars, tm.va) + ## Alternative would be to make a proper throw-away method: + # tmpf967858() = 1 + # tmpf967858.env.defs.sig = tm.sig + # tmpf967858.env.defs.tvars = tm.tvars + # tm = tmpf967858.env.defs # store old values to be restored at the end of the goto: - old_tmsig = tm.sig - old_tmtvars = tm.tvars tm.sig = tuple(fm.sig[1], tm.sig...) - @show fm.sig - @show tm.sig # check whether there are parameters too! - if fm.tvars!=() - fmtvars = isa(fm.tvars,TypeVar) ? (fm.tvars,) : fm.tvars # make sure it's a tuple of TypeVar - for ftv in fmtvars - @show flocs = find_tvar(fm.sig, ftv) - if flocs[1] # yep, has a constraint like call{T}(::Type{Array{T}},...) - if sum(flocs)==1 - @show tm.tvars = tuple(ftv, tm.tvars...) - else - println_verb("This check is not implemented, returning false.") - # less than 10 of the 1000 methods of call in - # Base end here. However, it does include the - # catch all: call{T}(::Type{T},args...) at - # base.jl:38 - @goto RETURN_FALSE - end + 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.tvars = tuple(ftv, tm.tvars...) + 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 + println(tm.sig, tmold.sig) end # No Vararg methods implement yet @@ -265,7 +267,7 @@ function isfitting(tm::Method, fm::Method; verbose=false) # tm=trait-method, fm= # # What do varargs mean? println_verb("Vararg methods not currently supported. Returning false.") - @goto RETURN_FALSE + return false end ## Check condition A: # If there are no type-vars then just compare the signatures: @@ -277,16 +279,12 @@ function isfitting(tm::Method, fm::Method; verbose=false) # tm=trait-method, fm= 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.") - @goto RETURN_FALSE + return false end end end println_verb("Reason fail/pass: no tvars in trait-method. Result: $(tm.sig<:fm.sig)") - if tm.sig<:fm.sig - @goto RETURN_TRUE - else - @goto RETURN_FALSE - end + return tm.sig<:fm.sig end # If !(tm.sig<:fm.sig) then tm<<:fm is false # but the converse is not true: @@ -294,13 +292,13 @@ function isfitting(tm::Method, fm::Method; verbose=false) # tm=trait-method, fm= println_verb("""Reason fail: !(tm.sig<:fm.sig) tm.sig = $(tm.sig) fm.sig = $(fm.sig)""") - @goto RETURN_FALSE + 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") - @goto RETURN_FALSE + return false end # Getting to here means that that condition (A) is fulfilled. @@ -309,7 +307,7 @@ function isfitting(tm::Method, fm::Method; verbose=false) # tm=trait-method, fm= # constraints play no role: if length(tm.sig)==1 println_verb("Reason pass: length(tm.sig)==1") - @goto RETURN_TRUE + return true end # Strategy: go through constraints on trait-method and check @@ -338,7 +336,7 @@ function isfitting(tm::Method, fm::Method; verbose=false) # tm=trait-method, fm= #@test istrait(Tr01{Int}, verbose=true) println_verb("Reason fail: parametric constraints on function method not as severe as on trait-method.") - @goto RETURN_FALSE + return false end if length(ftvs)>1 error("""Not supported if two or more TypeVar appear in the same arguments. @@ -354,25 +352,13 @@ function isfitting(tm::Method, fm::Method; verbose=false) # tm=trait-method, fm= 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!") - @goto RETURN_FALSE + return false end end end println_verb("Reason pass: all checks passed") - # only return here so it can be tidied up - @label RETURN_TRUE - if iscall_overload(tm,fm) - tm.sig = old_tmsig - tm.tvars = old_tmtvars - end return true - @label RETURN_FALSE - if iscall_overload(tm,fm) - tm.sig = old_tmsig - tm.tvars = old_tmtvars - end - return false end # helpers for isfitting diff --git a/src/commontraits.jl b/src/commontraits.jl index 133f02e..831f45d 100644 --- a/src/commontraits.jl +++ b/src/commontraits.jl @@ -55,8 +55,9 @@ end end @traitdef Assoc{X} <: Indexable{X} begin + tmp1 = println(X) K,V = eltype(X) - + tmp = println(K,V) # note, ObjectId dict is not part of this interface haskey(X, Any) get(X, Any, Any) From 49eca1f30fec91b21a2920efacd6c95c367da8f4 Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Sat, 11 Apr 2015 13:45:15 +0200 Subject: [PATCH 11/24] With the change in Taits.jl the Heisebug is gone?! --- src/Traits.jl | 6 ++---- src/commontraits.jl | 3 +-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Traits.jl b/src/Traits.jl index 3fa8f3b..05cf9e9 100644 --- a/src/Traits.jl +++ b/src/Traits.jl @@ -227,15 +227,14 @@ end function isfitting(tm::Method, fm::Method; verbose=false) # tm=trait-method, fm=function-method println_verb = verbose ? println : x->x println_verb = verbose ? x->x : x->x - iscall_overload(tm,fm) = fm.func.code.name==:call && tm.func.code.name!=:call # Special casing for call-overloading. This is a bit of a hack: # make a new method tm which includes the include the ::Type{...}. - if iscall_overload(tm,fm) + if fm.func.code.name==:call && tm.func.code.name!=:call # Make a throw-away method: tmold = tm tm = FakeMethod(tm.sig, tm.tvars, tm.va) - ## Alternative would be to make a proper throw-away method: + ## Alternative would be to make a proper throw-away method, this would make tm typestable: # tmpf967858() = 1 # tmpf967858.env.defs.sig = tm.sig # tmpf967858.env.defs.tvars = tm.tvars @@ -258,7 +257,6 @@ function isfitting(tm::Method, fm::Method; verbose=false) # tm=trait-method, fm= end end end - println(tm.sig, tmold.sig) end # No Vararg methods implement yet diff --git a/src/commontraits.jl b/src/commontraits.jl index 831f45d..26787a0 100644 --- a/src/commontraits.jl +++ b/src/commontraits.jl @@ -55,9 +55,8 @@ end end @traitdef Assoc{X} <: Indexable{X} begin - tmp1 = println(X) K,V = eltype(X) - tmp = println(K,V) + # note, ObjectId dict is not part of this interface haskey(X, Any) get(X, Any, Any) From 73d82fd4c3694291392ccb0133a563bd77b454ca Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Sat, 11 Apr 2015 13:50:07 +0200 Subject: [PATCH 12/24] Oh no, the bug is back with this slight change. It looks like it is a problem with one of my julia builds. Anotherone on the same commit 09ca2e17c4f04b96973338f4b5cf22f8bca344bc did not crash in many tries... The bug is gone after a completely fresh julia rebuild. At least on version 21df2db4dca8463f6ca6f14ec435324f135f6440 --- test/traitdef.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/traitdef.jl b/test/traitdef.jl index ca88fac..af90534 100644 --- a/test/traitdef.jl +++ b/test/traitdef.jl @@ -76,7 +76,7 @@ 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 dicts = [] #todo add again: Dict{Int,Int}] # , ObjectIdDict] From 1df032723e882292433e9a16dc3fa8838bfc8685 Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Sat, 11 Apr 2015 20:01:24 +0200 Subject: [PATCH 13/24] slight fix for latest julia0.4 --- test/perf/perf.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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, From b0af9ae8454932ee57837e6653dccd9572a7918f Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Sat, 11 Apr 2015 20:18:09 +0200 Subject: [PATCH 14/24] Oh no, the bug is back! Now with the fresh julia build. As a work-around I added a never executing block with a `@show` statement. I'll look into it in branch m3/heisenbug --- src/Traits.jl | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/Traits.jl b/src/Traits.jl index 05cf9e9..c56c9f4 100644 --- a/src/Traits.jl +++ b/src/Traits.jl @@ -200,10 +200,10 @@ function istrait(Trs::Tuple; verbose=false) end ## Helpers for istrait -type FakeMethod - sig - tvars - va +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 @@ -224,31 +224,23 @@ end {T<:Integer}(T, T, Integer) <<: {T<:Integer}(T, T, T) -> false as parametric constraints are not equal """ -> -function isfitting(tm::Method, fm::Method; verbose=false) # tm=trait-method, fm=function-method +function isfitting(tmm::Method, fm::Method; verbose=false) # tm=trait-method, fm=function-method println_verb = verbose ? println : x->x - println_verb = verbose ? x->x : x->x - - # Special casing for call-overloading. This is a bit of a hack: - # make a new method tm which includes the include the ::Type{...}. - if fm.func.code.name==:call && tm.func.code.name!=:call - # Make a throw-away method: - tmold = tm - tm = FakeMethod(tm.sig, tm.tvars, tm.va) - ## Alternative would be to make a proper throw-away method, this would make tm typestable: - # tmpf967858() = 1 - # tmpf967858.env.defs.sig = tm.sig - # tmpf967858.env.defs.tvars = tm.tvars - # tm = tmpf967858.env.defs - - # store old values to be restored at the end of the goto: - tm.sig = tuple(fm.sig[1], tm.sig...) - # check whether there are parameters too! + + # 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.tvars = tuple(ftv, tm.tvars...) + 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 @@ -257,8 +249,16 @@ function isfitting(tm::Method, fm::Method; verbose=false) # tm=trait-method, fm= 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 From 15ed883a6e73cce77ee0d0662cff0d416e2f0410 Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Sun, 12 Apr 2015 21:25:00 +0200 Subject: [PATCH 15/24] Implemented Vararg functions in traitdef Was a non-issue, just removed the error-throwing check... --- src/Traits.jl | 18 +++++----- test/runtests.jl | 2 -- test/traitdef.jl | 92 ++++++++++++++++++++++++++++++------------------ 3 files changed, 68 insertions(+), 44 deletions(-) diff --git a/src/Traits.jl b/src/Traits.jl index c56c9f4..bda5ab7 100644 --- a/src/Traits.jl +++ b/src/Traits.jl @@ -259,14 +259,6 @@ function isfitting(tmm::Method, fm::Method; verbose=false) # tm=trait-method, fm 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==() @@ -308,6 +300,16 @@ function isfitting(tmm::Method, fm::Method; verbose=false) # tm=trait-method, fm return true end + # # No Vararg methods with type constraints 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 + + # 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,) diff --git a/test/runtests.jl b/test/runtests.jl index 0e51ae7..57a6ddc 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -8,8 +8,6 @@ method_exists_bug2 = false # see https://github.com/JuliaLang/julia/issues/9043 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 diff --git a/test/traitdef.jl b/test/traitdef.jl index af90534..2159407 100644 --- a/test/traitdef.jl +++ b/test/traitdef.jl @@ -309,7 +309,33 @@ Base.getindex(::T3484675, i::Int) = i AssocIsBits{T3484675{Int,4.5,:a}}() @test istrait(AssocIsBits{T3484675{Int,4.5,:a}}) # errors because it is assumed that all # parameters are TypeVars +##### +# Varags +##### +@traitdef TT31{X} begin + foo31(X, Int...) +end +foo31(::String, x::UInt...) = 1 +@test !istrait(TT31{String}) +foo31(::String) = 2 # to avoid ambiguity warnings +foo31(::String, x::Int...) = 2 +@test istrait(TT31{String}) + +@traitdef TT32{X} begin + foo32(X...) +end +foo32(::String) = 1 +@test !istrait(TT32{String}) +foo32(a::String...) = 2 # to avoid ambiguity warnings +@test istrait(TT32{String}) +@traitdef TT33{X} begin + foo33{Y<:X}(X, Y...) +end +foo33(::String) = 1 +@test !istrait(TT33{String}) +foo33{T<:String}(::String, a::T...) = 2 +@test istrait(TT33{String}) #### # DataType constructors @@ -334,40 +360,38 @@ end -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 +@traitdef TT44{D} begin + Array(Type{D},Integer) # the standard array constructor, should be working for all Types +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 - # @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) + From ef47897b1a8299bac8140f8aa720c08a13302e65 Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Sun, 12 Apr 2015 22:35:11 +0200 Subject: [PATCH 16/24] Now fixed concrete_type_bug Unnecessarily, I made some machinery to create traits with dummy types. Dig them up in this commit if ever needed. --- src/Traits.jl | 90 +++++++++++++++++++++++++++++------------ src/commontraits.jl | 4 +- src/helpers.jl | 3 +- test/manual-traitdef.jl | 32 +++++++++++++++ test/runtests.jl | 10 ++--- test/traitdef.jl | 2 +- 6 files changed, 106 insertions(+), 35 deletions(-) diff --git a/src/Traits.jl b/src/Traits.jl index bda5ab7..87aab2a 100644 --- a/src/Traits.jl +++ b/src/Traits.jl @@ -82,8 +82,12 @@ type TraitException <: Exception msg::String end -# A helper type used in istrait below -immutable _TestType{T} end +# A helper dummy types used in istrait below +abstract _TestType{T} +immutable _TestTvar{T}<:_TestType{T} end # used for TypeVar testing +immutable _TestTraitPara{T}<:_TestType{T} end # used as dummy Trait parameters +immutable _TestAssoc{T}<:_TestType{T} end # used as dummy Associated types +Base.show{T<:_TestType}(io::IO, x::Type{T}) = print(io, string(x.parameters[1])*"_") ######### # istrait, one of the core functions @@ -138,15 +142,17 @@ function istrait{T<:Trait}(Tr::Type{T}; verbose=false) end # check call signature of methods: - 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 + + # Need a trick to check parameterized methods, see isfitting for details: + testtr = tr #make_testtrait(Tr) + # Loop over all generic functions in traitdef: + for ((gf,_gf), (tgf,_tgf)) in zip(tr.methods, testtr.methods) + # Loop over all methods defined for each function in traitdef + for (i,(tm,ttm)) in enumerate(zip(methods(_gf),methods(_tgf))) 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) + # Only loop over methods which have the right number of arguments: + for fm in methods(gf, NTuple{length(tm.sig),Any}) + if isfitting(tm, fm, ttm, verbose=verbose) checks = true break end @@ -200,6 +206,15 @@ function istrait(Trs::Tuple; verbose=false) end ## Helpers for istrait + +@doc """make_testtrait makes a trait containing test types instead of + 'real' types for the trait-parameters and the associated types.""" -> +function make_testtrait{T<:Trait}(Tr::Type{T}) + TTr = deparameterize_type(Tr) + TestTypes = [_TestTraitPara{TTr.parameters[i].name} for i=1:length(TTr.parameters) ] + testtr = TTr{TestTypes...}(_TestTraitPara) # note the _TestTraitPara argument +end + immutable FakeMethod sig::(Any...,) tvars::(Any...,) @@ -224,7 +239,7 @@ end {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 +function isfitting(tmm::Method, fm::Method, testtm::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: @@ -279,6 +294,7 @@ function isfitting(tmm::Method, fm::Method; verbose=false) # tm=trait-method, fm # If !(tm.sig<:fm.sig) then tm<<:fm is false # but the converse is not true: if !(tm.sig<:fm.sig) + @show typeof(tm.sig[1]) println_verb("""Reason fail: !(tm.sig<:fm.sig) tm.sig = $(tm.sig) fm.sig = $(fm.sig)""") @@ -314,11 +330,15 @@ function isfitting(tmm::Method, fm::Method; verbose=false) # tm=trait-method, fm # 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 + # However, if one of the tm.tvars is a leaftype, then relax + # requirement on fm.tvars to be of the same specific type. This + # is done using testtm. + testtvars = isa(testtm.tvars,TypeVar) ? (testtm.tvars,) : testtm.tvars + for (i,tv) in enumerate(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.") + throw(TraitException("The type variable should feature in at least on location.")) end # Find the tvar in fm which corresponds to tv. ftvs = Any[] @@ -329,18 +349,36 @@ function isfitting(tmm::Method, fm::Method; verbose=false) # tm=trait-method, fm push!(ftvs,ftv) end end + if length(ftvs)==0 - # TODO this should pass (bug traitdef_bug1): - # g01 => _g01{T<:X}(::T, ::T) = T() + @show tv, locs + ## This should pass, because the trait-parameter is a leaftype: + # @traitdef Tr01{X} begin + # g01{T<:X}(T, T) -> T + # end # g01(::Int, ::Int) = Int - #@test istrait(Tr01{Int}, verbose=true) - + # @assert istrait(Tr01{Int}, verbose=true) + + # find X + @show isleaftype(tv.ub) + if isleaftype(tv.ub) + @show ltype = tv.ub + # Now check if the method definition of fm + # has the same leaftypes in the same location. + @show fm.sig[locs] + @show mapreduce(x -> x==ltype, &, true, fm.sig[locs]) + if mapreduce(x -> x==ltype, &, true, fm.sig[locs]) + println_verb("Reason pass: parametric constraints only on leaftypes.") + return true + end + end 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})""") + # TODO: this should be able to pass + throw(TraitException("""Not supported yet 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. @@ -348,8 +386,8 @@ function isfitting(tmm::Method, fm::Method; verbose=false) # tm=trait-method, fm # 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}) + targ = subs_tvar(tv, tm.sig[i], _TestTvar{i}) + farg = subs_tvar(ftvs[1], fm.sig[i], _TestTvar{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 @@ -362,11 +400,11 @@ function isfitting(tmm::Method, fm::Method; verbose=false) # tm=trait-method, fm end # helpers for isfitting -function subs_tvar{T<:_TestType}(tv::TypeVar, arg::DataType, TestT::Type{T}) +function subs_tvar{T<:_TestTvar}(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} + # Array{I<:Int64,N} -> Array{_TestTvar{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: @@ -375,8 +413,8 @@ function subs_tvar{T<:_TestType}(tv::TypeVar, arg::DataType, TestT::Type{T}) 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 +subs_tvar{T<:_TestTvar}(tv::TypeVar, arg::TypeVar, TestT::Type{T}) = tv===arg ? TestT : arg # note === this it essential! +subs_tvar{T<:_TestTvar}(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: @@ -430,7 +468,7 @@ function issubtrait{T1<:Trait}(t1::Type{T1}, t2::Tuple) # the empty trait is the super-trait of all traits true else - error("") + throw(TraitException("")) end end diff --git a/src/commontraits.jl b/src/commontraits.jl index 26787a0..c89c4ed 100644 --- a/src/commontraits.jl +++ b/src/commontraits.jl @@ -16,8 +16,8 @@ end @traitdef Iter{X} begin # type-functions based on return_type: - State = Base.return_types(start, (X,))[1] # this is circular but that is ok, as trait needs to be implemented. - Item = Base.return_types(next, (X,State))[1][1] + State = Base.return_types(start, (X,))[1] + Item = Base.return_types(next, (X,State))[1][1] # use eltype instead # interface functions start(X) -> State diff --git a/src/helpers.jl b/src/helpers.jl index 7a1b08c..8c84237 100644 --- a/src/helpers.jl +++ b/src/helpers.jl @@ -6,7 +6,8 @@ export deparameterize_type It is often useful to make an associated type with this to match against methods which do not specialize on the type parameters. """ -> -deparameterize_type(A::Type) = eval(A.name.module, A.name.name)::DataType +deparameterize_type(A::DataType) = eval(A.name.module, A.name.name)::DataType +deparameterize_type(A::TypeConstructor) = error("TypeConstructor not supported by deparameterize_type.") # Internal helpers ################## diff --git a/test/manual-traitdef.jl b/test/manual-traitdef.jl index 5e21a6c..d44138c 100644 --- a/test/manual-traitdef.jl +++ b/test/manual-traitdef.jl @@ -160,6 +160,7 @@ end @test !istrait(CTrAs{Int, String}) # parametric methods +#################### # @traitdef Tr01{X} begin # g01{T<:X}(T, T) -> T @@ -238,3 +239,34 @@ end g03{I<:Integer}(::I, ::Vector{I}) = 1 @test istrait(Tr03{Integer}) @test istrait(Tr03{Int}) + + +# ## If I ever need to get to the trait parameters, something like this should work: +# # @traitdef Tr04{X} begin +# # g04{T<:X}(T, Vector{T}) +# # end +# immutable Tr04{X} <: Traits.Trait{()} +# methods::Traits.FDict +# constraints::Vector{Bool} +# assoctyps::Vector{Any} +# function Tr04() +# A1 = getassoc(X) +# new(Traits.FDict( +# g04 => _g04{T<:X}(::T, ::Vector{T}, ::A1) = T() +# ), +# Bool[], +# [A1] +# ) +# end +# function Tr04(::Type{Traits._TestTraitPara}) +# A1 = Traits._TestAssoc{:A1} +# new(Traits.FDict( +# g04 => _g04{T<:X}(::T, ::Vector{T}, ::A1) = T() +# ), +# Bool[], +# [A1] +# ) +# end +# end +# getassoc{T<:Integer}(::Type{T}) = UInt +# g04{I<:Integer}(::I, ::Vector{I}, ::Integer) = 1 diff --git a/test/runtests.jl b/test/runtests.jl index 57a6ddc..5e6ab6f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -8,7 +8,7 @@ method_exists_bug2 = false # see https://github.com/JuliaLang/julia/issues/9043 function_types_bug1 = true # set to false if function types get implemented in Julia # Traits.jl issues: dispatch_bug1 = true # in traitdispatch.jl -concrete_type_bug = true +concrete_type_bug = false # src/Traits.jl tests type A1 end @@ -16,12 +16,12 @@ type A2 end @test !istraittype(A1) # 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 +@test Traits.subs_tvar(TypeVar(:I), Array{TypeVar(:I,Int64)}, Traits._TestTvar{1})==Array{TypeVar(:I,Int64)} +@test Traits.subs_tvar(TypeVar(:I,Int64), Array{TypeVar(:I,Int64)}, Traits._TestTvar{1})==Array{Traits._TestTvar{1}} +@test Traits.subs_tvar(TypeVar(:T), Array, Traits._TestTvar{1})==Array{Traits._TestTvar{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.subs_tvar(other_T, Array, Traits._TestTvar{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] diff --git a/test/traitdef.jl b/test/traitdef.jl index 2159407..a8207af 100644 --- a/test/traitdef.jl +++ b/test/traitdef.jl @@ -206,7 +206,7 @@ fn77{Y<:Number}(a::Array,b::Y, c::Y) = a[1] @traitdef Pr3{X} begin - fn78{X}(X,X) + fn78{T<:X}(T,T) end fn78(b::Int, c::Int) = b if concrete_type_bug From 76f2e9b447b02eabeb5e6189031a3c96df88985b Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Mon, 13 Apr 2015 10:48:21 +0200 Subject: [PATCH 17/24] Removed extraneous bits from last commit --- src/Traits.jl | 58 ++++++++++----------------------------------------- 1 file changed, 11 insertions(+), 47 deletions(-) diff --git a/src/Traits.jl b/src/Traits.jl index 87aab2a..4fc5759 100644 --- a/src/Traits.jl +++ b/src/Traits.jl @@ -82,12 +82,10 @@ type TraitException <: Exception msg::String end -# A helper dummy types used in istrait below +# Helper dummy types used in istrait below abstract _TestType{T} immutable _TestTvar{T}<:_TestType{T} end # used for TypeVar testing -immutable _TestTraitPara{T}<:_TestType{T} end # used as dummy Trait parameters -immutable _TestAssoc{T}<:_TestType{T} end # used as dummy Associated types -Base.show{T<:_TestType}(io::IO, x::Type{T}) = print(io, string(x.parameters[1])*"_") +#Base.show{T<:_TestType}(io::IO, x::Type{T}) = print(io, string(x.parameters[1])*"_") ######### # istrait, one of the core functions @@ -141,18 +139,14 @@ function istrait{T<:Trait}(Tr::Type{T}; verbose=false) return false end - # check call signature of methods: - - # Need a trick to check parameterized methods, see isfitting for details: - testtr = tr #make_testtrait(Tr) - # Loop over all generic functions in traitdef: - for ((gf,_gf), (tgf,_tgf)) in zip(tr.methods, testtr.methods) + # Check call signature of all methods: + for (gf,_gf) in tr.methods # Loop over all methods defined for each function in traitdef - for (i,(tm,ttm)) in enumerate(zip(methods(_gf),methods(_tgf))) + for tm in methods(_gf) checks = false # Only loop over methods which have the right number of arguments: for fm in methods(gf, NTuple{length(tm.sig),Any}) - if isfitting(tm, fm, ttm, verbose=verbose) + if isfitting(tm, fm, verbose=verbose) checks = true break end @@ -206,15 +200,6 @@ function istrait(Trs::Tuple; verbose=false) end ## Helpers for istrait - -@doc """make_testtrait makes a trait containing test types instead of - 'real' types for the trait-parameters and the associated types.""" -> -function make_testtrait{T<:Trait}(Tr::Type{T}) - TTr = deparameterize_type(Tr) - TestTypes = [_TestTraitPara{TTr.parameters[i].name} for i=1:length(TTr.parameters) ] - testtr = TTr{TestTypes...}(_TestTraitPara) # note the _TestTraitPara argument -end - immutable FakeMethod sig::(Any...,) tvars::(Any...,) @@ -239,7 +224,7 @@ end {T<:Integer}(T, T, Integer) <<: {T<:Integer}(T, T, T) -> false as parametric constraints are not equal """ -> -function isfitting(tmm::Method, fm::Method, testtm::Method; verbose=false) # tm=trait-method, fm=function-method +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: @@ -316,25 +301,11 @@ function isfitting(tmm::Method, fm::Method, testtm::Method; verbose=false) # tm= return true end - # # No Vararg methods with type constraints 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 - - # 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 - # However, if one of the tm.tvars is a leaftype, then relax - # requirement on fm.tvars to be of the same specific type. This - # is done using testtm. - testtvars = isa(testtm.tvars,TypeVar) ? (testtm.tvars,) : testtm.tvars - for (i,tv) in enumerate(tvars) + for tv in tvars # find all occurrences in the signature locs = find_tvar(tm.sig, tv) if !any(locs) @@ -351,23 +322,16 @@ function isfitting(tmm::Method, fm::Method, testtm::Method; verbose=false) # tm= end if length(ftvs)==0 - @show tv, locs ## This should pass, because the trait-parameter is a leaftype: # @traitdef Tr01{X} begin # g01{T<:X}(T, T) -> T # end # g01(::Int, ::Int) = Int # @assert istrait(Tr01{Int}, verbose=true) - - # find X - @show isleaftype(tv.ub) if isleaftype(tv.ub) - @show ltype = tv.ub - # Now check if the method definition of fm - # has the same leaftypes in the same location. - @show fm.sig[locs] - @show mapreduce(x -> x==ltype, &, true, fm.sig[locs]) - if mapreduce(x -> x==ltype, &, true, fm.sig[locs]) + # Check if the method definition of fm has the same + # leaftypes in the same location. + if mapreduce(x -> x==tv.ub, &, true, fm.sig[locs]) println_verb("Reason pass: parametric constraints only on leaftypes.") return true end From 0957998e492d5bbbea3a26bc9c23384094bb44b8 Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Mon, 13 Apr 2015 11:07:00 +0200 Subject: [PATCH 18/24] Added test and flag for another missing feature: two parameters appearing in same two arguments. --- src/Traits.jl | 6 ++++-- test/manual-traitdef.jl | 6 +----- test/runtests.jl | 2 +- test/traitdef.jl | 40 +++++++++++++++++++++++++++++++++++----- 4 files changed, 41 insertions(+), 13 deletions(-) diff --git a/src/Traits.jl b/src/Traits.jl index 4fc5759..37885ce 100644 --- a/src/Traits.jl +++ b/src/Traits.jl @@ -341,8 +341,10 @@ function isfitting(tmm::Method, fm::Method; verbose=false) # tm=trait-method, fm end if length(ftvs)>1 # TODO: this should be able to pass - throw(TraitException("""Not supported yet if two or more TypeVar appear in the same arguments. - Example f{K,V}(::Dict{K,V}, ::Dict{V,K})""")) + println_verb("""Not supported yet if two or more TypeVar appear in the same arguments. + Example f{K,V}(::Dict{K,V}, ::Dict{V,K}) + Returning false.""") + return false end # Check that they constrain the same thing in each argument. diff --git a/test/manual-traitdef.jl b/test/manual-traitdef.jl index d44138c..90f13e9 100644 --- a/test/manual-traitdef.jl +++ b/test/manual-traitdef.jl @@ -181,11 +181,7 @@ 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{Int}) # == true as constraints Int isleaftype @test !istrait(Tr01{Integer}) g01{I<:Integer}(::I, ::I) = I @test istrait(Tr01{Integer}) # == true diff --git a/test/runtests.jl b/test/runtests.jl index 5e6ab6f..1f3e074 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -8,7 +8,7 @@ method_exists_bug2 = false # see https://github.com/JuliaLang/julia/issues/9043 function_types_bug1 = true # set to false if function types get implemented in Julia # Traits.jl issues: dispatch_bug1 = true # in traitdispatch.jl -concrete_type_bug = false +two_parameters_in_one_arg_bug = true # src/Traits.jl tests type A1 end diff --git a/test/traitdef.jl b/test/traitdef.jl index a8207af..f19e87b 100644 --- a/test/traitdef.jl +++ b/test/traitdef.jl @@ -170,6 +170,7 @@ end @test !issubtrait(Tr21{Int}, Tr20{Float64}) @test !issubtrait((Tr21{Int},), (Tr20{Float64},)) +#### # Test functions parameterized on non-trait parameters. ### @traitdef Pr0{X} begin @@ -204,20 +205,49 @@ fn77{Y<:Real}(a::Array,b::Y, c::Y) = a[1] fn77{Y<:Number}(a::Array,b::Y, c::Y) = a[1] @test istrait(Pr2{Array}) +##### +# Trait functions parameterized on trait parameters +#### @traitdef Pr3{X} begin fn78{T<:X}(T,T) end fn78(b::Int, c::Int) = b -if concrete_type_bug - @test !istrait(Pr3{Int}) # this should not fail! -else - @test istrait(Pr3{Int}) # this should not fail! -end +@test istrait(Pr3{Int}) fn78(b::Real, c::Real) = b @test !istrait(Pr3{Real}) fn78{T}(b::T, c::T) = b @test istrait(Pr3{Real}) +@test istrait(Pr3{Any}) + +@traitdef Pr04{X} begin + fnpr04{T<:X, S<:Integer}(T,T, S, S) +end +fnpr04(b::Int, c::Int, ::Int, ::Int) = b +@test !istrait(Pr04{Int}) +fnpr04{I<:Integer}(b::Int, c::Int, ::I, ::I) = b +@test istrait(Pr04{Int}) + + +@traitdef Pr05{X} begin + fnpr05{T<:X, S<:Integer}(Dict{T,T}, Dict{S,T}) +end +fnpr05{T<:FloatingPoint, S<:Integer}(::Dict{T,T}, ::Dict{S,T}) = 1 +if !two_parameters_in_one_arg_bug + @test istrait(Pr05{Float64}) +else + @test !istrait(Pr05{Float64}) +end + +@traitdef Pr06{X} begin + fnpr06{T<:X, S<:Integer}(Dict{T,S}, Dict{S,T}) +end +fnpr06{T<:FloatingPoint, S<:Integer}(::Dict{T,S}, ::Dict{S,T}) = 1 +if !two_parameters_in_one_arg_bug + @test istrait(Pr06{Float64}) +else + @test !istrait(Pr06{Float64}) +end # Test constraints ### From d083ba7076418276a82cb127c1516869749a2440 Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Mon, 13 Apr 2015 11:25:12 +0200 Subject: [PATCH 19/24] Fixed two_parameters_in_one_arg_bug missing feature --- src/Traits.jl | 31 ++++++++++++++----------------- test/runtests.jl | 1 - test/traitdef.jl | 12 ++---------- 3 files changed, 16 insertions(+), 28 deletions(-) diff --git a/src/Traits.jl b/src/Traits.jl index 37885ce..ec29ca6 100644 --- a/src/Traits.jl +++ b/src/Traits.jl @@ -339,26 +339,23 @@ function isfitting(tmm::Method, fm::Method; verbose=false) # tm=trait-method, fm println_verb("Reason fail: parametric constraints on function method not as severe as on trait-method.") return false end - if length(ftvs)>1 - # TODO: this should be able to pass - println_verb("""Not supported yet if two or more TypeVar appear in the same arguments. - Example f{K,V}(::Dict{K,V}, ::Dict{V,K}) - Returning false.""") - return false - 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], _TestTvar{i}) - farg = subs_tvar(ftvs[1], fm.sig[i], _TestTvar{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 + checks = false + for ft in ftvs + for i in find(locs) + targ = subs_tvar(tv, tm.sig[i], _TestTvar{i}) + farg = subs_tvar(ft, fm.sig[i], _TestTvar{i}) + checks = checks || (targ<:farg) end end + if !checks + println_verb("Reason fail: parametric constraints on args $(tm.sig[i]) and $(fm.sig[i]) on different TypeVar locations!") + return false + end end println_verb("Reason pass: all checks passed") @@ -395,12 +392,12 @@ function find_tvar(sig::Tuple, tv) 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) +function find_tvar(arg::DataType, tv) + isleaftype(arg) && return [false] + ns = length(arg.parameters) out = false for i=1:ns - out = out || any(find_tvar(sig.parameters[i], tv)) + out = out || any(find_tvar(arg.parameters[i], tv)) end return [out] end diff --git a/test/runtests.jl b/test/runtests.jl index 1f3e074..b6bbedd 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -8,7 +8,6 @@ method_exists_bug2 = false # see https://github.com/JuliaLang/julia/issues/9043 function_types_bug1 = true # set to false if function types get implemented in Julia # Traits.jl issues: dispatch_bug1 = true # in traitdispatch.jl -two_parameters_in_one_arg_bug = true # src/Traits.jl tests type A1 end diff --git a/test/traitdef.jl b/test/traitdef.jl index f19e87b..dfb3bde 100644 --- a/test/traitdef.jl +++ b/test/traitdef.jl @@ -233,21 +233,13 @@ fnpr04{I<:Integer}(b::Int, c::Int, ::I, ::I) = b fnpr05{T<:X, S<:Integer}(Dict{T,T}, Dict{S,T}) end fnpr05{T<:FloatingPoint, S<:Integer}(::Dict{T,T}, ::Dict{S,T}) = 1 -if !two_parameters_in_one_arg_bug - @test istrait(Pr05{Float64}) -else - @test !istrait(Pr05{Float64}) -end +@test istrait(Pr05{Float64}) @traitdef Pr06{X} begin fnpr06{T<:X, S<:Integer}(Dict{T,S}, Dict{S,T}) end fnpr06{T<:FloatingPoint, S<:Integer}(::Dict{T,S}, ::Dict{S,T}) = 1 -if !two_parameters_in_one_arg_bug - @test istrait(Pr06{Float64}) -else - @test !istrait(Pr06{Float64}) -end +@test istrait(Pr06{Float64}) # Test constraints ### From 08ea6122abf824ea224062dcfbaf432e2ea45a27 Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Mon, 13 Apr 2015 12:15:45 +0200 Subject: [PATCH 20/24] Back to the strange bug: Checking Traits.Collection{Dict{Int64,V}}: Could not instantiate instance for type encoding the trait Traits.Collection{Dict{Int64,V}}. This usually indicates that something is amiss with the @traitdef or that one of the generic functions is not defined. The error was: ArgumentError("invalid type for argument #s75 in method definition for __eltype at null:0") --- src/Traits.jl | 25 +++++++++++++++++++++---- test/traitdef.jl | 8 ++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/Traits.jl b/src/Traits.jl index ec29ca6..fcb4d50 100644 --- a/src/Traits.jl +++ b/src/Traits.jl @@ -113,7 +113,11 @@ istraittype(x::Tuple) = mapreduce(istraittype, &, x) `istrait( (Tr1{Int, Float64}, Tr2{Int}) )` """ -> function istrait{T<:Trait}(Tr::Type{T}; verbose=false) - println_verb = verbose ? println : x->x + if verbose + println_verb(x) = println("Checking $Tr: " * x) + else + println_verb = x->x + end if !hasparameters(Tr) throw(TraitException("Trait $Tr has no type parameters.")) @@ -141,6 +145,7 @@ function istrait{T<:Trait}(Tr::Type{T}; verbose=false) # Check call signature of all methods: for (gf,_gf) in tr.methods + println_verb("Checking function $gf") # Loop over all methods defined for each function in traitdef for tm in methods(_gf) checks = false @@ -162,7 +167,7 @@ function istrait{T<:Trait}(Tr::Type{T}; verbose=false) # 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 + 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 @@ -267,10 +272,22 @@ function isfitting(tmm::Method, fm::Method; verbose=false) # tm=trait-method, fm # 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.") + @show typs = tm.sig[find_tvar(fm.sig, ftv)] + if length(typs)==0 + println_verb("Reason fail: this method is not callable because: static parameter does not occur in signature.") return false end + + if length(typs)>1 && !( all(map(isleaftype, typs)) && all(map(x -> x==typs[1], typs)) ) +# if !( all(map(isleaftype, typs)) && all(map(x -> x==typs[1], typs)) ) + println_verb("Reason fail: tvars-constraints in function-method are on non-leaftypes in traitmethod.") + return false + end + + # 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)") diff --git a/test/traitdef.jl b/test/traitdef.jl index dfb3bde..b4b774c 100644 --- a/test/traitdef.jl +++ b/test/traitdef.jl @@ -241,6 +241,14 @@ end fnpr06{T<:FloatingPoint, S<:Integer}(::Dict{T,S}, ::Dict{S,T}) = 1 @test istrait(Pr06{Float64}) + +@traitdef Pr07{X} begin + fnpr07(X, X, Integer) +end +fnpr07{T<:Integer}(::T, ::T, ::Integer) = 1 +@test !istrait(Pr07{Integer}) +@test istrait(Pr07{Int}) + # Test constraints ### @traitdef Cr20{X} begin From 4594bbfbe68db632f50d226f42c4ac04518b59d1 Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Mon, 13 Apr 2015 21:32:18 +0200 Subject: [PATCH 21/24] Fixed bug triggered by ex_graphs.jl The Heisenbug still is showing up, but only with running the tests in the REPL with include("runtests.jl") --- src/Traits.jl | 18 +++++------------- test/traitdef.jl | 2 +- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/Traits.jl b/src/Traits.jl index fcb4d50..cf8c8b4 100644 --- a/src/Traits.jl +++ b/src/Traits.jl @@ -127,7 +127,7 @@ function istrait{T<:Trait}(Tr::Type{T}; verbose=false) # check instantiating tr = nothing - try + try tr = Tr() catch err println_verb("""Could not instantiate instance for type encoding the trait $Tr. @@ -248,8 +248,8 @@ function isfitting(tmm::Method, fm::Method; verbose=false) # tm=trait-method, fm 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. + # Note that none of the 1000 methods of call in + # Base end up here. return false end end @@ -272,22 +272,15 @@ function isfitting(tmm::Method, fm::Method; verbose=false) # tm=trait-method, fm # one argument, then return false. fmtvars = isa(fm.tvars,TypeVar) ? (fm.tvars,) : fm.tvars for ftv in fmtvars - @show typs = tm.sig[find_tvar(fm.sig, ftv)] + typs = tm.sig[find_tvar(fm.sig, ftv)] if length(typs)==0 println_verb("Reason fail: this method is not callable because: static parameter does not occur in signature.") return false end - - if length(typs)>1 && !( all(map(isleaftype, typs)) && all(map(x -> x==typs[1], typs)) ) -# if !( all(map(isleaftype, typs)) && all(map(x -> x==typs[1], typs)) ) + if length(typs)>1 && !all(map(isleaftype, typs)) println_verb("Reason fail: tvars-constraints in function-method are on non-leaftypes in traitmethod.") return false end - - # 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)") @@ -410,7 +403,6 @@ function find_tvar(sig::Tuple, tv) end find_tvar(sig::TypeVar, tv) = sig===tv ? [true] : [false] # note ===, this it essential! function find_tvar(arg::DataType, tv) - isleaftype(arg) && return [false] ns = length(arg.parameters) out = false for i=1:ns diff --git a/test/traitdef.jl b/test/traitdef.jl index b4b774c..a740964 100644 --- a/test/traitdef.jl +++ b/test/traitdef.jl @@ -70,7 +70,7 @@ a,b,c = Traits.parsebody(td3.args[end]) start(X) end -## Testing trait definitions +## Testing trait definitions in commontraits.jl @test istrait(Cmp{Int,Int}) @test istrait(Cmp{Int,Float64}) @test !istrait(Cmp{Int,String}) From 546ece80e5b73e6dc0ecd3d5e9a0e4085757a7ed Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Mon, 13 Apr 2015 21:57:04 +0200 Subject: [PATCH 22/24] Removed all the debug `@show` Also reenabled tests due to issue 9135 as it does not apply anymore. --- src/Traits.jl | 10 ++++++---- src/commontraits.jl | 5 ++--- src/traitdef.jl | 2 -- src/traitimpl.jl | 5 +++-- test/manual-traitdef.jl | 4 ++-- test/runtests.jl | 3 +++ test/traitdef.jl | 34 ++++++++++++++++------------------ 7 files changed, 32 insertions(+), 31 deletions(-) diff --git a/src/Traits.jl b/src/Traits.jl index cf8c8b4..f64df4a 100644 --- a/src/Traits.jl +++ b/src/Traits.jl @@ -170,12 +170,15 @@ function istrait{T<:Trait}(Tr::Type{T}; verbose=false) 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 + tret_typ = Base.return_types(_gf, tm.sig) # trait-defined return type + if length(tret_typ)==0 + continue # this means the signature contains None which is not compatible with return types + # TODO: introduce a specical type signalling that no return type was given. + elseif 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 tret_typ = tret_typ[1] - @show fret_typ = Base.return_types(gf, tm.sig) + 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 @@ -289,7 +292,6 @@ function isfitting(tmm::Method, fm::Method; verbose=false) # tm=trait-method, fm # If !(tm.sig<:fm.sig) then tm<<:fm is false # but the converse is not true: if !(tm.sig<:fm.sig) - @show typeof(tm.sig[1]) println_verb("""Reason fail: !(tm.sig<:fm.sig) tm.sig = $(tm.sig) fm.sig = $(fm.sig)""") diff --git a/src/commontraits.jl b/src/commontraits.jl index c89c4ed..ceeac31 100644 --- a/src/commontraits.jl +++ b/src/commontraits.jl @@ -44,9 +44,8 @@ end @traitdef Indexable{X} <:Collection{X} begin El = eltype(X) - # TODO issue https://github.com/JuliaLang/julia/issues/9135: - #getindex(X, All) - #setindex!(X, El, All) + getindex(X, None) # when using None no return types can be used... + setindex!(X, El, None) length(X) -> Integer # automatically provided: diff --git a/src/traitdef.jl b/src/traitdef.jl index 5d1a478..9735b02 100644 --- a/src/traitdef.jl +++ b/src/traitdef.jl @@ -155,10 +155,8 @@ function parsefnstypes!(outfns, ln) end # 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) diff --git a/src/traitimpl.jl b/src/traitimpl.jl index 241aa87..4ed3c7e 100644 --- a/src/traitimpl.jl +++ b/src/traitimpl.jl @@ -122,7 +122,8 @@ macro traitimpl(head, body) trait = eval_curmod(trait_expr) ## Check supertraits are implemented: - if !(istrait(traitgetsuper(trait); verbose=true)) + if !istrait(traitgetsuper(trait)) + istrait(traitgetsuper(trait); verbose=true) throw(TraitException("""Not all supertraits of $trait are implemented. Implement them first.""")) end @@ -138,6 +139,6 @@ macro traitimpl(head, body) end ## Assert that the implementation went smoothly - push!(out.args, :(@assert istrait($trait_expr, verbose=true))) + push!(out.args, :(istrait($trait_expr) ? nothing : @assert istrait($trait_expr, verbose=true))) return esc(out) end diff --git a/test/manual-traitdef.jl b/test/manual-traitdef.jl index 90f13e9..7704f10 100644 --- a/test/manual-traitdef.jl +++ b/test/manual-traitdef.jl @@ -34,7 +34,7 @@ end @test traitgetsuper(Tr3{A1,A2})==(Tr1{A1},Tr2{A1,A2}) # any type is part of a unconstrained trait: -@test istrait(Tr1{Int}, verbose=true) +@test istrait(Tr1{Int}, verbose=verbose) @test istrait(Tr2{DataType,Int}) @test istrait(Tr3{String,DataType}) @test_throws TraitException istrait(Tr3{:a,7}) # maybe this should error? @@ -55,7 +55,7 @@ immutable D1{X1} <: Traits.Trait{()} end end -@test istrait(D1{Int}, verbose=true) +@test istrait(D1{Int}, verbose=verbose) @test !istrait(D1{String}) immutable D2{X1,X2} <: Traits.Trait{(D1{X1}, D1{X2})} diff --git a/test/runtests.jl b/test/runtests.jl index b6bbedd..887e1cd 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -9,6 +9,9 @@ function_types_bug1 = true # set to false if function types get implemented in J # Traits.jl issues: dispatch_bug1 = true # in traitdispatch.jl +# how much output to print +verbose=false + # src/Traits.jl tests type A1 end type A2 end diff --git a/test/traitdef.jl b/test/traitdef.jl index a740964..fe1e74d 100644 --- a/test/traitdef.jl +++ b/test/traitdef.jl @@ -86,35 +86,33 @@ 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) - @test istrait(IterColl{c}, verbose=true) + @test istrait(Collection{c}, verbose=verbose) + @test istrait(Iter{c}, verbose=verbose) + @test istrait(IterColl{c}, verbose=verbose) end -println("""After fix of https://github.com/JuliaLang/julia/issues/9135 - uncomment following line again and in commontraits.jl""") -# @test !istrait(Indexable{Set}) +@test !istrait(Indexable{Set}) for c in iter - @test istrait(Iter{c}, verbose=true) + @test istrait(Iter{c}, verbose=verbose) end for c in dicts - @test istrait(Assoc{c}, verbose=true) + @test istrait(Assoc{c}, verbose=verbose) end for c in index - @test istrait(Indexable{c}, verbose=true) + @test istrait(Indexable{c}, verbose=verbose) end -@test istrait(Iter{Array}, verbose=true) -@test istrait(Iter{ASCIIString}, verbose=true) -@test istrait(Iter{Int}, verbose=true) +@test istrait(Iter{Array}, verbose=verbose) +@test istrait(Iter{ASCIIString}, verbose=verbose) +@test istrait(Iter{Int}, verbose=verbose) @test !istrait(Iter{Nothing}) arith = [Int, Float64, Rational{Int}] for a1 in arith for a2 in arith - @test istrait(Arith{a1,a2}, verbose=true) + @test istrait(Arith{a1,a2}, verbose=verbose) end end @@ -416,12 +414,12 @@ end 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 + @test istrait(TT46{Dict{Int,Int}}, verbose=verbose) # 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{Set{Int}}, verbose=verbose) 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) +@test istrait(TT46{Array{Int,1}}, verbose=verbose) +# @test istrait(TT46{Array{Int}}, verbose=verbose) # this does not pass currently because of https://github.com/JuliaLang/julia/issues/10642 +@test istrait(TT46{Array}, verbose=verbose) From 4673139473d9a68c6da0af8e305f7915038be89b Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Tue, 14 Apr 2015 16:10:37 +0200 Subject: [PATCH 23/24] More improvements for parameter constraint methods Definitly getting the the area of dimishing returns... --- src/Traits.jl | 198 +++++++++++++++++++++++++---------- src/helpers.jl | 262 ++--------------------------------------------- src/traitdef.jl | 1 - test/runtests.jl | 19 +++- test/traitdef.jl | 44 +++++++- 5 files changed, 205 insertions(+), 319 deletions(-) diff --git a/src/Traits.jl b/src/Traits.jl index f64df4a..b447e0f 100644 --- a/src/Traits.jl +++ b/src/Traits.jl @@ -8,7 +8,7 @@ module Traits - they are structural types: i.e. they needn't be declared explicitly """ -> current_module() -export istrait, istraittype, issubtrait, +export istrait, istraittype, issubtrait, check_return_types, traitgetsuper, traitgetpara, traitmethods, @traitdef, @traitimpl, @traitfn, TraitException, All @@ -29,12 +29,18 @@ include("helpers.jl") # 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 + const flag_check_return_types = Main.Traits_check_return_types else - flag_check_return_types = true + const flag_check_return_types = true end @doc "Flag to select whether return types in @traitdef's are checked" flag_check_return_types +@doc "Toggles return type checking. Will issue warning because of const declaration, ignore:"-> +function check_return_types(flg::Bool) + global flag_check_return_types + flag_check_return_types = flg +end + ####### # Types ####### @@ -114,7 +120,7 @@ istraittype(x::Tuple) = mapreduce(istraittype, &, x) """ -> function istrait{T<:Trait}(Tr::Type{T}; verbose=false) if verbose - println_verb(x) = println("Checking $Tr: " * x) + println_verb(x) = println("**** Checking $(deparameterize_type(Tr)): " * x) else println_verb = x->x end @@ -145,9 +151,10 @@ function istrait{T<:Trait}(Tr::Type{T}; verbose=false) # Check call signature of all methods: for (gf,_gf) in tr.methods - println_verb("Checking function $gf") + println_verb("*** Checking function $gf") # Loop over all methods defined for each function in traitdef for tm in methods(_gf) + println_verb("** Checking method $tm") checks = false # Only loop over methods which have the right number of arguments: for fm in methods(gf, NTuple{length(tm.sig),Any}) @@ -157,8 +164,8 @@ function istrait{T<:Trait}(Tr::Type{T}; verbose=false) end end 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""") + println_verb("""No method of the generic function/call-overloaded `$gf` matched the + trait specification: `$tm`""") return false end end @@ -166,16 +173,20 @@ function istrait{T<:Trait}(Tr::Type{T}; verbose=false) # check return-type. Specifed return type tret and return-type of # the methods frets should fret<:tret. This is backwards to - # argument types... + # argument types checking above. if flag_check_return_types for (gf,_gf) in tr.methods + println_verb("*** Checking return types of function $gf") for tm in methods(_gf) # loop over all methods defined for each function in traitdef + println_verb("** Checking return types of method $tm") tret_typ = Base.return_types(_gf, tm.sig) # trait-defined return type if length(tret_typ)==0 continue # this means the signature contains None which is not compatible with return types - # TODO: introduce a specical type signalling that no return type was given. + # TODO: introduce a special type signaling that no return type was given. elseif length(tret_typ)>1 - throw(TraitException("Querying the return type of the trait-method $tm did not return exactly one return type: $tret_typ")) + if !allequal(tret_typ) # Ok if all return types are the same. + throw(TraitException("Querying the return type of the trait-method $tm did not return exactly one return type: $tret_typ")) + end end tret_typ = tret_typ[1] fret_typ = Base.return_types(gf, tm.sig) @@ -191,6 +202,7 @@ function istrait{T<:Trait}(Tr::Type{T}; verbose=false) $tret_typ List of found return types: $fret_typ + Returning false. """) return false end @@ -213,17 +225,25 @@ immutable FakeMethod 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. +@doc """isfitting checks whether the signature of a method `tm` + specified in the trait definition is fulfilled by one method `fm` + of the corresponding generic function. This is the core function + which is 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. + fm and tm are equal where applicable. 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) - 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. + B) The parametric constraints parameters on `sig` and `tsig` need + to feature in the same argument positions. Except when the + corresponding function parameter is constraint by a concrete + type: then make sure that all the occurrences are the same + concrete type. 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) @@ -232,19 +252,25 @@ end {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 +function isfitting(tmm::Method, fmm::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) + # Make a "copy" of tmm & fmm as it may get updated: + tm = FakeMethod(tmm.sig, isa(tmm.tvars,Tuple) ? tmm.tvars : (tmm.tvars,), tmm.va) + fm = FakeMethod(fmm.sig, isa(fmm.tvars,Tuple) ? fmm.tvars : (fmm.tvars,), fmm.va) + # Note the `? : ` is needed because of https://github.com/JuliaLang/julia/issues/10811 + + # Replace type parameters which are constraint by a concrete type + # (because Vector{TypeVar(:V, Int)}<:Vector{Int}==false but we need ==true) + tm = replace_concrete_tvars(tm) + fm = replace_concrete_tvars(fm) # Special casing for call-overloading. - if fm.func.code.name==:call && tmm.func.code.name!=:call # true if only fm is call-overloaded + if fmm.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 + for ftv in fm.tvars flocs = find_tvar(fm.sig, ftv) if flocs[1] # yep, has a constraint like call{T}(::Type{Array{T}},...) if sum(flocs)==1 @@ -265,28 +291,12 @@ function isfitting(tmm::Method, fm::Method; verbose=false) # tm=trait-method, fm error("This is not possible") end 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 - typs = tm.sig[find_tvar(fm.sig, ftv)] - if length(typs)==0 - println_verb("Reason fail: this method is not callable because: static parameter does not occur in signature.") - return false - end - if length(typs)>1 && !all(map(isleaftype, typs)) - println_verb("Reason fail: tvars-constraints in function-method are on non-leaftypes in traitmethod.") - return false - end - end - end - println_verb("Reason fail/pass: no tvars in trait-method. Result: $(tm.sig<:fm.sig)") + # If there are no function parameters then just compare the + # signatures. + if tm.tvars==() && fm.tvars==() + println_verb("Reason fail/pass: no tvars in trait-method only checking signature. Result: $(tm.sig<:fm.sig)") return tm.sig<:fm.sig end # If !(tm.sig<:fm.sig) then tm<<:fm is false @@ -300,7 +310,7 @@ function isfitting(tmm::Method, fm::Method; verbose=false) # tm=trait-method, fm # 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") + println_verb("Reason fail: not same argument length.") return false end # Getting to here means that that condition (A) is fulfilled. @@ -313,20 +323,47 @@ function isfitting(tmm::Method, fm::Method; verbose=false) # tm=trait-method, fm return true end + # First special case if tm.tvars==() && !(fm.tvars==()) + if tm.tvars==() + fm.tvars==() && error("Execution shouldn't get here as this should have been checked above!") + for (i,ftv) in enumerate(fm.tvars) + # If all the types in tm.sig, which correspond to a + # parameter constraint argument of fm.sig, are the same then pass. + typs = tm.sig[find_tvar(fm.sig, ftv)] + if length(typs)==0 + println_verb("Reason fail: this method $fmm is not callable because the static parameter does not occur in signature.") + return false + elseif length(typs)==1 # Necessarily the same + continue + else # length(typs)>1 + if !all(map(isleaftype, typs)) # note isleaftype can have some issues with inner constructors + println_verb("Reason fail: not all parametric-constraints in function-method $fmm are on leaftypes in traitmethod $tmm.") + return false + else + # Now check that all of the tm.sig-types have the same type at the parametric-constraint sites. + if !allequal(find_correponding_type(tm.sig, fm.sig, ftv)) + println_verb("Reason fail: not all parametric-constraints in function-method $fmm correspond to the same type in traitmethod $tmm.") + return false + end + end + end + end + println_verb("""Reason pass: All occurrences of the parametric-constraint in $fmm correspond to the + same type in trait-method $tmm.""") + 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 + for tv in tm.tvars # find all occurrences in the signature locs = find_tvar(tm.sig, tv) if !any(locs) - throw(TraitException("The type variable should feature in at least on location.")) + throw(TraitException("The parametric-constraint of trait-method $tmm has to feature in at least one argument of the signature.")) 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 + for ftv in fm.tvars flocs = find_tvar(fm.sig, ftv) if all(flocs[find(locs)]) push!(ftvs,ftv) @@ -340,7 +377,7 @@ function isfitting(tmm::Method, fm::Method; verbose=false) # tm=trait-method, fm # end # g01(::Int, ::Int) = Int # @assert istrait(Tr01{Int}, verbose=true) - if isleaftype(tv.ub) + if isleaftype(tv.ub) # note isleaftype can have some issues with inner constructors # Check if the method definition of fm has the same # leaftypes in the same location. if mapreduce(x -> x==tv.ub, &, true, fm.sig[locs]) @@ -375,7 +412,7 @@ function isfitting(tmm::Method, fm::Method; verbose=false) # tm=trait-method, fm end # helpers for isfitting -function subs_tvar{T<:_TestTvar}(tv::TypeVar, arg::DataType, TestT::Type{T}) +function subs_tvar(tv::TypeVar, arg::DataType, TestT::DataType) # Substitute `TestT` for a particular TypeVar `tv` in an argument `arg`. # # Example: @@ -388,8 +425,59 @@ function subs_tvar{T<:_TestTvar}(tv::TypeVar, arg::DataType, TestT::Type{T}) return typ{pa...} end end -subs_tvar{T<:_TestTvar}(tv::TypeVar, arg::TypeVar, TestT::Type{T}) = tv===arg ? TestT : arg # note === this it essential! -subs_tvar{T<:_TestTvar}(tv::TypeVar, arg, TestT::Type{T}) = arg # for anything else +subs_tvar(tv::TypeVar, arg::TypeVar, TestT::DataType) = tv===arg ? TestT : arg # note === this it essential! +subs_tvar(tv::TypeVar, arg, TestT::DataType) = arg # for anything else + +function replace_concrete_tvars(m::FakeMethod) + # Example: + # FakeMethod((T<:Int64,Array{T<:Int64,1},Integer),(T<:Int64,),false) + # -> + # FakeMethod((Int64, Array{Int64,1}, Integer),() ,false) + newtv = [] + newsig = Any[m.sig...] # without the Any I get seg-faults and + # other strange erros! + for tv in m.tvars + if !isleaftype(tv.ub) + push!(newtv, tv) + else + newsig = Any[subs_tvar(tv, arg, tv.ub) for arg in newsig] + end + end + FakeMethod(tuple(newsig...), tuple(newtv...), m.va) +end + +# Finds the types in tmsig which correspond to TypeVar ftv in fmsig +function find_correponding_type(tmsig::Tuple, fmsig::Tuple, ftv::TypeVar) + out = Any[] + for (ta,fa) in zip(tmsig,fmsig) + if isa(fa, TypeVar) + fa===ftv && push!(out, ta) + elseif isa(fa, DataType) || isa(fa, Tuple) + append!(out, find_correponding_type(ta,fa,ftv)) + else + @show ta, fa + error("Not implemented") + end + end + return out +end +function find_correponding_type(ta::DataType, fa::DataType, ftv::TypeVar) + # gets here if fa is not a TypeVar + out = Any[] + if !( deparameterize_type(ta)<:deparameterize_type(fa)) # || + # length(ta.parameters)!=length(fa.parameters) # don't check for length. If not the same length, assume that the first parameters are corresponding... + push!(out, _TestType{:no_match}) # this will lead to a no-match in isfitting + return out + end + for (tp,fp) in zip(ta.parameters,fa.parameters) + if isa(fp, TypeVar) + fp===ftv && push!(out, tp) + elseif isa(fp, DataType) || isa(fa, Tuple) + append!(out, find_correponding_type(tp,fp,ftv)) + end + end + return out +end # find_tvar finds index of arguments in a function signature `sig` where a # particular TypeVar `tv` features. Example: diff --git a/src/helpers.jl b/src/helpers.jl index 8c84237..9d0dac6 100644 --- a/src/helpers.jl +++ b/src/helpers.jl @@ -38,6 +38,8 @@ function Base.done(lns::Lines, nr) end end +@doc "Checks all elements of a collection are equal" -> +allequal(x) = all([x[1]==xx for xx in x]) ## Parsing #### @@ -83,18 +85,6 @@ function tvar2tvar!(exs::Vector{Any}) nothing end -# function return_types_v2(f::Function, typs::ANY) -# # for some reason this function take forever to JIT. (about 4 secs!) -# # see https://github.com/JuliaLang/julia/issues/9131 -# a = code_typed(f, typs) -# if length(a)>1 -# error("several return types") -# elseif length(a)==0 -# error("no return types") -# end -# a[1].args[end].typ -# end - # check whether a type is parameterized isparameterized(t::DataType) = length(t.parameters)==0 ? false : true @@ -110,244 +100,10 @@ function hasparameters(t::DataType) end end -# # check whether a function is parameterized -# function isparameterized(m::Method) -# if isa(m.tvars, Tuple) -# return length(m.tvars)==0 ? false : true -# else -# return true -# end -# end - -# Base.subtypes(x::(ANY...)) = Base.subtypes(Main, x::(ANY...)) -# function Base.subtypes(m::Module, ts::(ANY...); maxout=100_000) -# # Extends subtypes to work with tuple-types. -# # Returns at most maxout (default=100_000) types. -# slots = Array(Tuple, length(ts)) -# allempty = true -# for (i,t) in enumerate(ts) -# st = subtypes(t) -# if length(st)==0 -# slots[i] = (t,) # if a slot is empty use t -# else -# slots[i] = tuple(st...) -# allempty = false -# end -# end -# allempty && return Any[] -# # Make all possible combinations (could be many!) -# out = combos(slots, maxout) -# end - -# function combos{T<:Tuple}(typs::Vector{T}, maxout; verbose=true) -# # makes all possible combinations. See test/helpers.jl -# len = length(typs) -# lens = map(length, typs) -# n = min(prod(lens), maxout) -# if n==maxout && verbose -# println("subtypes: not returning all $(prod(lens)) type tuples, only $maxout") -# end -# inds = ones(Int,len) -# out = Array(Tuple, n) -# for j=1:n -# try -# out[j] = tuple([typs[ii][i] for (ii,i) in enumerate(inds)]...) -# catch e -# @show j, inds -# end -# # updated inds -# inds[1] += 1 -# for i=1:len-1 -# if inds[i]>lens[i] -# inds[i] = 1 -# inds[i+1] += 1 -# end -# end -# end -# return out -# end - -# # Does the same as method_exists except that it also works for -# # abstract/parameterized types by considering whether method_exists -# # for all concrete subtypes. -# # -# # I.e. -# # method_exists_forall_subtypes(f, ts)==true => for all sts<:ts, f(::ts) works -# baremodule Res -# const F = 0 # false -# const T = 1 # true -# const M = 2 # undecided, maybe, i.e. look further -# end - -# # function get_intypes(f, Ts) -# # # based on Jiahao's: http://nbviewer.ipython.org/gist/jiahao/b0d4279cec83b681d95f -# # ct = code_typed(f, Ts) -# # intypes = Array(Any, length(ct)) -# # for (i,method) in enumerate(code_typed(f, Ts)) -# # #Get types of inputs -# # tmp = [x[2] for x in method.args[2][2][1:length(Ts)]] -# # for (j,tm) in enumerate(tmp) -# # if isa(tm,TypeVar) -# # tmp[j] = tm.ub -# # end -# # end -# # intypes[i] = tmp -# # end -# # return intypes -# # end - -# dbg_println(st::String) = verb && println(st) -# const verb = true -# function is_fnparameter_match_inputs(meth_tvars) -# for i=1:2:length(meth_tvars) -# # @show meth_tvars[i+1], meth_tvars[i] -# !( meth_tvars[i+1]<:meth_tvars[i] ) && return false -# end -# return true -# end - -# # function is_type_parameter_match_inputs(TS, meth_sig) -# # for i=1:2:length(meth_tvars) -# # # @show meth_tvars[i+1], meth_tvars[i] -# # !( meth_tvars[i+1]<:meth_tvars[i] ) && return false -# # end -# # return true -# # end - -# function method_exists_forthis_type(f::Function, TS::(ANY...), meths) -# # if the method is defined for TS we're done: -# #@show meths - -# if method_exists(f, TS) # note this does no match parameterized -# # functions when its parameters are not -# # specified. -# dbg_println("A method matches exactly.") -# return Res.T -# end - -# # if there is no method, we're done: -# if length(meths)==0 -# dbg_println("False: No methods for: $TS") -# return Res.F -# end - -# # if there are one or more methods, check them -# for mm in meths -# if is_fnparameter_match_inputs(mm[2]) -# ----------> check this -# @show TS, mm[1], TS<:mm[1], mm[1]<:TS -# if TS<:mm[1] -# dbg_println("A parameterized method matches exactly: $TS") -# return Res.T -# else -# dbg_println("A parameterized method does not match exactly: $TS, looking further") -# end -# else -# dbg_println("A parameterized cannot match: $TS") -# return Res.F -# end - -# end - -# # if length(meths)==1 && isparameterized(meths[1][3]) -# # if is_parameter_match_inputs(meths[1][2]) -# # if Base.typeseq(meths[1][1], TS) -# # dbg_println("A parameterized method matches exactly: $TS") -# # return Res.T -# # else -# # dbg_println("A parameterized method does not match exactly: $TS, looking further") -# # end -# # else -# # dbg_println("A parameterized cannot match: $TS") -# # return Res.F -# # end -# # end - -# # If it is a leaf-type and we're not done by now, there is no hope: -# if all(map(isleaftype,TS)) # todo think about parameterized types -# dbg_println("Leaftype testing false: $TS") -# return Res.F -# else # we don't know -# return Res.M -# end -# end - -# function method_exists_forall_subtypes(f::Function, TS::(ANY...); depth=4, checktop=true) -# # Checks whether a method of f exists for all subtypes of TS, up -# # to a specified search depth (default=4, set to -1 for inf depth). -# # -# # Return: -# # - 1 if true -# # - 0 if false -# # - 2 if undecided -# dbg_println("recusing at depth $depth") - -# if depth==0 -# return Res.M -# end - -# # reflection.jl/_methods. See video "Introduction to Julia" by Jeff -# # Internals-osdeT-tWjzk.mp4, minute 29 -# # inputs: function, type, limit of number of matches -# # outputs: -# # matchtype-signature, values of method parameters, method object -# if checktop -# ms = Base._methods(f, TS, -1) -# if ms!=false -# r = method_exists_forthis_type(f, TS, ms) -# if r==Res.T || r==Res.F -# return r -# end # otherwise check further -# else # no method found -# dbg_println("No methods found, recursing.") -# end -# end - -# # Do a breadth first search in the subtypes: -# checksTS = Any[] -# for sTS in subtypes(TS) -# ms = Base._methods(f, sTS, 2) -# if ms==false -# if isleaftype(sTS) -# dbg_println("False: one of the subtypes test false: $sTS") -# return Res.F -# else -# push!(checksTS, sTS) # needs further checking below -# continue -# end -# end -# #@show 2, ms -# r = method_exists_forthis_type(f, sTS, ms) -# if r==Res.F # if one subtype does not check-out return false -# dbg_println("False: one subtype does not check: $sTS") -# return Res.F -# end -# if r==Res.M -# push!(checksTS, sTS) # needs further checking below -# end -# end -# # recurse into subtypes where above was not -# out = 0 -# for (i,sTS) in enumerate(checksTS) -# r = method_exists_forall_subtypes(f, sTS, depth=(depth-1), checktop=false) -# if r==Res.F # if one subtype does not check-out return false -# dbg_println("False: one lower subtype does not check: $sTS") -# return Res.F -# end -# out += r # accumulate undicided -# end - -# if out==0 -# dbg_println("True: all subtypes test true.") -# return Res.T -# else -# return Res.M -# end -# end - -# # reflection.jl/_methods. See Introduction to Julia Internals-osdeT-tWjzk.mp4 minute 29 -# # 1) inputs: function, type, limit of number of matches -# # 2) inputs: only used in its internal recursion -# # -# # output: -# # matchtype -signature, values of method parameters, method object +# debugging +# import Traits: FakeMethod, replace_concrete_tvars, get_tms_fms, isfitting +function get_tms_fms(Tr, gf::Function, ind=1) + tm = collect(methods(Tr().methods[gf]))[1] + fms = collect(methods(gf, NTuple{length(tm.sig),Any})) + tm, fms +end diff --git a/src/traitdef.jl b/src/traitdef.jl index 9735b02..3a6c6c3 100644 --- a/src/traitdef.jl +++ b/src/traitdef.jl @@ -139,7 +139,6 @@ function parsefnstypes!(outfns, ln) # parses f(X,Y), f{X <:T}(X,Y) and X+Y # into f and _f(...) - # getsymbol = gensym getsymbol(fn) = symbol("__"*string(fn)) _fn = deepcopy(def) diff --git a/test/runtests.jl b/test/runtests.jl index 887e1cd..fe62308 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -18,16 +18,25 @@ type A2 end @test !istraittype(A1) # istrait helper function: -@test Traits.subs_tvar(TypeVar(:I), Array{TypeVar(:I,Int64)}, Traits._TestTvar{1})==Array{TypeVar(:I,Int64)} +I = TypeVar(:I) +T = TypeVar(:T) +@test Traits.subs_tvar(I, Array{TypeVar(:I,Int64)}, Traits._TestTvar{1})==Array{TypeVar(:I,Int64)} @test Traits.subs_tvar(TypeVar(:I,Int64), Array{TypeVar(:I,Int64)}, Traits._TestTvar{1})==Array{Traits._TestTvar{1}} -@test Traits.subs_tvar(TypeVar(:T), Array, Traits._TestTvar{1})==Array{Traits._TestTvar{1}} # this is kinda bad +@test Traits.subs_tvar(T, Array, Traits._TestTvar{1})==Array{Traits._TestTvar{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._TestTvar{1})==Array # f8576.env.defs.tvars looks like TypeVar(:T) but is different! +@test Traits.subs_tvar(other_T, Array, Traits._TestTvar{1})==Array # f8576.env.defs.tvars looks like T but is different! -@test Traits.find_tvar( (Array, ), TypeVar(:T))==[true] +@test Traits.find_tvar( (Array, ), T)==[true] @test Traits.find_tvar( (Array, ), other_T)==[false] -@test Traits.find_tvar( (Int, Array, ), TypeVar(:T))==[false,true] +@test Traits.find_tvar( (Int, Array, ), T)==[false,true] + +@test Traits.find_correponding_type(Array{Int,2}, Array{I,2}, I)==Any[Int] +@test Traits.find_correponding_type((Array{Int,2}, Float64, (UInt8, UInt16)), + (Array{I,2}, I, (String, I)) , I) == Any[Int, Float64, Traits._TestType{:no_match}, UInt16] +@test Traits.find_correponding_type((Array{Int,2}, Float64, (UInt8, UInt16)), + (Array{I,2}, I, (UInt8, I)) , I) == Any[Int, Float64, UInt16] + # manual implementations include("manual-traitdef.jl") diff --git a/test/traitdef.jl b/test/traitdef.jl index fe1e74d..1eb0782 100644 --- a/test/traitdef.jl +++ b/test/traitdef.jl @@ -86,6 +86,7 @@ end index = [Array{Int,2}, StepRange{Int,Int}] c =1 for c in coll +# @show Collection{c}() # heisenbug protection @test istrait(Collection{c}, verbose=verbose) @test istrait(Iter{c}, verbose=verbose) @test istrait(IterColl{c}, verbose=verbose) @@ -206,7 +207,7 @@ fn77{Y<:Number}(a::Array,b::Y, c::Y) = a[1] ##### # Trait functions parameterized on trait parameters #### - +check_return_types(false) @traitdef Pr3{X} begin fn78{T<:X}(T,T) end @@ -247,6 +248,38 @@ fnpr07{T<:Integer}(::T, ::T, ::Integer) = 1 @test !istrait(Pr07{Integer}) @test istrait(Pr07{Int}) +# function parameters only one of the methods +@traitdef Pr08{X} begin + fnpr08(X, Vector{X}, Integer) +end +fnpr08{T<:Integer}(::T, ::Vector{T}, ::Integer) = 1 +@test !istrait(Pr08{Integer}) +@test istrait(Pr08{Int}) + +@traitdef Pr10{X} begin + fnpr10{T<:X}(T, Vector{T}, Integer) +end +fnpr10(::Int, ::Vector{Int}, ::Integer) = 1 +@test !istrait(Pr10{Integer}) +@test istrait(Pr10{Int}) + +@traitdef Pr11{X} begin + fnpr11(Int, Vector{UInt}, X) +end +fnpr11{T<:Integer}(::T, ::Vector{T}, ::Integer) = 1 +@test !istrait(Pr11{Integer}) +@test !istrait(Pr11{Int}) +@test !istrait(Pr11{UInt}) + +@traitdef Pr12{X} begin + fnpr12(Int, Vector{UInt}, X) +end +fnpr12{T<:Integer}(::T, ::Vector{T}, ::Integer) = 1 +@test !istrait(Pr12{Integer}) +@test !istrait(Pr12{Int}) + +check_return_types(true) + # Test constraints ### @traitdef Cr20{X} begin @@ -288,6 +321,7 @@ end ###### # istrait ##### +check_return_types(false) f12(x::Int) = 1 @traitdef UU{X} begin f12(X) @@ -305,7 +339,7 @@ end @test !istrait(UU13{Any}) @test istrait(UU13{Integer}) @test istrait(UU13{Int8}) - +check_return_types(true) ##### # Associated types #### @@ -389,7 +423,7 @@ end @traitdef TT44{D} begin - Array(Type{D},Integer) # the standard array constructor, should be working for all Types + Array(Type{D},Integer) -> Array # the standard array constructor, should be working for all Types end @test istrait(TT44{A4758}) @test istrait(TT44{A4759}) @@ -404,8 +438,8 @@ end 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 + Arnp(T, Int64) -> Ar + Arnp(T, Int...) -> Ar @constraints begin length(Ar.parameters)>1 # need at least two parameters to be array-like, right? end From 096c551704c7bf7191693f8413be4981afb144a3 Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Tue, 14 Apr 2015 17:00:33 +0200 Subject: [PATCH 24/24] Added and updated documentation --- NEWS.md | 9 +++ README.md | 120 ++++++++++++++---------------- dev_notes.md => docs/dev_notes.md | 29 ++++++++ docs/traitdef.md | 112 ++++++++++++++++++++++++++++ docs/traitfns.md | 8 ++ examples/ex1.jl | 36 +++++---- examples/ex2.jl | 39 +++++++--- examples/ex_tims_traits.jl | 21 ++++++ src/Traits.jl | 2 + test/runtests.jl | 2 + test/traitdef.jl | 3 +- 11 files changed, 286 insertions(+), 95 deletions(-) rename dev_notes.md => docs/dev_notes.md (89%) create mode 100644 docs/traitdef.md create mode 100644 docs/traitfns.md create mode 100644 examples/ex_tims_traits.jl diff --git a/NEWS.md b/NEWS.md index 3a08ff3..08e84fd 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,15 @@ Feature updates for Traits.jl ============================= +14 April 2015 +------------- + +Major overhaul of `istrait` function. Now it uses custom-programmed +checks instead of `method_exists`. This allows parameterized methods +to be used both in the `@traitdef` and in the trait implementation. +This may have pushed Traits.jl into usable terrain. Closes issues #2 +and #8. + 30 March 2015 ------------- diff --git a/README.md b/README.md index 4f831f3..e785935 100644 --- a/README.md +++ b/README.md @@ -26,57 +26,67 @@ mentioned in (1). But Julia does not support (2) or (3) yet. (2) is fairly easy to implement. However, dispatch on a "contract" is not easily possible, but Tim Holy recently came up with [a trick](https://github.com/JuliaLang/julia/issues/2345#issuecomment-54537633). -The cool thing about that trick is that the code for a trait-dispatch -function should be identical to a duck-typed function, i.e. there is -no loss in performance. +The cool thing about that trick is that the generated machine-code for +a trait-dispatch function should be identical to a duck-typed +function, i.e. there is no loss in performance. `Traits.jl` adds those kind of traits to Julia, using Tim's trick -combined with stagedfunctions. See also the Julia-issue +combined with stagedfunctions and extensive facilities to define +traits. See also the Julia-issue [#6975](https://github.com/JuliaLang/julia/issues/6975) concerning interfaces/traits. -Example: +Example `examples/ex1.jl`: ```julia using Traits -# Check Cmp-trait (comparison) which is implemented in src/commontraits.jl -@assert istrait(Cmp{Int,Float64}) -@assert istrait(Cmp{Int,String})==false +# Check Cmp-trait (comparison) which is implemented in Traits.jl/src/commontraits.jl +@assert istrait(Cmp{Int,Float64}) # Int and Float64 can be compared +@assert istrait(Cmp{Int,String})==false # Int and String cannot be compared # make a new trait and add a type to it: @traitdef MyTr{X,Y} begin - foobar(X,Y) -> Bool + foobar(X,Y) -> Bool # All type-tuples for which there is a method foo + # with that signature belong to MyTr end type A a::Int end -foobar(a::A, b::A) = a.a==b.a -@assert istrait(MyTr{A,A}) # true +@assert istrait(MyTr{A,A})==false # foobar not implement yet +foobar(a::A, b::A) = a.a==b.a # implement it +@assert istrait(MyTr{A,A}) # voila! @assert istrait(MyTr{Int,Int})==false # make a function which dispatches on traits: @traitfn ft1{X,Y; Cmp{X,Y}}(x::X,y::Y) = x>y ? 5 : 6 @traitfn ft1{X,Y; MyTr{X,Y}}(x::X,y::Y) = foobar(x,y) ? -99 : -999 -ft1(4,5) # 6 -ft1(A(5), A(6)) # -999 +ft1(4,5) # ==6 i.e. dispatches to first definition +ft1(A(5), A(6)) # ==-999 i.e. dispatches to second definition ft1("asdf", 6) # -> ERROR: TraitException("No matching trait found for function ft1") ``` -This is an experimental package and I will not try to keep backwards -compatibility as I move on. But please give it a try in your code and -give feedback. I will try to document the new features in [NEWS](NEWS.md). +# Package status + +New features are documented in [NEWS](NEWS.md) as they are added. I +keep some notes, musings and plans in [dev_notes.md](docs/dev_notes.md). + +This is a fairly experimental package and I will not try to keep +backwards compatibility as I move on. Please try it out and give me +feedback, issues or pull requests! # Syntax -(source in `examples/ex2.jl`) +The source of below examples is in `examples/ex2.jl`. Most of the +important functions are documented and will respond to `?` in the REPL. -Trait definition: +Trait definition (for details see [traitdef.md](docs/traitdef.md)): ```julia using Traits # simple @traitdef Tr1{X} begin - fun1(X) -> Number + fun1(X) -> Number # this means a method with signature fun1(::X) + # returning a Number end @traitdef Tr2{X,Y} begin fun2(X,Y) -> Number @@ -101,13 +111,12 @@ end end ``` Note that return-type checking is quite experimental. It can be -turned off by defining `Main.Traits_check_return_types=false` before -`using Traits`. +turned off with `check_return_types(false)`. -Trait implementation: +Trait implementation and checking with `istrait`: ```julia -# manual, i.e. just define the functions +# manual definiton, i.e. just define the functions fun1(x::Int) = 5x @assert istrait(Tr1{Int}) @@ -159,20 +168,18 @@ catch e end ``` -Trait functions & dispatch: +Trait functions & dispatch (for details see [traitfns.md](docs/traitfns.md)): ```julia -@traitfn tf1{X, Y; Tr1{X}, Tr1{Y}}(a::X, b::Y) = fun1(a) + fun1(b) -@traitfn tf1{X, Y; Tr2{X,Y}}(a::X, b::Y) = fun2(a,b) +@traitfn tf1{X, Y; Tr1{X}, Tr1{Y}}(a::X, b::Y) = fun1(a) + fun1(b) # I +@traitfn tf1{X, Y; Tr1{X}, Tr1{Y}}(a::X, b::Y, c::Int) = fun1(a) + fun1(b) + c # II +@traitfn tf1{X, Y; Tr2{X,Y}}(a::X, b::Y) = fun2(a,b) # III # Note that all the type-parameters are in the {} and that all # arguments need a type parameter (a limitation of the -# macro-parser). Bad examples are: +# macro-parser). This doesn't work: # # julia> @traitfn ttt1{X, Y; Tr1{X}, Tr1{Y}}(a::X, b::Y, c) = fun1(a) + fun1(b) + c # ERROR: type Symbol has no field args # -# julia> @traitfn ttt1{X, Y; Tr1{X}, Tr1{Y}}(a::X, b::Y, c::Int) = fun1(a) + fun1(b) + c -# ERROR: X3 not defined -# # But this works: # # julia> @traitfn ttt1{X, Y, Z; Tr1{X}, Tr1{Y}}(a::X, b::Y, c::Z) = fun1(a) + fun1(b) + c @@ -180,24 +187,31 @@ Trait functions & dispatch: # tf1 now dispatches on traits -tf1(5.,6.) # -> 77 (Float64 is part of Tr1 but not Tr2) +@assert tf1(5.,6.)==77. # -> 77 ; dispatches to I because istrait(Tr1{Float64}) + # but not istrait(Tr2{Float64,Float64}) +@assert tf1(5.,6.,77)==154. # -> 154. ; dispatches to II because of the extra argument # Errors because of dispatch ambiguity: try - tf1(5,6) # Int is part of Tr1{Int} and Tr2{Int, Int} + tf1(5,6) # istrait(Tr1{Int}) and istrait(Tr2{Int,Int}) are both true! catch e println(e) end -# adding a type to Tr1 will make it work with tf1: +# Implementing Tr1 for a type will make it work with tf1: type MyType a::Int end +try + tf1(MyType(8), 9) # not implemented yet +catch e + println(e) +end @traitimpl Tr1{MyType} begin fun1(x::MyType) = x.a+9 end -tf1(MyType(8), 9) # -> 62 +@assert tf1(MyType(8), 9)==62 # -> 62 ; dispatches to I ``` # Generated code @@ -221,7 +235,7 @@ top: ``` However, for more complicated functions code is not quite the same, -see `test/traitdispatch.jl`. +see `test/perf/perf.jl`. # Inner workings @@ -248,12 +262,12 @@ In Julia dispatch works on types, to extend this to traits I use His trick uses a function to check whether its input types satisfy certain conditions (only dependent on their type) and returns one type or another depending on the outcome. That check-function is then used -for dispatch in another function. Example of Tim's trick: +for dispatch in another function. Example of Tim's trick (`examples/ex_tims_traits.jl`): ```julia type Trait1 end type Trait2 end type Trait3 end -# now define function +# now define function f which should dispatch on those traits f(x,y) = _f(x,y, checkfn(x,y)) _f(x,y,::Type{Trait1}) = x+y _f(x,y,::Type{Trait2}) = x-y @@ -265,12 +279,12 @@ checkfn(::Int, ::Int) = Trait1 checkfn(::Int, ::FloatingPoint) = Trait2 checkfn(::FloatingPoint, ::FloatingPoint) = Trait3 # use -f(3,4) # 7 -f(3,4.) # -1.0 -f(3.,4.) # 12.0 +@assert f(3,4)==7 # Trait1 +@assert f(3,4.)==-1.0 # Trait2 +@assert f(3.,4.)==12.0 # Trait3 # add another type-tuple to Trait3 checkfn(::String, ::String) = Trait3 -f("Lorem ", "Ipsum") # "Lorem Ipsum" +@assert f("Lorem ", "Ipsum")=="Lorem Ipsum" ``` What does this add compared to what we had before with usual dispatch? @@ -334,30 +348,6 @@ trait-hierarchies into account. Although, note that it is easily possible to have unsolvable ambiguities with trait-dispatch as traits do not have a strict hierarchy like types. -# To ponder - -- For many "traits" in Julia, only a few functions need to be - implemented to provide many more. For example for comparison only - `isless` and `==` need to be implemented to automatically get `>`, - `<`, `>=`, `<=`. It would be nice to somehow specify or query those - automatic functions. - -- Are there better ways for trait-dispatch? - -- Sometimes it would be good to get at type parameters, for instance - for Arrays and the like: - ```julia - @traitdef Indexable{X{Y}} begin - getindex(X, Any) -> Y - setindex!(X, Y, Any) -> X - end - ``` - This problem is similar to triangular dispatch and may be solved - by: https://github.com/JuliaLang/julia/issues/6984#issuecomment-49751358 - -# Issues - - # Other trait implementations See the Julia-issue diff --git a/dev_notes.md b/docs/dev_notes.md similarity index 89% rename from dev_notes.md rename to docs/dev_notes.md index 452bbc5..97c9b60 100644 --- a/dev_notes.md +++ b/docs/dev_notes.md @@ -1,6 +1,35 @@ Development notes ================= +Planned work +------------ + +- [ ] Making it easy to specify traits for datatype, see issue #1 +- [ ] improve dispatch of traitfn, see issue #5 + +To ponder +--------- + +- For many "traits" in Julia, only a few functions need to be + implemented to provide many more. For example for comparison only + `isless` and `==` need to be implemented to automatically get `>`, + `<`, `>=`, `<=`. It would be nice to somehow specify or query those + automatic functions. + +- Are there better ways for trait-dispatch? + +- Sometimes it would be good to get at type parameters, for instance + for Arrays and the like: + ```julia + @traitdef Indexable{X{Y}} begin + getindex(X, Any) -> Y + setindex!(X, Y, Any) -> X + end + ``` + This problem is similar to triangular dispatch and may be solved + by: https://github.com/JuliaLang/julia/issues/6984#issuecomment-49751358 + + Road blocks ----------- diff --git a/docs/traitdef.md b/docs/traitdef.md new file mode 100644 index 0000000..4958772 --- /dev/null +++ b/docs/traitdef.md @@ -0,0 +1,112 @@ +About trait definitions +======================= + +It turns out that trait definitions based on methods signatures are a +rather tricky business because Julia allows for quite intricate method +definitions. + +For a generic function `f` and a trait definition +```julia +@traitdef Tr{X} begin + f{...}(...) -> ... +end +``` +what does it mean that `istrait(Tr{T})==true` for some type `T`? +First a slight detour on the meaning of `{...}(...)`, this is +essentially a type tuple with constraints on the actual types in +`(...)` in `{...}`. Inside a `Method` `m` these two parts are stored +in `m.tvars` (the `{}`) and `m.sig` (the `()`), which I will use below. + + +What I implemented (discounting bugs) are the following rules: + +## Method call signature + +The method call signature, the `{...}(...)` <=> `tm.tvars`, `tm.sig` +part of above definition, is satisfied if at least one method `fm` of +generic function `f` satisfies: + +A) `tm.sig<:fm.sig` i.e. just the type tuple of the trait-method is a + subtype of the generic-fn-method. + +B) The parametric constraints parameters on `fm.sig` and `tm.sig` need + to feature in the same argument positions. Except when the + corresponding function parameter is constraint by a concrete type: + then make sure that all the occurrences are the same concrete type. + +So, as long as neither the trait-method nor the generic-fn-method has +any parametric constraints, it's easy. It's just the subtyping +relation between the two. However, once parametric constraints are +use on either or both then it is complicated. + +Examples + +The same constraints on both methods: +```julia +@traitdef Pr0{X} begin + fn75{Y <: Integer}(X, Y) +end +fn75{Y<:Integer}(x::UInt8, y::Y) = y+x +@test istrait(Pr0{UInt8}) +```` + +Only the last constraint is general enough to assure `fn77` will be +callable for all `X` which are `Pr2{X}`: +```julia +@traitdef Pr2{X} begin + fn77{Y<:Number}(X,Y,Y) +end +fn77(a::Array,b::Int, c::Float64) = a[1] +@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}) +``` + +A trait-method with constraints can be implemented with a method +without constraints for a concrete type: +```julia +@traitdef Pr3{X} begin + fn78{T<:X}(T,T) +end +fn78(b::Int, c::Int) = b +# This works because fn78 can be called for all arguments (Int,): +@test istrait(Pr3{Int}) + +fn78(b::Real, c::Real) = b +# This fails because the call fn78(5, 6.) is possible but not allowed +# by Pr3: +@test !istrait(Pr3{Real}) +# Now all good: +fn78{T}(b::T, c::T) = b +@test istrait(Pr3{Real}) +@test istrait(Pr3{Any}) +``` + +The other way around is similar +```julia +@traitdef Pr07{X} begin + fnpr07(X, X, Integer) # no parametric-constraints +end +fnpr07{T<:Integer}(::T, ::T, ::Integer) = 1 +# This is not true as for instance a call fnpr07(8, UInt(8)) would fail: +@test !istrait(Pr07{Integer}) +# This is fine as any call (Int,Int) will succeed: +@test istrait(Pr07{Int}) +``` + +There are a lot more examples in `test/traitdef.jl`. Most of this +functionality is implemented in the `isfitting` function. + +## Method return signature + +The specifed return type in the `@traitdef` (`tret`) and return-type +interfered with `Base.treturn_types` of the generic function has to be +`fret<:tret`. Note that this is backwards to argument types checking +above, which makes sense as the variance of functions arguments and +return types is different. + +Note that currently there is no check that the method which satisfies +the 'method call signature' test is the same method which satisfies +the 'return signature' test. diff --git a/docs/traitfns.md b/docs/traitfns.md new file mode 100644 index 0000000..6ccb87f --- /dev/null +++ b/docs/traitfns.md @@ -0,0 +1,8 @@ +About trait functions +===================== + +ToDo + +Dispatch +-------- +See issue #5 and links diff --git a/examples/ex1.jl b/examples/ex1.jl index ee4630d..d9bf345 100644 --- a/examples/ex1.jl +++ b/examples/ex1.jl @@ -1,35 +1,33 @@ using Traits -using Base.Test - -# check some traits implemented in src/commontraits.jl -@assert istrait(Cmp{Int,Float64}) -@assert istrait(Cmp{Int,String})==false +# Check Cmp-trait (comparison) which is implemented in Traits.jl/src/commontraits.jl +@assert istrait(Cmp{Int,Float64}) # Int and Float64 can be compared +@assert istrait(Cmp{Int,String})==false # Int and String cannot be compared # make a new trait and add a type to it: @traitdef MyTr{X,Y} begin - foobar(X,Y) -> Bool + foobar(X,Y) -> Bool # All type-tuples for which there is a method foo + # with that signature belong to MyTr end type A a::Int end -foobar(a::A, b::A) = a.a==b.a -@assert istrait(MyTr{A,A}) # true +@assert istrait(MyTr{A,A})==false # foobar not implement yet +foobar(a::A, b::A) = a.a==b.a # implement it +@assert istrait(MyTr{A,A}) # voila! @assert istrait(MyTr{Int,Int})==false # make a function which dispatches on traits: @traitfn ft1{X,Y; Cmp{X,Y}}(x::X,y::Y) = x>y ? 5 : 6 @traitfn ft1{X,Y; MyTr{X,Y}}(x::X,y::Y) = foobar(x,y) ? -99 : -999 -ft1(4,5) # 6 -ft1(A(5), A(6)) # -999 - -# # dispatch on traits has its pitfalls: -# @traitfn ft1{X,Y; Arith{X,Y}}(x::X,y::Y) = x+y +ft1(4,5) # ==6 i.e. dispatches to first definition +ft1(A(5), A(6)) # ==-999 i.e. dispatches to second definition -# # now it's impossible to decide which method of ft1 to pick -# @test_throws TraitException ft1(4,5) - -@test_throws TraitException ft1("asdf", 5) +try + ft1("asdf", 5) +catch err + println(err) +end foobar(a::String, b::Int) = length(a)==b ft1("asdf", 5) @@ -56,5 +54,5 @@ bar(a::B2, b::B2) = a.a==b.a # here for Julia to infer the # return type -@test gt1(B1(1), B1(1))=="MyTr" -@test gt1(B2(1), B2(1))=="MyTr2" +@assert gt1(B1(1), B1(1))=="MyTr" +@assert gt1(B2(1), B2(1))=="MyTr2" diff --git a/examples/ex2.jl b/examples/ex2.jl index 586766c..60ba912 100644 --- a/examples/ex2.jl +++ b/examples/ex2.jl @@ -4,7 +4,8 @@ using Traits # simple @traitdef Tr1{X} begin - fun1(X) -> Number + fun1(X) -> Number # this means a method with signature fun1(::X) + # returning a Number end @traitdef Tr2{X,Y} begin fun2(X,Y) -> Number @@ -78,31 +79,49 @@ end ### Trait functions ################### -@traitfn tf1{X, Y; Tr1{X}, Tr1{Y}}(a::X, b::Y) = fun1(a) + fun1(b) -@traitfn tf1{X, Y; Tr2{X,Y}}(a::X, b::Y) = fun2(a,b) +@traitfn tf1{X, Y; Tr1{X}, Tr1{Y}}(a::X, b::Y) = fun1(a) + fun1(b) # I +@traitfn tf1{X, Y; Tr1{X}, Tr1{Y}}(a::X, b::Y, c::Int) = fun1(a) + fun1(b) + c # II +@traitfn tf1{X, Y; Tr2{X,Y}}(a::X, b::Y) = fun2(a,b) # III +# Note that all the type-parameters are in the {} and that all +# arguments need a type parameter (a limitation of the +# macro-parser). This doesn't work: +# +# julia> @traitfn ttt1{X, Y; Tr1{X}, Tr1{Y}}(a::X, b::Y, c) = fun1(a) + fun1(b) + c +# ERROR: type Symbol has no field args +# +# But this works: +# +# julia> @traitfn ttt1{X, Y, Z; Tr1{X}, Tr1{Y}}(a::X, b::Y, c::Z) = fun1(a) + fun1(b) + c +# ttt1 (generic function with 6 methods) # tf1 now dispatches on traits -tf1(5.,6.) # -> 77 +@assert tf1(5.,6.)==77. # -> 77 ; dispatches to I because istrait(Tr1{Float64}) + # but not istrait(Tr2{Float64,Float64}) +@assert tf1(5.,6.,77)==154. # -> 154. ; dispatches to II because of the extra argument # Errors because of dispatch ambiguity: try - tf1(5,6) + tf1(5,6) # istrait(Tr1{Int}) and istrait(Tr2{Int,Int}) are both true! catch e println(e) end -# adding a type to Tr1 will make it work with tf1: +# Implementing Tr1 for a type will make it work with tf1: type MyType a::Int end +try + tf1(MyType(8), 9) # not implemented yet +catch e + println(e) +end @traitimpl Tr1{MyType} begin fun1(x::MyType) = x.a+9 end +@assert tf1(MyType(8), 9)==62 # -> 62 ; dispatches to I -tf1(MyType(8), 9) # -> 62 - -### Generated code -################## +### Generated machine code +########################## f(x,y) = 7x + 7y @code_llvm f(5.,6.) @code_llvm tf1(5.,6.) diff --git a/examples/ex_tims_traits.jl b/examples/ex_tims_traits.jl new file mode 100644 index 0000000..3edddef --- /dev/null +++ b/examples/ex_tims_traits.jl @@ -0,0 +1,21 @@ +type Trait1 end +type Trait2 end +type Trait3 end +# now define function f which should dispatch on those traits +ftim(x,y) = _ftim(x,y, checkfn(x,y)) +_ftim(x,y,::Type{Trait1}) = x+y +_ftim(x,y,::Type{Trait2}) = x-y +_ftim(x,y,::Type{Trait3}) = x*y +# default +checkfn{T,S}(x::T,y::S) = error("Function ftim not implemented for type ($T,$S)") +# associate types-tuples to Trait1, Trait2 or Trait3: +checkfn(::Int, ::Int) = Trait1 +checkfn(::Int, ::FloatingPoint) = Trait2 +checkfn(::FloatingPoint, ::FloatingPoint) = Trait3 +# use +@assert ftim(3,4)==7 # Trait1 +@assert ftim(3,4.)==-1.0 # Trait2 +@assert ftim(3.,4.)==12.0 # Trait3 +# Add another type-tuple to Trait3 +checkfn(::String, ::String) = Trait3 +@assert ftim("Lorem ", "Ipsum")=="Lorem Ipsum" diff --git a/src/Traits.jl b/src/Traits.jl index b447e0f..13ff2bb 100644 --- a/src/Traits.jl +++ b/src/Traits.jl @@ -171,6 +171,8 @@ function istrait{T<:Trait}(Tr::Type{T}; verbose=false) end end + # TODO: only check return-types for methods which passed call-signature checks. + # check return-type. Specifed return type tret and return-type of # the methods frets should fret<:tret. This is backwards to # argument types checking above. diff --git a/test/runtests.jl b/test/runtests.jl index fe62308..c00fe70 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -5,6 +5,8 @@ using Traits # 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 +# these two are not relevant anymore as method_exists is not used anymore + function_types_bug1 = true # set to false if function types get implemented in Julia # Traits.jl issues: dispatch_bug1 = true # in traitdispatch.jl diff --git a/test/traitdef.jl b/test/traitdef.jl index 1eb0782..8f2ac0f 100644 --- a/test/traitdef.jl +++ b/test/traitdef.jl @@ -280,8 +280,9 @@ fnpr12{T<:Integer}(::T, ::Vector{T}, ::Integer) = 1 check_return_types(true) +#### # Test constraints -### +#### @traitdef Cr20{X} begin length(X) -> Any