Skip to content
This repository was archived by the owner on Feb 7, 2019. It is now read-only.

Commit 4673139

Browse files
committed
More improvements for parameter constraint methods
Definitly getting the the area of dimishing returns...
1 parent 546ece8 commit 4673139

File tree

5 files changed

+205
-319
lines changed

5 files changed

+205
-319
lines changed

src/Traits.jl

Lines changed: 143 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ module Traits
88
- they are structural types: i.e. they needn't be declared explicitly
99
""" -> current_module()
1010

11-
export istrait, istraittype, issubtrait,
11+
export istrait, istraittype, issubtrait, check_return_types,
1212
traitgetsuper, traitgetpara, traitmethods,
1313
@traitdef, @traitimpl, @traitfn, TraitException, All
1414

@@ -29,12 +29,18 @@ include("helpers.jl")
2929
# TODO: update to use functions.
3030
if isdefined(Main, :Traits_check_return_types)
3131
println("Traits.jl: not using return types of @traitdef functions")
32-
flag_check_return_types = Main.Traits_check_return_types
32+
const flag_check_return_types = Main.Traits_check_return_types
3333
else
34-
flag_check_return_types = true
34+
const flag_check_return_types = true
3535
end
3636
@doc "Flag to select whether return types in @traitdef's are checked" flag_check_return_types
3737

38+
@doc "Toggles return type checking. Will issue warning because of const declaration, ignore:"->
39+
function check_return_types(flg::Bool)
40+
global flag_check_return_types
41+
flag_check_return_types = flg
42+
end
43+
3844
#######
3945
# Types
4046
#######
@@ -114,7 +120,7 @@ istraittype(x::Tuple) = mapreduce(istraittype, &, x)
114120
""" ->
115121
function istrait{T<:Trait}(Tr::Type{T}; verbose=false)
116122
if verbose
117-
println_verb(x) = println("Checking $Tr: " * x)
123+
println_verb(x) = println("**** Checking $(deparameterize_type(Tr)): " * x)
118124
else
119125
println_verb = x->x
120126
end
@@ -145,9 +151,10 @@ function istrait{T<:Trait}(Tr::Type{T}; verbose=false)
145151

146152
# Check call signature of all methods:
147153
for (gf,_gf) in tr.methods
148-
println_verb("Checking function $gf")
154+
println_verb("*** Checking function $gf")
149155
# Loop over all methods defined for each function in traitdef
150156
for tm in methods(_gf)
157+
println_verb("** Checking method $tm")
151158
checks = false
152159
# Only loop over methods which have the right number of arguments:
153160
for fm in methods(gf, NTuple{length(tm.sig),Any})
@@ -157,25 +164,29 @@ function istrait{T<:Trait}(Tr::Type{T}; verbose=false)
157164
end
158165
end
159166
if !checks # if check==false no fitting method was found
160-
println_verb("""No method of the generic function/call-overloaded $gf matched the
161-
trait specification: $tm""")
167+
println_verb("""No method of the generic function/call-overloaded `$gf` matched the
168+
trait specification: `$tm`""")
162169
return false
163170
end
164171
end
165172
end
166173

