@@ -464,120 +464,6 @@ take into account field default values, and this change is necessary to make it
464
464
use them when the given ` p: Product ` has a smaller ` productArity ` than the current
465
465
` CaseClass ` implementation
466
466
467
- ### Abstract Methods
468
-
469
- Apart from ` final ` methods, ` @unroll ` also supports purely abstract methods. Consider
470
- the following example with a trait ` Unrolled ` and an implementation ` UnrolledObj ` :
471
-
472
- ``` scala
473
- trait Unrolled { // version 3
474
- def foo (s : String , n : Int = 1 , @ unroll b : Boolean = true , @ unroll l : Long = 0 ): String
475
- }
476
- ```
477
- ``` scala
478
- object UnrolledObj extends Unrolled { // version 3
479
- def foo (s : String , n : Int = 1 , @ unroll b : Boolean = true , @ unroll l : Long = 0 ) = s + n + b
480
- }
481
- ```
482
-
483
- This unrolls to:
484
- ``` scala
485
- trait Unrolled { // version 3
486
- def foo (s : String , n : Int = 1 , @ unroll b : Boolean = true , @ unroll l : Long = 0 ): String = foo(s, n, b)
487
- def foo (s : String , n : Int , b : Boolean ): String = foo(s, n)
488
- def foo (s : String , n : Int ): String
489
- }
490
- ```
491
- ``` scala
492
- object UnrolledObj extends Unrolled { // version 3
493
- def foo (s : String , n : Int = 1 , @ unroll b : Boolean = true , @ unroll l : Long = 0 ) = s + n + b + l
494
- def foo (s : String , n : Int , b : Boolean ) = foo(s, n, b, 0 )
495
- def foo (s : String , n : Int ) = foo(s, n, true )
496
- }
497
- ```
498
-
499
- Note that both the abstract methods from ` trait Unrolled ` and the concrete methods
500
- from ` object UnrolledObj ` generate forwarders when ` @unroll ` ed, but the forwarders
501
- are generated _ in opposite directions_ ! Unrolled concrete methods forward from longer
502
- parameter lists to shorter parameter lists, while unrolled abstract methods forward
503
- from shorter parameter lists to longer parameter lists. For example, we may have a
504
- version of ` object UnrolledObj ` that was compiled against an earlier version of ` trait Unrolled ` :
505
-
506
-
507
- ``` scala
508
- object UnrolledObj extends Unrolled { // version 2
509
- def foo (s : String , n : Int = 1 , @ unroll b : Boolean = true ) = s + n + b
510
- def foo (s : String , n : Int ) = foo(s, n, true )
511
- }
512
- ```
513
-
514
- But further downstream code calling ` .foo ` on ` UnrolledObj ` may expect any of the following signatures,
515
- depending on what version of ` Unrolled ` and ` UnrolledObj ` it was compiled against:
516
-
517
- ``` scala
518
- UnrolledObj .foo(String , Int )
519
- UnrolledObj .foo(String , Int , Boolean )
520
- UnrolledObj .foo(String , Int , Boolean , Long )
521
- ```
522
-
523
- Because such downstream code cannot know which version of ` Unrolled ` that ` UnrolledObj `
524
- was compiled against, we need to ensure all such calls find their way to the correct
525
- implementation of ` def foo ` , which may be at any of the above signatures. This "double
526
- forwarding" strategy ensures that regardless of _ which_ version of ` .foo ` gets called,
527
- it ends up eventually forwarding to the actual implementation of ` foo ` , with
528
- the correct combination of passed arguments and default arguments
529
-
530
- ``` scala
531
- UnrolledObj .foo(String , Int ) // forwards to UnrolledObj.foo(String, Int, Boolean)
532
- UnrolledObj .foo(String , Int , Boolean ) // actual implementation
533
- UnrolledObj .foo(String , Int , Boolean , Long ) // forwards to UnrolledObj.foo(String, Int, Boolean)
534
- ```
535
-
536
- As is the case for ` @unroll ` ed methods on ` trait ` s and ` class ` es, ` @unroll ` ed
537
- implementations of an abtract method must be final.
538
-
539
- #### Are Reverse Forwarders Really Necessary?
540
-
541
- This "double forwarding" strategy is not strictly necessary to support
542
- [ Backwards Compatibility] ( #backwards-compatibility ) : the "reverse" forwarders
543
- generated for abstract methods are only necessary when a downstream callsite
544
- of ` UnrolledObj.foo ` is compiled against a newer version of the original
545
- ` trait Unrolled ` than the ` object UnrolledObj ` was, as shown below:
546
-
547
- ``` scala
548
- trait Unrolled { // version 3
549
- def foo (s : String , n : Int = 1 , @ unroll b : Boolean = true , @ unroll l : Long = 0 ): String = foo(s, n, b)
550
- // generated
551
- def foo (s : String , n : Int , b : Boolean ): String = foo(s, n)
552
- def foo (s : String , n : Int ): String
553
- }
554
- ```
555
- ``` scala
556
- object UnrolledObj extends Unrolled { // version 2
557
- def foo (s : String , n : Int = 1 , @ unroll b : Boolean = true ) = s + n + b
558
- // generated
559
- def foo (s : String , n : Int ) = foo(s, n, true )
560
- }
561
- ```
562
- ``` scala
563
- // version 3
564
- UnrolledObj .foo(" hello" , 123 , true , 456L )
565
- ```
566
-
567
- If we did not have the reverse forwarder from ` foo(String, Int, Boolean, Long) ` to
568
- ` foo(String, Int, Boolean) ` , this call would fail at runtime with an ` AbstractMethodError ` .
569
- It also will get caught by MiMa as a ` ReversedMissingMethodProblem ` .
570
-
571
- This configuration of version is not allowed given our definition of backwards compatibility:
572
- that definition assumes that ` Unrolled ` must be of a greater or equal version than ` UnrolledObj ` ,
573
- which itself must be of a greater or equal version than the final call to ` UnrolledObj.foo ` . However,
574
- the reverse forwarders are needed to fulfill our requirement
575
- [ All Overrides Are Equivalent] ( #all-overrides-are-equivalent ) :
576
- looking at ` trait Unrolled // version 3 ` and ` object UnrolledObj // version 2 ` in isolation,
577
- we find that without the reverse forwarders the signature ` foo(String, Int, Boolean, Long) `
578
- is defined but not implemented. Such an un-implemented abstract method is something
579
- we want to avoid, even if our artifact version constraints mean it should technically
580
- never get called.
581
467
582
468
### Hiding Generated Forwarder Methods
583
469
@@ -811,6 +697,121 @@ evolution. It does so with the same Scala-level syntax and semantics, with the s
811
697
and limitations that common schema/API/protocol-evolution best-practices have in the broader
812
698
software engineering community.
813
699
700
+ ### Abstract Methods
701
+
702
+ Apart from ` final ` methods, ` @unroll ` also supports purely abstract methods. Consider
703
+ the following example with a trait ` Unrolled ` and an implementation ` UnrolledObj ` :
704
+
705
+ ``` scala
706
+ trait Unrolled { // version 3
707
+ def foo (s : String , n : Int = 1 , @ unroll b : Boolean = true , @ unroll l : Long = 0 ): String
708
+ }
709
+ ```
710
+ ``` scala
711
+ object UnrolledObj extends Unrolled { // version 3
712
+ def foo (s : String , n : Int = 1 , @ unroll b : Boolean = true , @ unroll l : Long = 0 ) = s + n + b
713
+ }
714
+ ```
715
+
716
+ This unrolls to:
717
+ ``` scala
718
+ trait Unrolled { // version 3
719
+ def foo (s : String , n : Int = 1 , @ unroll b : Boolean = true , @ unroll l : Long = 0 ): String = foo(s, n, b)
720
+ def foo (s : String , n : Int , b : Boolean ): String = foo(s, n)
721
+ def foo (s : String , n : Int ): String
722
+ }
723
+ ```
724
+ ``` scala
725
+ object UnrolledObj extends Unrolled { // version 3
726
+ def foo (s : String , n : Int = 1 , @ unroll b : Boolean = true , @ unroll l : Long = 0 ) = s + n + b + l
727
+ def foo (s : String , n : Int , b : Boolean ) = foo(s, n, b, 0 )
728
+ def foo (s : String , n : Int ) = foo(s, n, true )
729
+ }
730
+ ```
731
+
732
+ Note that both the abstract methods from ` trait Unrolled ` and the concrete methods
733
+ from ` object UnrolledObj ` generate forwarders when ` @unroll ` ed, but the forwarders
734
+ are generated _ in opposite directions_ ! Unrolled concrete methods forward from longer
735
+ parameter lists to shorter parameter lists, while unrolled abstract methods forward
736
+ from shorter parameter lists to longer parameter lists. For example, we may have a
737
+ version of ` object UnrolledObj ` that was compiled against an earlier version of ` trait Unrolled ` :
738
+
739
+
740
+ ``` scala
741
+ object UnrolledObj extends Unrolled { // version 2
742
+ def foo (s : String , n : Int = 1 , @ unroll b : Boolean = true ) = s + n + b
743
+ def foo (s : String , n : Int ) = foo(s, n, true )
744
+ }
745
+ ```
746
+
747
+ But further downstream code calling ` .foo ` on ` UnrolledObj ` may expect any of the following signatures,
748
+ depending on what version of ` Unrolled ` and ` UnrolledObj ` it was compiled against:
749
+
750
+ ``` scala
751
+ UnrolledObj .foo(String , Int )
752
+ UnrolledObj .foo(String , Int , Boolean )
753
+ UnrolledObj .foo(String , Int , Boolean , Long )
754
+ ```
755
+
756
+ Because such downstream code cannot know which version of ` Unrolled ` that ` UnrolledObj `
757
+ was compiled against, we need to ensure all such calls find their way to the correct
758
+ implementation of ` def foo ` , which may be at any of the above signatures. This "double
759
+ forwarding" strategy ensures that regardless of _ which_ version of ` .foo ` gets called,
760
+ it ends up eventually forwarding to the actual implementation of ` foo ` , with
761
+ the correct combination of passed arguments and default arguments
762
+
763
+ ``` scala
764
+ UnrolledObj .foo(String , Int ) // forwards to UnrolledObj.foo(String, Int, Boolean)
765
+ UnrolledObj .foo(String , Int , Boolean ) // actual implementation
766
+ UnrolledObj .foo(String , Int , Boolean , Long ) // forwards to UnrolledObj.foo(String, Int, Boolean)
767
+ ```
768
+
769
+ As is the case for ` @unroll ` ed methods on ` trait ` s and ` class ` es, ` @unroll ` ed
770
+ implementations of an abtract method must be final.
771
+
772
+ #### Are Reverse Forwarders Really Necessary?
773
+
774
+ This "double forwarding" strategy is not strictly necessary to support
775
+ [ Backwards Compatibility] ( #backwards-compatibility ) : the "reverse" forwarders
776
+ generated for abstract methods are only necessary when a downstream callsite
777
+ of ` UnrolledObj.foo ` is compiled against a newer version of the original
778
+ ` trait Unrolled ` than the ` object UnrolledObj ` was, as shown below:
779
+
780
+ ``` scala
781
+ trait Unrolled { // version 3
782
+ def foo (s : String , n : Int = 1 , @ unroll b : Boolean = true , @ unroll l : Long = 0 ): String = foo(s, n, b)
783
+ // generated
784
+ def foo (s : String , n : Int , b : Boolean ): String = foo(s, n)
785
+ def foo (s : String , n : Int ): String
786
+ }
787
+ ```
788
+ ``` scala
789
+ object UnrolledObj extends Unrolled { // version 2
790
+ def foo (s : String , n : Int = 1 , @ unroll b : Boolean = true ) = s + n + b
791
+ // generated
792
+ def foo (s : String , n : Int ) = foo(s, n, true )
793
+ }
794
+ ```
795
+ ``` scala
796
+ // version 3
797
+ UnrolledObj .foo(" hello" , 123 , true , 456L )
798
+ ```
799
+
800
+ If we did not have the reverse forwarder from ` foo(String, Int, Boolean, Long) ` to
801
+ ` foo(String, Int, Boolean) ` , this call would fail at runtime with an ` AbstractMethodError ` .
802
+ It also will get caught by MiMa as a ` ReversedMissingMethodProblem ` .
803
+
804
+ This configuration of version is not allowed given our definition of backwards compatibility:
805
+ that definition assumes that ` Unrolled ` must be of a greater or equal version than ` UnrolledObj ` ,
806
+ which itself must be of a greater or equal version than the final call to ` UnrolledObj.foo ` . However,
807
+ the reverse forwarders are needed to fulfill our requirement
808
+ [ All Overrides Are Equivalent] ( #all-overrides-are-equivalent ) :
809
+ looking at ` trait Unrolled // version 3 ` and ` object UnrolledObj // version 2 ` in isolation,
810
+ we find that without the reverse forwarders the signature ` foo(String, Int, Boolean, Long) `
811
+ is defined but not implemented. Such an un-implemented abstract method is something
812
+ we want to avoid, even if our artifact version constraints mean it should technically
813
+ never get called.
814
+
814
815
## Minor Alternatives:
815
816
816
817
0 commit comments