@@ -8,7 +8,7 @@ module Traits
8
8
- they are structural types: i.e. they needn't be declared explicitly
9
9
""" -> current_module ()
10
10
11
- export istrait, istraittype, issubtrait,
11
+ export istrait, istraittype, issubtrait, check_return_types,
12
12
traitgetsuper, traitgetpara, traitmethods,
13
13
@traitdef , @traitimpl , @traitfn , TraitException, All
14
14
@@ -29,12 +29,18 @@ include("helpers.jl")
29
29
# TODO : update to use functions.
30
30
if isdefined (Main, :Traits_check_return_types )
31
31
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
33
33
else
34
- flag_check_return_types = true
34
+ const flag_check_return_types = true
35
35
end
36
36
@doc " Flag to select whether return types in @traitdef's are checked" flag_check_return_types
37
37
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
+
38
44
# ######
39
45
# Types
40
46
# ######
@@ -114,7 +120,7 @@ istraittype(x::Tuple) = mapreduce(istraittype, &, x)
114
120
""" ->
115
121
function istrait {T<:Trait} (Tr:: Type{T} ; verbose= false )
116
122
if verbose
117
- println_verb (x) = println (" Checking $Tr : " * x)
123
+ println_verb (x) = println (" **** Checking $( deparameterize_type (Tr)) : " * x)
118
124
else
119
125
println_verb = x-> x
120
126
end
@@ -145,9 +151,10 @@ function istrait{T<:Trait}(Tr::Type{T}; verbose=false)
145
151
146
152
# Check call signature of all methods:
147
153
for (gf,_gf) in tr. methods
148
- println_verb (" Checking function $gf " )
154
+ println_verb (" *** Checking function $gf " )
149
155
# Loop over all methods defined for each function in traitdef
150
156
for tm in methods (_gf)
157
+ println_verb (" ** Checking method $tm " )
151
158
checks = false
152
159
# Only loop over methods which have the right number of arguments:
153
160
for fm in methods (gf, NTuple{length (tm. sig),Any})
@@ -157,25 +164,29 @@ function istrait{T<:Trait}(Tr::Type{T}; verbose=false)
157
164
end
158
165
end
159
166
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 ` """ )
162
169
return false
163
170
end
164
171
end
165
172
end
166
173
167
174
# check return-type. Specifed return type tret and return-type of
168
175
# the methods frets should fret<:tret. This is backwards to
169
- # argument types.. .
176
+ # argument types checking above .
170
177
if flag_check_return_types
171
178
for (gf,_gf) in tr. methods
179
+ println_verb (" *** Checking return types of function $gf " )
172
180
for tm in methods (_gf) # loop over all methods defined for each function in traitdef
181
+ println_verb (" ** Checking return types of method $tm " )
173
182
tret_typ = Base. return_types (_gf, tm. sig) # trait-defined return type
174
183
if length (tret_typ)== 0
175
184
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.
177
186
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
179
190
end
180
191
tret_typ = tret_typ[1 ]
181
192
fret_typ = Base. return_types (gf, tm. sig)
@@ -191,6 +202,7 @@ function istrait{T<:Trait}(Tr::Type{T}; verbose=false)
191
202
$tret_typ
192
203
List of found return types:
193
204
$fret_typ
205
+ Returning false.
194
206
""" )
195
207
return false
196
208
end
@@ -213,17 +225,25 @@ immutable FakeMethod
213
225
tvars:: (Any...,)
214
226
va:: Bool
215
227
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.
219
232
220
233
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)
222
241
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.
227
247
228
248
Examples, left trait-method, right implementation-method:
229
249
{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
232
252
{T<:Integer}(T, T, Integer) <<: {T<:Integer}(T, T, T)
233
253
-> false as parametric constraints are not equal
234
254
""" ->
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
236
256
println_verb = verbose ? println : x-> x
237
257
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)
240
267
241
268
# 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
243
270
# prepend ::Type{...} to signature
244
271
tm = FakeMethod (tuple (fm. sig[1 ], tm. sig... ), tm. tvars, tm. va)
245
272
# 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
248
274
flocs = find_tvar (fm. sig, ftv)
249
275
if flocs[1 ] # yep, has a constraint like call{T}(::Type{Array{T}},...)
250
276
if sum (flocs)== 1
@@ -265,28 +291,12 @@ function isfitting(tmm::Method, fm::Method; verbose=false) # tm=trait-method, fm
265
291
error (" This is not possible" )
266
292
end
267
293
end
268
-
269
294
270
295
# # 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 ) " )
290
300
return tm. sig<: fm.sig
291
301
end
292
302
# 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
300
310
# False if there are not the same number of arguments: (I don't
301
311
# think this test is necessary as it is tested above.)
302
312
if length (tm. sig)!= length (fm. sig)!
303
- println_verb (" Reason fail: wrong length" )
313
+ println_verb (" Reason fail: not same argument length. " )
304
314
return false
305
315
end
306
316
# 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
313
323
return true
314
324
end
315
325
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
+
316
356
# Strategy: go through constraints on trait-method and check
317
357
# 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
321
359
# find all occurrences in the signature
322
360
locs = find_tvar (tm. sig, tv)
323
361
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 ." ))
325
363
end
326
364
# Find the tvar in fm which corresponds to tv.
327
365
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
330
367
flocs = find_tvar (fm. sig, ftv)
331
368
if all (flocs[find (locs)])
332
369
push! (ftvs,ftv)
@@ -340,7 +377,7 @@ function isfitting(tmm::Method, fm::Method; verbose=false) # tm=trait-method, fm
340
377
# end
341
378
# g01(::Int, ::Int) = Int
342
379
# @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
344
381
# Check if the method definition of fm has the same
345
382
# leaftypes in the same location.
346
383
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
375
412
end
376
413
377
414
# 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 )
379
416
# Substitute `TestT` for a particular TypeVar `tv` in an argument `arg`.
380
417
#
381
418
# Example:
@@ -388,8 +425,59 @@ function subs_tvar{T<:_TestTvar}(tv::TypeVar, arg::DataType, TestT::Type{T})
388
425
return typ{pa... }
389
426
end
390
427
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
393
481
394
482
# find_tvar finds index of arguments in a function signature `sig` where a
395
483
# particular TypeVar `tv` features. Example:
0 commit comments