@@ -340,46 +340,55 @@ struct Threw <: ExecutionResult
340
340
source:: LineNumberNode
341
341
end
342
342
343
- function eval_test (evaluated:: Expr , quoted:: Expr , source:: LineNumberNode , negate:: Bool = false )
344
- evaled_args = evaluated. args
343
+ function eval_test_comparison (comparisons:: Tuple , quoted:: Expr , source:: LineNumberNode , negate:: Bool = false )
345
344
quoted_args = quoted. args
346
- n = length (evaled_args )
345
+ n = length (comparisons )
347
346
kw_suffix = " "
348
- if evaluated. head === :comparison
349
- args = evaled_args
350
- res = true
351
- i = 1
352
- while i < n
353
- a, op, b = args[i], args[i+ 1 ], args[i+ 2 ]
354
- if res
355
- res = op (a, b)
356
- end
357
- quoted_args[i] = a
358
- quoted_args[i+ 2 ] = b
359
- i += 2
360
- end
361
347
362
- elseif evaluated. head === :call
363
- op = evaled_args[1 ]
364
- kwargs = (evaled_args[2 ]:: Expr ). args # Keyword arguments from `Expr(:parameters, ...)`
365
- args = evaled_args[3 : n]
366
-
367
- res = op (args... ; kwargs... )
368
-
369
- # Create "Evaluated" expression which looks like the original call but has all of
370
- # the arguments evaluated
371
- func_sym = quoted_args[1 ]:: Union{Symbol,Expr}
372
- if isempty (kwargs)
373
- quoted = Expr (:call , func_sym, args... )
374
- elseif func_sym === :≈ && ! res
375
- quoted = Expr (:call , func_sym, args... )
376
- kw_suffix = " ($(join ([" $k =$v " for (k, v) in kwargs], " , " )) )"
377
- else
378
- kwargs_expr = Expr (:parameters , [Expr (:kw , k, v) for (k, v) in kwargs]. .. )
379
- quoted = Expr (:call , func_sym, kwargs_expr, args... )
348
+ res = true
349
+ i = 1
350
+ while i < n
351
+ a, op, b = comparisons[i], comparisons[i+ 1 ], comparisons[i+ 2 ]
352
+ if res
353
+ res = op (a, b)
380
354
end
355
+ quoted_args[i] = a
356
+ quoted_args[i+ 2 ] = b
357
+ i += 2
358
+ end
359
+
360
+ if negate
361
+ res = ! res
362
+ quoted = Expr (:call , :! , quoted)
363
+ end
364
+
365
+ Returned (res,
366
+ # stringify arguments in case of failure, for easy remote printing
367
+ res === true ? quoted : sprint (print, quoted, context= (:limit => true )) * kw_suffix,
368
+ source)
369
+ end
370
+
371
+ function eval_test_function (func, args, kwargs, quoted_func:: Union{Expr,Symbol} , source:: LineNumberNode , negate:: Bool = false )
372
+ res = func (args... ; kwargs... )
373
+
374
+ # Create "Evaluated" expression which looks like the original call but has all of
375
+ # the arguments evaluated
376
+ kw_suffix = " "
377
+ if quoted_func === :≈ && ! res
378
+ kw_suffix = " ($(join ([" $k =$v " for (k, v) in kwargs], " , " )) )"
379
+ quoted_args = args
380
+ elseif isempty (kwargs)
381
+ quoted_args = args
381
382
else
382
- throw (ArgumentError (" Unhandled expression type: $(evaluated. head) " ))
383
+ kwargs_expr = Expr (:parameters , [Expr (:kw , k, v) for (k, v) in kwargs]. .. )
384
+ quoted_args = [kwargs_expr, args... ]
385
+ end
386
+
387
+ # Properly render broadcast function call syntax, e.g. `(==).(1, 2)` or `Base.:(==).(1, 2)`.
388
+ quoted = if isa (quoted_func, Expr) && quoted_func. head === :. && length (quoted_func. args) == 1
389
+ Expr (:., quoted_func. args[1 ], Expr (:tuple , quoted_args... ))
390
+ else
391
+ Expr (:call , quoted_func, quoted_args... )
383
392
end
384
393
385
394
if negate
@@ -576,14 +585,90 @@ macro test_skip(ex, kws...)
576
585
return :(record (get_testset (), $ testres))
577
586
end
578
587
579
- function _can_escape_call (@nospecialize ex)
580
- ex. head === :call || return false
588
+ function _should_escape_call (@nospecialize ex)
589
+ isa (ex, Expr) || return false
590
+
591
+ args = if ex. head === :call
592
+ ex. args[2 : end ]
593
+ elseif ex. head === :. && length (ex. args) == 2 && isa (ex. args[2 ], Expr) && ex. args[2 ]. head === :tuple
594
+ # Support for broadcasted function calls (e.g. `(==).(1, 2)`)
595
+ ex. args[2 ]. args
596
+ else
597
+ # Expression is not a function call
598
+ return false
599
+ end
600
+
601
+ # Avoid further processing on calls without any arguments
602
+ return length (args) > 0
603
+ end
604
+
605
+ # Escapes all of the positional arguments and keywords of a function such that we can call
606
+ # the function at runtime.
607
+ function _escape_call (@nospecialize ex)
608
+ if isa (ex, Expr) && ex. head === :call
609
+ # Update broadcast comparison calls to the function call syntax
610
+ # (e.g. `1 .== 1` becomes `(==).(1, 1)`)
611
+ func_str = string (ex. args[1 ])
612
+ escaped_func = if first (func_str) == ' .'
613
+ esc (Expr (:., Symbol (func_str[2 : end ])))
614
+ else
615
+ esc (ex. args[1 ])
616
+ end
617
+ quoted_func = QuoteNode (ex. args[1 ])
618
+ args = ex. args[2 : end ]
619
+ elseif isa (ex, Expr) && ex. head === :. && length (ex. args) == 2 && isa (ex. args[2 ], Expr) && ex. args[2 ]. head === :tuple
620
+ # Support for broadcasted function calls (e.g. `(==).(1, 2)`)
621
+ escaped_func = if isa (ex. args[1 ], Expr) && ex. args[1 ]. head == :.
622
+ Expr (:call , Expr (:., :Broadcast , QuoteNode (:BroadcastFunction )), esc (ex. args[1 ]))
623
+ else
624
+ Expr (:., esc (ex. args[1 ]))
625
+ end
626
+ quoted_func = QuoteNode (Expr (:., ex. args[1 ]))
627
+ args = ex. args[2 ]. args
628
+ else
629
+ throw (ArgumentError (" $ex is not a call expression" ))
630
+ end
631
+
632
+ escaped_args = []
633
+ escaped_kwargs = []
581
634
582
- # Broadcasted functions are not currently supported
583
- first (string (ex. args[1 ])) != ' .' || return false
635
+ # Positional arguments and keywords that occur before `;`. Note that the keywords are
636
+ # being revised into a form we can splat.
637
+ for a in args
638
+ if isa (a, Expr) && a. head === :parameters
639
+ continue
640
+ elseif isa (a, Expr) && a. head === :kw
641
+ # Keywords that occur before `;`. Note that the keywords are being revised into
642
+ # a form we can splat.
643
+ push! (escaped_kwargs, Expr (:call , :(=> ), QuoteNode (a. args[1 ]), esc (a. args[2 ])))
644
+ elseif isa (a, Expr) && a. head === :...
645
+ push! (escaped_args, Expr (:... , esc (a. args[1 ])))
646
+ else
647
+ push! (escaped_args, esc (a))
648
+ end
649
+ end
584
650
585
- # At least one positional argument or keyword
586
- return length (ex. args) > 1
651
+ # Keywords that occur after ';'
652
+ if length (args) > 0 && isa (args[1 ], Expr) && args[1 ]. head === :parameters
653
+ for kw in args[1 ]. args
654
+ if isa (kw, Expr) && kw. head === :kw
655
+ push! (escaped_kwargs, Expr (:call , :(=> ), QuoteNode (kw. args[1 ]), esc (kw. args[2 ])))
656
+ elseif isa (kw, Expr) && kw. head === :...
657
+ push! (escaped_kwargs, Expr (:... , esc (kw. args[1 ])))
658
+ elseif isa (kw, Expr) && kw. head === :.
659
+ push! (escaped_kwargs, Expr (:call , :(=> ), QuoteNode (kw. args[2 ]. value), esc (Expr (:., kw. args[1 ], QuoteNode (kw. args[2 ]. value)))))
660
+ elseif isa (kw, Symbol)
661
+ push! (escaped_kwargs, Expr (:call , :(=> ), QuoteNode (kw), esc (kw)))
662
+ end
663
+ end
664
+ end
665
+
666
+ return (;
667
+ func= escaped_func,
668
+ args= escaped_args,
669
+ kwargs= escaped_kwargs,
670
+ quoted_func,
671
+ )
587
672
end
588
673
589
674
# An internal function, called by the code generated by the @test
@@ -613,60 +698,22 @@ function get_test_result(ex, source)
613
698
ex = Expr (:comparison , ex. args[1 ], ex. head, ex. args[2 ])
614
699
end
615
700
if isa (ex, Expr) && ex. head === :comparison
616
- # pass all terms of the comparison to `eval_comparison `, as an Expr
701
+ # pass all terms of the comparison to `eval_test_comparison `, as a tuple
617
702
escaped_terms = [esc (arg) for arg in ex. args]
618
703
quoted_terms = [QuoteNode (arg) for arg in ex. args]
619
- testret = :(eval_test (
620
- Expr ( :comparison , $ (escaped_terms... )),
704
+ testret = :(eval_test_comparison (
705
+ ( $ (escaped_terms... ), ),
621
706
Expr (:comparison , $ (quoted_terms... )),
622
707
$ (QuoteNode (source)),
623
708
$ negate,
624
709
))
625
- elseif isa (ex, Expr) && _can_escape_call (ex)
626
- escaped_func = esc (ex. args[1 ])
627
- quoted_func = QuoteNode (ex. args[1 ])
628
-
629
- escaped_args = []
630
- escaped_kwargs = []
631
-
632
- # Keywords that occur before `;`. Note that the keywords are being revised into
633
- # a form we can splat.
634
- for a in ex. args[2 : end ]
635
- if isa (a, Expr) && a. head === :kw
636
- push! (escaped_kwargs, Expr (:call , :(=> ), QuoteNode (a. args[1 ]), esc (a. args[2 ])))
637
- end
638
- end
639
-
640
- # Keywords that occur after ';'
641
- parameters_expr = ex. args[2 ]
642
- if isa (parameters_expr, Expr) && parameters_expr. head === :parameters
643
- for a in parameters_expr. args
644
- if isa (a, Expr) && a. head === :kw
645
- push! (escaped_kwargs, Expr (:call , :(=> ), QuoteNode (a. args[1 ]), esc (a. args[2 ])))
646
- elseif isa (a, Expr) && a. head === :...
647
- push! (escaped_kwargs, Expr (:... , esc (a. args[1 ])))
648
- elseif isa (a, Expr) && a. head === :.
649
- push! (escaped_kwargs, Expr (:call , :(=> ), QuoteNode (a. args[2 ]. value), esc (Expr (:., a. args[1 ], QuoteNode (a. args[2 ]. value)))))
650
- elseif isa (a, Symbol)
651
- push! (escaped_kwargs, Expr (:call , :(=> ), QuoteNode (a), esc (a)))
652
- end
653
- end
654
- end
655
-
656
- # Positional arguments
657
- for a in ex. args[2 : end ]
658
- isa (a, Expr) && a. head in (:kw , :parameters ) && continue
659
-
660
- if isa (a, Expr) && a. head === :...
661
- push! (escaped_args, Expr (:... , esc (a. args[1 ])))
662
- else
663
- push! (escaped_args, esc (a))
664
- end
665
- end
666
-
667
- testret = :(eval_test (
668
- Expr (:call , $ escaped_func, Expr (:parameters , $ (escaped_kwargs... )), $ (escaped_args... )),
669
- Expr (:call , $ quoted_func),
710
+ elseif _should_escape_call (ex)
711
+ call = _escape_call (ex)
712
+ testret = :(eval_test_function (
713
+ $ (call. func),
714
+ ($ (call. args... ),),
715
+ ($ (call. kwargs... ),),
716
+ $ (call. quoted_func),
670
717
$ (QuoteNode (source)),
671
718
$ negate,
672
719
))
0 commit comments