167174
# check return-type. Specifed return type tret and return-type of
168175
# the methods frets should fret<:tret. This is backwards to
169-
# argument types...
176+
# argument types checking above.
170177
if flag_check_return_types
171178
for (gf,_gf) in tr.methods
179+
println_verb("*** Checking return types of function $gf")
172180
for tm in methods(_gf) # loop over all methods defined for each function in traitdef
181+
println_verb("** Checking return types of method $tm")
173182
tret_typ = Base.return_types(_gf, tm.sig) # trait-defined return type
174183
if length(tret_typ)==0
175184
continue # this means the signature contains None which is not compatible with return types
176-
# TODO: introduce a specical type signalling that no return type was given.
185+
# TODO: introduce a special type signaling that no return type was given.
177186
elseif length(tret_typ)>1
178-
throw(TraitException("Querying the return type of the trait-method $tm did not return exactly one return type: $tret_typ"))
187+
if !allequal(tret_typ) # Ok if all return types are the same.
188+
throw(TraitException("Querying the return type of the trait-method $tm did not return exactly one return type: $tret_typ"))
189+
end
179190
end
180191
tret_typ = tret_typ[1]
181192
fret_typ = Base.return_types(gf, tm.sig)
@@ -191,6 +202,7 @@ function istrait{T<:Trait}(Tr::Type{T}; verbose=false)
191202
$tret_typ
192203
List of found return types:
193204
$fret_typ
205+
Returning false.
194206
""")
195207
return false
196208
end
@@ -213,17 +225,25 @@ immutable FakeMethod
213225
tvars::(Any...,)
214226
va::Bool
215227
end
216-
@doc """isfitting checks whether a method `tm` specified in the trait definition
217-
is fulfilled by a method `fm` of the corresponding generic function. The
218-
core function called by istraits.
228+
@doc """isfitting checks whether the signature of a method `tm`
229+
specified in the trait definition is fulfilled by one method `fm`
230+
of the corresponding generic function. This is the core function
231+
which is called by istraits.
219232
220233
Checks that tm.sig<:fm.sig and that the parametric constraints on
221-
fm and tm are equal. Lets call this relation tm<<:fm.
234+
fm and tm are equal where applicable. Lets call this relation tm<<:fm.
235+
236+
So, summarizing, for a trait-signature to be satisfied (fitting)
237+
the following condition need to hold:
238+
239+
A) `tsig<:sig` for just the types themselves (sans parametric
240+
constraints)
222241
223-
So, summarizing, for a trait-signature to be satisfied (fitting) the following
224-
condition need to hold:
225-
A) `tsig<:sig` for just the types themselves (sans parametric constraints)
226-
B) The parametric constraints on `sig` and `tsig` need to be equal.
242+
B) The parametric constraints parameters on `sig` and `tsig` need
243+
to feature in the same argument positions. Except when the
244+
corresponding function parameter is constraint by a concrete
245+
type: then make sure that all the occurrences are the same
246+
concrete type.
227247
228248
Examples, left trait-method, right implementation-method:
229249
{T<:Real, S}(a::T, b::Array{T,1}, c::S, d::S) <<: {T<:Number, S}(a::T, b::AbstractArray{T,1}, c::S, d::S)
@@ -232,19 +252,25 @@ end
232252
{T<:Integer}(T, T, Integer) <<: {T<:Integer}(T, T, T)
233253
-> false as parametric constraints are not equal
234254
""" ->
235-
function isfitting(tmm::Method, fm::Method; verbose=false) # tm=trait-method, fm=function-method
255+
function isfitting(tmm::Method, fmm::Method; verbose=false) # tm=trait-method, fm=function-method
236256
println_verb = verbose ? println : x->x
237257

238-
# Make a "copy" of tmm as it may get updated:
239-
tm = FakeMethod(tmm.sig, tmm.tvars, tmm.va)
258+
# Make a "copy" of tmm & fmm as it may get updated:
259+
tm = FakeMethod(tmm.sig, isa(tmm.tvars,Tuple) ? tmm.tvars : (tmm.tvars,), tmm.va)
260+
fm = FakeMethod(fmm.sig, isa(fmm.tvars,Tuple) ? fmm.tvars : (fmm.tvars,), fmm.va)
261+
# Note the `? : ` is needed because of https://github.com/JuliaLang/julia/issues/10811
262+
263+
# Replace type parameters which are constraint by a concrete type
264+
# (because Vector{TypeVar(:V, Int)}<:Vector{Int}==false but we need ==true)
265+
tm = replace_concrete_tvars(tm)
266+
fm = replace_concrete_tvars(fm)
240267

241268
# Special casing for call-overloading.
242-
if fm.func.code.name==:call && tmm.func.code.name!=:call # true if only fm is call-overloaded
269+
if fmm.func.code.name==:call && tmm.func.code.name!=:call # true if only fm is call-overloaded
243270
# prepend ::Type{...} to signature
244271
tm = FakeMethod(tuple(fm.sig[1], tm.sig...), tm.tvars, tm.va)
245272
# check whether there are method parameters too:
246-
fmtvars = isa(fm.tvars,TypeVar) ? (fm.tvars,) : fm.tvars # make sure it's a tuple of TypeVars
247-
for ftv in fmtvars
273+
for ftv in fm.tvars
248274
flocs = find_tvar(fm.sig, ftv)
249275
if flocs[1] # yep, has a constraint like call{T}(::Type{Array{T}},...)
250276
if sum(flocs)==1
@@ -265,28 +291,12 @@ function isfitting(tmm::Method, fm::Method; verbose=false) # tm=trait-method, fm
265291
error("This is not possible")
266292
end
267293
end
268-
269294

