From 5e26d3b17f3deb1e49768ebbe076e11b49c230d0 Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Thu, 9 Apr 2015 21:09:22 +0200 Subject: [PATCH 01/15] 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 6ebe6622181cc97d3bac7bb016edf65b3c8b06ca Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Thu, 9 Apr 2015 22:07:24 +0200 Subject: [PATCH 02/15] 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 bccce6e0f80b02462007ca4882ebf2602619d469 Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Fri, 10 Apr 2015 13:13:33 +0200 Subject: [PATCH 03/15] 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 7cb5ffaa60af46f6e520a51e90060a0ac9ed0694 Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Fri, 10 Apr 2015 14:26:47 +0200 Subject: [PATCH 04/15] 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 d5f9c3433fb1291a01328e819e6171624abf69f1 Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Fri, 10 Apr 2015 16:00:39 +0200 Subject: [PATCH 05/15] 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 907bc7894cc6eba5f2d3c0381b2a737815502837 Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Fri, 10 Apr 2015 16:39:19 +0200 Subject: [PATCH 06/15] 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 4f720b6e10f216902d1c8921fcd3abe01774dc7a Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Fri, 10 Apr 2015 17:17:21 +0200 Subject: [PATCH 07/15] 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 4344c4ca2d0a2944522a5bf64fcfb87cd16c7c0a Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Fri, 10 Apr 2015 17:45:40 +0200 Subject: [PATCH 08/15] 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 3056bc27fa8bde930d988b26b70be92ec8219015 Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Fri, 10 Apr 2015 22:48:48 +0200 Subject: [PATCH 09/15] 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 1448124efe9a9fa2833d3b3bcda41d5ef3bfc171 Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Sat, 11 Apr 2015 12:58:51 +0200 Subject: [PATCH 10/15] 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 823e2bc7eb15d24076771d2eb4fabb74005317f7 Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Sat, 11 Apr 2015 13:45:15 +0200 Subject: [PATCH 11/15] 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 ef3e0a2a1e26fd95129ae122966cfbe90d523010 Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Sat, 11 Apr 2015 13:50:07 +0200 Subject: [PATCH 12/15] 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 d732360c46705a21b99eaca7d3914b5fa93f4445 Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Sat, 11 Apr 2015 20:01:24 +0200 Subject: [PATCH 13/15] 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 c32d72cc2ee559ae5845f164b7386e467f5f7671 Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Sat, 11 Apr 2015 20:18:09 +0200 Subject: [PATCH 14/15] 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 c83a3c5501c06eccb9fa828e9955c3dae1763bab Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Sun, 12 Apr 2015 20:58:37 +0200 Subject: [PATCH 15/15] Commented work-around, now throwing error again. --- src/Traits.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Traits.jl b/src/Traits.jl index c56c9f4..7d26a87 100644 --- a/src/Traits.jl +++ b/src/Traits.jl @@ -252,10 +252,10 @@ function isfitting(tmm::Method, fm::Method; verbose=false) # tm=trait-method, fm # 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 + # if length(tm.sig)==-10 + # @show tm + # error("This is not possible") + # end end