270295
## Check condition A:
271-
# If there are no type-vars then just compare the signatures:
272-
if tm.tvars==()
273-
if !(fm.tvars==())
274-
# If there are parameter constraints affecting more than
275-
# one argument, then return false.
276-
fmtvars = isa(fm.tvars,TypeVar) ? (fm.tvars,) : fm.tvars
277-
for ftv in fmtvars
278-
typs = tm.sig[find_tvar(fm.sig, ftv)]
279-
if length(typs)==0
280-
println_verb("Reason fail: this method is not callable because: static parameter does not occur in signature.")
281-
return false
282-
end
283-
if length(typs)>1 && !all(map(isleaftype, typs))
284-
println_verb("Reason fail: tvars-constraints in function-method are on non-leaftypes in traitmethod.")
285-
return false
286-
end
287-
end
288-
end
289-
println_verb("Reason fail/pass: no tvars in trait-method. Result: $(tm.sig<:fm.sig)")
296+
# If there are no function parameters then just compare the
297+
# signatures.
298+
if tm.tvars==() && fm.tvars==()
299+
println_verb("Reason fail/pass: no tvars in trait-method only checking signature. Result: $(tm.sig<:fm.sig)")
290300
return tm.sig<:fm.sig
291301
end
292302
# If !(tm.sig<:fm.sig) then tm<<:fm is false
@@ -300,7 +310,7 @@ function isfitting(tmm::Method, fm::Method; verbose=false) # tm=trait-method, fm
300310
# False if there are not the same number of arguments: (I don't
301311
# think this test is necessary as it is tested above.)
302312
if length(tm.sig)!=length(fm.sig)!
303-
println_verb("Reason fail: wrong length")
313+
println_verb("Reason fail: not same argument length.")
304314
return false
305315
end
306316
# Getting to here means that that condition (A) is fulfilled.
@@ -313,20 +323,47 @@ function isfitting(tmm::Method, fm::Method; verbose=false) # tm=trait-method, fm
313323
return true
314324
end
315325

326+
# First special case if tm.tvars==() && !(fm.tvars==())
327+
if tm.tvars==()
328+
fm.tvars==() && error("Execution shouldn't get here as this should have been checked above!")
329+
for (i,ftv) in enumerate(fm.tvars)
330+
# If all the types in tm.sig, which correspond to a
331+
# parameter constraint argument of fm.sig, are the same then pass.
332+
typs = tm.sig[find_tvar(fm.sig, ftv)]
333+
if length(typs)==0
334+
println_verb("Reason fail: this method $fmm is not callable because the static parameter does not occur in signature.")
335+
return false
336+
elseif length(typs)==1 # Necessarily the same
337+
continue
338+
else # length(typs)>1
339+
if !all(map(isleaftype, typs)) # note isleaftype can have some issues with inner constructors
340+
println_verb("Reason fail: not all parametric-constraints in function-method $fmm are on leaftypes in traitmethod $tmm.")
341+
return false
342+
else
343+
# Now check that all of the tm.sig-types have the same type at the parametric-constraint sites.
344+
if !allequal(find_correponding_type(tm.sig, fm.sig, ftv))
345+
println_verb("Reason fail: not all parametric-constraints in function-method $fmm correspond to the same type in traitmethod $tmm.")
346+
return false
347+
end
348+
end
349+
end
350+
end
351+
println_verb("""Reason pass: All occurrences of the parametric-constraint in $fmm correspond to the
352+
same type in trait-method $tmm.""")
353+
return true
354+
end
355+
316356
# Strategy: go through constraints on trait-method and check
317357
# whether they are fulfilled in function-method.
318-
tmtvars = isa(tm.tvars,Tuple) ? tm.tvars : (tm.tvars,)
319-
tvars = isa(tm.tvars,TypeVar) ? (tm.tvars,) : tm.tvars
320-
for tv in tvars
358+
for tv in tm.tvars
321359
# find all occurrences in the signature
322360
locs = find_tvar(tm.sig, tv)
323361
if !any(locs)
324-
throw(TraitException("The type variable should feature in at least on location."))
362+
throw(TraitException("The parametric-constraint of trait-method $tmm has to feature in at least one argument of the signature."))
325363
end
326364
# Find the tvar in fm which corresponds to tv.
327365
ftvs = Any[]
328-
fmtvars = isa(fm.tvars,TypeVar) ? (fm.tvars,) : fm.tvars # make sure it's a tuple of TypeVar
329-
for ftv in fmtvars
366+
for ftv in fm.tvars
330367
flocs = find_tvar(fm.sig, ftv)
331368
if all(flocs[find(locs)])
332369
push!(ftvs,ftv)
@@ -340,7 +377,7 @@ function isfitting(tmm::Method, fm::Method; verbose=false) # tm=trait-method, fm
340377
# end
341378
# g01(::Int, ::Int) = Int
342379
# @assert istrait(Tr01{Int}, verbose=true)
343-
if isleaftype(tv.ub)
380+
if isleaftype(tv.ub) # note isleaftype can have some issues with inner constructors
344381
# Check if the method definition of fm has the same
345382
# leaftypes in the same location.
346383
if mapreduce(x -> x==tv.ub, &, true, fm.sig[locs])
@@ -375,7 +412,7 @@ function isfitting(tmm::Method, fm::Method; verbose=false) # tm=trait-method, fm
375412
end
376413

377414
# helpers for isfitting
378-
function subs_tvar{T<:_TestTvar}(tv::TypeVar, arg::DataType, TestT::Type{T})
415+
function subs_tvar(tv::TypeVar, arg::DataType, TestT::DataType)
379416
# Substitute `TestT` for a particular TypeVar `tv` in an argument `arg`.
380417
#
381418
# Example:
@@ -388,8 +425,59 @@ function subs_tvar{T<:_TestTvar}(tv::TypeVar, arg::DataType, TestT::Type{T})
388425
return typ{pa...}
389426
end
390427
end
391-
subs_tvar{T<:_TestTvar}(tv::TypeVar, arg::TypeVar, TestT::Type{T}) = tv===arg ? TestT : arg # note === this it essential!
392-
subs_tvar{T<:_TestTvar}(tv::TypeVar, arg, TestT::Type{T}) = arg # for anything else
428+
subs_tvar(tv::TypeVar, arg::TypeVar, TestT::DataType) = tv===arg ? TestT : arg # note === this it essential!
429+
subs_tvar(tv::TypeVar, arg, TestT::DataType) = arg # for anything else
430+
431+
function replace_concrete_tvars(m::FakeMethod)
432+
# Example:
433+
# FakeMethod((T<:Int64,Array{T<:Int64,1},Integer),(T<:Int64,),false)
434+
# ->
435+
# FakeMethod((Int64, Array{Int64,1}, Integer),() ,false)
436+
newtv = []
437+
newsig = Any[m.sig...] # without the Any I get seg-faults and
438+
# other strange erros!
439+
for tv in m.tvars
440+
if !isleaftype(tv.ub)
441+
push!(newtv, tv)
442+
else
443+
newsig = Any[subs_tvar(tv, arg, tv.ub) for arg in newsig]
444+
end
445+
end
446+
FakeMethod(tuple(newsig...), tuple(newtv...), m.va)
447+
end
448+
449+
# Finds the types in tmsig which correspond to TypeVar ftv in fmsig
450+
function find_correponding_type(tmsig::Tuple, fmsig::Tuple, ftv::TypeVar)
451+
out = Any[]
452+
for (ta,fa) in zip(tmsig,fmsig)
453+
if isa(fa, TypeVar)
454+
fa===ftv && push!(out, ta)
455+
elseif isa(fa, DataType) || isa(fa, Tuple)
456+
append!(out, find_correponding_type(ta,fa,ftv))
457+
else
458+
@show ta, fa
459+
error("Not implemented")
460+
end
461+
end
462+
return out
463+
end
464+
function find_correponding_type(ta::DataType, fa::DataType, ftv::TypeVar)
465+
# gets here if fa is not a TypeVar
466+
out = Any[]
467+
if !( deparameterize_type(ta)<:deparameterize_type(fa)) # ||
468+
# length(ta.parameters)!=length(fa.parameters) # don't check for length. If not the same length, assume that the first parameters are corresponding...
469+
push!(out, _TestType{:no_match}) # this will lead to a no-match in isfitting
470+
return out
471+
end
472+
for (tp,fp) in zip(ta.parameters,fa.parameters)
473+
if isa(fp, TypeVar)
474+
fp===ftv && push!(out, tp)
475+
elseif isa(fp, DataType) || isa(fa, Tuple)
476+
append!(out, find_correponding_type(tp,fp,ftv))
477+
end
478+
end
479+
return out
480+
end
393481

394482
# find_tvar finds index of arguments in a function signature `sig` where a
395483
# particular TypeVar `tv` features. Example:

0 commit comments

Comments
 (0)