-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
Copy pathErasure.scala
1111 lines (1002 loc) · 49.7 KB
/
Erasure.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package dotty.tools
package dotc
package transform
import ast.Trees
import core.Phases.*
import core.DenotTransformers.*
import core.Denotations.*
import core.SymDenotations.*
import core.Symbols.*
import core.Contexts.*
import core.Types.*
import core.Names.*
import core.StdNames.*
import core.NameOps.*
import core.Periods.currentStablePeriod
import core.NameKinds.{AdaptedClosureName, BodyRetainerName, DirectMethName}
import core.Scopes.newScopeWith
import core.Decorators.*
import core.Constants.*
import core.Definitions.*
import core.Annotations.BodyAnnotation
import typer.NoChecking
import inlines.Inlines
import typer.ProtoTypes.*
import typer.ErrorReporting.errorTree
import typer.Checking.checkValue
import core.TypeErasure.*
import core.Decorators.*
import dotty.tools.dotc.ast.{tpd, untpd}
import ast.TreeTypeMap
import dotty.tools.dotc.core.{Constants, Flags}
import ValueClasses.*
import ContextFunctionResults.*
import ExplicitOuter.*
import core.Mode
import util.Property
import reporting.*
import scala.annotation.tailrec
class Erasure extends Phase with DenotTransformer {
override def phaseName: String = Erasure.name
override def description: String = Erasure.description
/** List of names of phases that should precede this phase */
override def runsAfter: Set[String] = Set(InterceptedMethods.name, ElimRepeated.name)
override def changesMembers: Boolean = true // the phase adds bridges
override def changesParents: Boolean = true // the phase drops Any
def transform(ref: SingleDenotation)(using Context): SingleDenotation = ref match {
case ref: SymDenotation =>
def isCompacted(symd: SymDenotation) =
symd.isAnonymousFunction && {
atPhase(ctx.phase.next)(symd.info) match {
case MethodType(nme.ALLARGS :: Nil) => true
case _ => false
}
}
def erasedName =
if ref.is(Flags.Method)
&& contextResultsAreErased(ref.symbol)
&& (ref.owner.is(Flags.Trait) || ref.symbol.allOverriddenSymbols.hasNext)
then
// Add a `$direct` to prevent this method from having the same signature
// as a method it overrides. We need a bridge between the
// two methods, so they are not allowed to already override after erasure.
DirectMethName(ref.targetName.asTermName)
else
ref.targetName
assert(ctx.phase == this, s"transforming $ref at ${ctx.phase}")
if (ref.symbol eq defn.ObjectClass) {
// After erasure, all former Any members are now Object members
val ClassInfo(pre, _, ps, decls, selfInfo) = ref.info: @unchecked
val extendedScope = decls.cloneScope
for decl <- defn.AnyClass.classInfo.decls do
if !decl.isConstructor then extendedScope.enter(decl)
ref.copySymDenotation(
info = transformInfo(ref.symbol,
ClassInfo(pre, defn.ObjectClass, ps, extendedScope, selfInfo))
)
}
else {
val oldSymbol = ref.symbol
val newSymbol =
if ((oldSymbol.owner eq defn.AnyClass) && oldSymbol.isConstructor) then
//assert(false)
defn.ObjectClass.primaryConstructor
else oldSymbol
val oldOwner = ref.owner
val newOwner = if oldOwner == defn.AnyClass then defn.ObjectClass else oldOwner
val oldName = ref.name
val newName = erasedName
val oldInfo = ref.info
var newInfo = transformInfo(oldSymbol, oldInfo)
val oldFlags = ref.flags
var newFlags =
if oldSymbol.is(Flags.TermParam) && isCompacted(oldSymbol.owner.denot) then oldFlags &~ Flags.Param
else oldFlags
val oldAnnotations = ref.annotations
var newAnnotations = oldAnnotations
if oldSymbol.isRetainedInlineMethod then
newFlags = newFlags &~ Flags.Inline
newAnnotations = newAnnotations.filterConserve(!_.isInstanceOf[BodyAnnotation])
oldSymbol match
case cls: ClassSymbol if cls.is(Flags.Erased) =>
newFlags = newFlags | Flags.Trait | Flags.JavaInterface
newAnnotations = Nil
newInfo = erasedClassInfo(cls)
case _ =>
// TODO: define derivedSymDenotation?
if ref.is(Flags.PackageClass)
|| !ref.isClass // non-package classes are always copied since their base types change
&& (oldSymbol eq newSymbol)
&& (oldOwner eq newOwner)
&& (oldName eq newName)
&& (oldInfo eq newInfo)
&& (oldFlags == newFlags)
&& (oldAnnotations eq newAnnotations)
then
ref
else
ref.copySymDenotation(
symbol = newSymbol,
owner = newOwner,
name = newName,
initFlags = newFlags,
info = newInfo,
annotations = newAnnotations)
}
case ref: JointRefDenotation =>
new UniqueRefDenotation(
ref.symbol, transformInfo(ref.symbol, ref.symbol.info), currentStablePeriod, ref.prefix)
case _ =>
ref.derivedSingleDenotation(ref.symbol, transformInfo(ref.symbol, ref.symbol.info))
}
private val eraser = new Erasure.Typer(this)
def run(using Context): Unit = {
val unit = ctx.compilationUnit
unit.tpdTree = eraser.typedExpr(unit.tpdTree)(using ctx.fresh.setTyper(eraser).setPhase(this.next))
}
/** erased classes get erased to empty traits with Object as parent and an empty constructor */
private def erasedClassInfo(cls: ClassSymbol)(using Context) =
cls.classInfo.derivedClassInfo(
declaredParents = defn.ObjectClass.typeRef :: Nil,
decls = newScopeWith(newConstructor(cls, Flags.EmptyFlags, Nil, Nil)))
override def checkPostCondition(tree: tpd.Tree)(using Context): Unit = {
assertErased(tree)
tree match {
case _: tpd.Import => assert(false, i"illegal tree: $tree")
case res: tpd.This =>
assert(!ExplicitOuter.referencesOuter(ctx.owner.lexicallyEnclosingClass, res),
i"Reference to $res from ${ctx.owner.showLocated}")
case ret: tpd.Return =>
// checked only after erasure, as checking before erasure is complicated
// due presence of type params in returned types
val from = if (ret.from.isEmpty) ctx.owner.enclosingMethod else ret.from.symbol
val rType = from.info.finalResultType
assert(ret.expr.tpe <:< rType,
i"Returned value:${ret.expr} does not conform to result type(${ret.expr.tpe.widen} of method $from")
case _ =>
}
}
/** Assert that tree type and its widened underlying type are erased.
* Also assert that term refs have fixed symbols (so we are sure
* they need not be reloaded using member; this would likely fail as signatures
* may change after erasure).
*/
def assertErased(tree: tpd.Tree)(using Context): Unit = {
assertErased(tree.typeOpt, tree)
if (!defn.isPolymorphicAfterErasure(tree.symbol))
assertErased(tree.typeOpt.widen, tree)
if (ctx.mode.isExpr)
tree.tpe match {
case ref: TermRef =>
assert(ref.denot.isInstanceOf[SymDenotation] ||
ref.denot.isInstanceOf[UniqueRefDenotation],
i"non-sym type $ref of class ${ref.getClass} with denot of class ${ref.denot.getClass} of $tree")
case _ =>
}
}
def assertErased(tp: Type, tree: tpd.Tree = tpd.EmptyTree)(using Context): Unit = {
def isAllowed(cls: Symbol, sourceName: String) =
tp.typeSymbol == cls && ctx.compilationUnit.source.file.name == sourceName
assert(
isErasedType(tp)
|| isAllowed(defn.ArrayClass, "Array.scala")
|| isAllowed(defn.TupleClass, "Tuple.scala")
|| isAllowed(defn.NonEmptyTupleClass, "Tuple.scala")
|| isAllowed(defn.PairClass, "Tuple.scala")
|| isAllowed(defn.PureClass, "Pure.scala"),
i"The type $tp - ${tp.toString} of class ${tp.getClass} of tree $tree : ${tree.tpe} / ${tree.getClass} is illegal after erasure, phase = ${ctx.phase.prev}")
}
}
object Erasure {
import tpd.*
import TypeTestsCasts.*
val name: String = "erasure"
val description: String = "rewrite types to JVM model"
/** An attachment on Apply nodes indicating that multiple arguments
* are passed in a single array. This occurs only if the function
* implements a FunctionXXL apply.
*/
private val BunchedArgs = new Property.Key[Unit]
/** An Apply node which might still be missing some arguments */
def partialApply(fn: Tree, args: List[Tree])(using Context): Tree =
untpd.Apply(fn, args.toList)
.withType(applyResultType(fn.tpe.widen.asInstanceOf[MethodType], args))
/** The type of an Apply node which might still be missing some arguments */
private def applyResultType(mt: MethodType, args: List[Tree])(using Context): Type =
if mt.paramInfos.length <= args.length then mt.resultType
else MethodType(mt.paramInfos.drop(args.length), mt.resultType)
/** The method type corresponding to `mt`, except that bunched parameters
* are expanded. The expansion is determined using the original function
* tree `origFun` which has a type that is not yet converted to a type
* with bunched arguments.
*/
def expandedMethodType(mt: MethodType, origFun: Tree)(using Context): MethodType =
mt.paramInfos match
case JavaArrayType(elemType) :: Nil if elemType.isRef(defn.ObjectClass) =>
val origArity = totalParamCount(origFun.symbol)(using preErasureCtx)
if origArity > MaxImplementedFunctionArity then
MethodType(List.fill(origArity)(defn.ObjectType), mt.resultType)
else mt
case _ => mt
object Boxing:
def isUnbox(sym: Symbol)(using Context): Boolean =
sym.name == nme.unbox && sym.owner.linkedClass.isPrimitiveValueClass
def isBox(sym: Symbol)(using Context): Boolean =
sym.name == nme.box && sym.owner.linkedClass.isPrimitiveValueClass
def boxMethod(cls: ClassSymbol)(using Context): Symbol =
cls.linkedClass.info.member(nme.box).symbol
def unboxMethod(cls: ClassSymbol)(using Context): Symbol =
cls.linkedClass.info.member(nme.unbox).symbol
/** Isf this tree is an unbox operation which can be safely removed
* when enclosed in a box, the unboxed argument, otherwise EmptyTree.
* Note that one can't always remove a Box(Unbox(x)) combination because the
* process of unboxing x may lead to throwing an exception.
* This is important for specialization: calls to the super constructor should not box/unbox specialized
* fields (see TupleX). (ID)
*/
private def safelyRemovableUnboxArg(tree: Tree)(using Context): Tree = tree match {
case Apply(fn, arg :: Nil)
if isUnbox(fn.symbol) && defn.ScalaBoxedClasses().contains(arg.tpe.typeSymbol) =>
arg
case _ =>
EmptyTree
}
def constant(tree: Tree, const: Tree)(using Context): Tree =
(if (isPureExpr(tree)) const else Block(tree :: Nil, const)).withSpan(tree.span)
final def box(tree: Tree, target: => String = "")(using Context): Tree = trace(i"boxing ${tree.showSummary()}: ${tree.tpe} into $target") {
tree.tpe.widen match {
case ErasedValueType(tycon, _) =>
New(tycon, cast(tree, underlyingOfValueClass(tycon.symbol.asClass)) :: Nil) // todo: use adaptToType?
case tp =>
val cls = tp.classSymbol
if (cls eq defn.UnitClass) constant(tree, ref(defn.BoxedUnit_UNIT))
else if (cls eq defn.NothingClass) tree // a non-terminating expression doesn't need boxing
else {
assert(cls ne defn.ArrayClass)
val arg = safelyRemovableUnboxArg(tree)
if (arg.isEmpty) ref(boxMethod(cls.asClass)).appliedTo(tree)
else {
report.log(s"boxing an unbox: ${tree.symbol} -> ${arg.tpe}")
arg
}
}
}
}
def unbox(tree: Tree, pt: Type)(using Context): Tree = trace(i"unboxing ${tree.showSummary()}: ${tree.tpe} as a $pt") {
pt match {
case ErasedValueType(tycon, underlying) =>
def unboxedTree(t: Tree) =
adaptToType(t, tycon)
.select(valueClassUnbox(tycon.symbol.asClass))
.appliedToNone
// Null unboxing needs to be treated separately since we cannot call a method on null.
// "Unboxing" null to underlying is equivalent to doing null.asInstanceOf[underlying]
// See tests/pos/valueclasses/nullAsInstanceOfVC.scala for cases where this might happen.
val tree1 =
if (tree.tpe isRef defn.NullClass)
adaptToType(tree, underlying)
else if (!(tree.tpe <:< tycon)) {
assert(!(tree.tpe.typeSymbol.isPrimitiveValueClass))
val nullTree = nullLiteral
val unboxedNull = adaptToType(nullTree, underlying)
evalOnce(tree) { t =>
If(t.select(defn.Object_eq).appliedTo(nullTree),
unboxedNull,
unboxedTree(t))
}
}
else unboxedTree(tree)
cast(tree1, pt)
case _ =>
val cls = pt.classSymbol
if (cls eq defn.UnitClass) constant(tree, unitLiteral)
else {
assert(cls ne defn.ArrayClass)
ref(unboxMethod(cls.asClass)).appliedTo(tree)
}
}
}
/** Generate a synthetic cast operation from tree.tpe to pt.
* Does not do any boxing/unboxing (this is handled upstream).
* Casts from and to ErasedValueType are special, see the explanation
* in ExtensionMethods#transform.
*/
def cast(tree: Tree, pt: Type)(using Context): Tree = trace(i"cast ${tree.tpe.widen} --> $pt", show = true) {
def wrap(tycon: TypeRef) =
ref(u2evt(tycon.typeSymbol.asClass)).appliedTo(tree)
def unwrap(tycon: TypeRef) =
ref(evt2u(tycon.typeSymbol.asClass)).appliedTo(tree)
assert(!pt.isInstanceOf[SingletonType], pt)
if (pt isRef defn.UnitClass) unbox(tree, pt)
else (tree.tpe.widen, pt) match {
// Convert primitive arrays into reference arrays, this path is only
// needed to handle repeated arguments, see
// `Definitions#FromJavaObjectSymbol` and `ElimRepeated#adaptToArray`.
case (JavaArrayType(treeElem), JavaArrayType(ptElem))
if treeElem.widen.isPrimitiveValueType && !ptElem.isPrimitiveValueType =>
cast(ref(defn.ScalaRuntime_toObjectArray).appliedTo(tree), pt)
// When casting between two EVTs, we need to check which one underlies the other to determine
// whether u2evt or evt2u should be used.
case (tp1 @ ErasedValueType(tycon1, underlying1), tp2 @ ErasedValueType(tycon2, underlying2)) =>
if (tp1 <:< underlying2)
// Cast EVT(tycon1, underlying1) to EVT(tycon2, EVT(tycon1, underlying1))
wrap(tycon2)
else {
assert(underlying1 <:< tp2, i"Non-sensical cast between unrelated types $tp1 and $tp2")
// Cast EVT(tycon1, EVT(tycon2, underlying2)) to EVT(tycon2, underlying2)
unwrap(tycon1)
}
// When only one type is an EVT then we already know that the other one is the underlying
case (_, ErasedValueType(tycon2, _)) =>
wrap(tycon2)
case (ErasedValueType(tycon1, _), _) =>
unwrap(tycon1)
case _ =>
if (pt.isPrimitiveValueType)
primitiveConversion(tree, pt.classSymbol)
else
tree.asInstance(pt)
}
}
/** Adaptation of an expression `e` to an expected type `PT`, applying the following
* rewritings exhaustively as long as the type of `e` is not a subtype of `PT`.
*
* e -> e() if `e` appears not as the function part of an application
* e -> box(e) if `e` is of erased value type
* e -> unbox(e, PT) otherwise, if `PT` is an erased value type
* e -> box(e) if `e` is of primitive type and `PT` is not a primitive type
* e -> unbox(e, PT) if `PT` is a primitive type and `e` is not of primitive type
* e -> cast(e, PT) otherwise
*/
def adaptToType(tree: Tree, pt: Type)(using Context): Tree = pt match
case _: FunProto | AnyFunctionProto => tree
case _ => tree.tpe.widen match
case mt: MethodType if tree.isTerm =>
assert(mt.paramInfos.isEmpty, i"bad adapt for $tree: $mt")
adaptToType(tree.appliedToNone, pt)
case tpw =>
if (pt.isInstanceOf[ProtoType] || tree.tpe <:< pt)
tree
else if (tpw.isErasedValueType)
if (pt.isErasedValueType) then
tree.asInstance(pt)
else
adaptToType(box(tree), pt)
else if (pt.isErasedValueType)
adaptToType(unbox(tree, pt), pt)
else if (tpw.isPrimitiveValueType && !pt.isPrimitiveValueType)
adaptToType(box(tree), pt)
else if (pt.isPrimitiveValueType && !tpw.isPrimitiveValueType)
adaptToType(unbox(tree, pt), pt)
else
cast(tree, pt)
end adaptToType
/** The following code:
*
* val f: Function1[Int, Any] = x => ...
*
* results in the creation of a closure and an implementation method in the typer:
*
* def $anonfun(x: Int): Any = ...
* val f: Function1[Int, Any] = closure($anonfun)
*
* Notice that `$anonfun` takes a primitive as argument, but the SAM (Single Abstract Method)
* of `Function1` after erasure is:
*
* def apply(x: Object): Object
*
* which takes a reference as argument. Hence, some form of adaptation is
* required. The most reliable way to do this adaptation is to replace the
* closure implementation method by a bridge method that forwards to the
* original method with appropriate boxing/unboxing. For our example above,
* this would be:
*
* def $anonfun$adapted(x: Object): Object = $anonfun(BoxesRunTime.unboxToInt(x))
* val f: Function1 = closure($anonfun$adapted)
*
* But in some situations we can avoid generating this bridge, either
* because the runtime can perform auto-adaptation, or because we can
* replace the closure functional interface by a specialized sub-interface,
* see comments in this method for details.
*
* See test cases lambda-*.scala and t8017/ for concrete examples.
*/
def adaptClosure(tree: tpd.Closure)(using Context): Tree =
val Closure(env, meth, tpt) = tree
assert(env.isEmpty, tree)
// The type of the lambda expression
val lambdaType = tree.tpe
// The interface containing the SAM that this closure should implement
val functionalInterface = tpt.tpe
// A lack of an explicit functional interface means we're implementing a scala.FunctionN
val isFunction = !functionalInterface.exists
// The actual type of the implementation method
val implType = meth.tpe.widen.asInstanceOf[MethodType]
val implParamTypes = implType.paramInfos
val implResultType = implType.resultType
val implReturnsUnit = implResultType.classSymbol eq defn.UnitClass
// The SAM that this closure should implement.
// At this point it should be already guaranteed that there's only one method to implement
val Seq(sam: MethodType) = lambdaType.possibleSamMethods.map(_.info): @unchecked
val samParamTypes = sam.paramInfos
val samResultType = sam.resultType
/** Can the implementation parameter type `tp` be auto-adapted to a different
* parameter type in the SAM?
*
* For derived value classes, we always need to do the bridging manually.
* For primitives, we cannot rely on auto-adaptation on the JVM because
* the Scala spec requires null to be "unboxed" to the default value of
* the value class, but the adaptation performed by LambdaMetaFactory
* will throw a `NullPointerException` instead. See `lambda-null.scala`
* for test cases.
*
* @see [LambdaMetaFactory](https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html)
*/
def autoAdaptedParam(tp: Type) =
!tp.isErasedValueType && !tp.isPrimitiveValueType
/** Can the implementation result type be auto-adapted to a different result
* type in the SAM?
*
* For derived value classes, it's the same story as for parameters.
* For non-Unit primitives, we can actually rely on the `LambdaMetaFactory`
* adaptation, because it only needs to box, not unbox, so no special
* handling of null is required.
*/
def autoAdaptedResult =
!implResultType.isErasedValueType && !implReturnsUnit
def sameClass(tp1: Type, tp2: Type) = tp1.classSymbol == tp2.classSymbol
val paramAdaptationNeeded =
implParamTypes.lazyZip(samParamTypes).exists((implType, samType) =>
!sameClass(implType, samType) && !autoAdaptedParam(implType))
val resultAdaptationNeeded =
!sameClass(implResultType, samResultType) && !autoAdaptedResult
if paramAdaptationNeeded || resultAdaptationNeeded then
// Instead of instantiating `scala.FunctionN`, see if we can instantiate
// a specialized sub-interface where the SAM type matches the
// implementation method type, thus avoiding the need for bridging.
// This optimization is skipped when using Scala.js because its backend
// does not support closures using custom functional interfaces.
if isFunction && !ctx.settings.scalajs.value then
val arity = implParamTypes.length
val specializedFunctionalInterface =
if !implType.hasErasedParams && defn.isSpecializableFunctionSAM(implParamTypes, implResultType) then
// Using these subclasses is critical to avoid boxing since their
// SAM is a specialized method `apply$mc*$sp` whose default
// implementation in FunctionN boxes.
tpnme.JFunctionPrefix(arity).specializedFunction(implResultType, implParamTypes)
else if !paramAdaptationNeeded && implReturnsUnit then
// Here, there is no actual boxing to avoid so we could get by
// without JProcedureN, but Unit-returning functions are very
// common so it seems worth it to not generate bridges for them.
tpnme.JProcedure(arity)
else
EmptyTypeName
if !specializedFunctionalInterface.isEmpty then
return cpy.Closure(tree)(tpt = TypeTree(requiredClass(specializedFunctionalInterface).typeRef))
// Otherwise, generate a new closure implemented with a bridge.
val bridgeType =
if paramAdaptationNeeded then
if resultAdaptationNeeded then
sam
else
implType.derivedLambdaType(paramInfos = samParamTypes)
else
implType.derivedLambdaType(resType = samResultType)
val bridge = newSymbol(ctx.owner, AdaptedClosureName(meth.symbol.name.asTermName), Flags.Synthetic | Flags.Method | Flags.Bridge, bridgeType)
Closure(bridge, bridgeParamss =>
inContext(ctx.withOwner(bridge)) {
val List(bridgeParams) = bridgeParamss
assert(ctx.typer.isInstanceOf[Erasure.Typer])
val rhs = Apply(meth, bridgeParams.lazyZip(implParamTypes).map(ctx.typer.adapt(_, _)))
ctx.typer.adapt(rhs, bridgeType.resultType)
},
targetType = functionalInterface).withSpan(tree.span)
else
tree
end adaptClosure
end Boxing
class Typer(erasurePhase: DenotTransformer) extends typer.ReTyper with NoChecking {
import Boxing.*
def isErased(tree: Tree)(using Context): Boolean = tree match {
case TypeApply(Select(qual, _), _) if tree.symbol == defn.Any_typeCast =>
isErased(qual)
case _ => tree.symbol.isEffectivelyErased
}
/** Check that Java statics and packages can only be used in selections.
*/
private def checkNotErased(tree: Tree)(using Context): tree.type =
if !ctx.mode.is(Mode.Type) then
if isErased(tree) then
val msg =
if tree.symbol.is(Flags.Inline) then
em"""${tree.symbol} is declared as `inline`, but was not inlined
|
|Try increasing `-Xmax-inlines` above ${ctx.settings.XmaxInlines.value}"""
else
em"${tree.symbol} is declared as `erased`, but is in fact used"
report.error(msg, tree.srcPos)
tree.symbol.getAnnotation(defn.CompileTimeOnlyAnnot) match
case Some(annot) =>
val message = annot.argumentConstant(0) match
case Some(c) =>
val addendum = tree match
case tree: RefTree
if tree.symbol == defn.Compiletime_deferred && tree.name != nme.deferred =>
i".\nNote that `deferred` can only be used under its own name when implementing a given in a trait; `${tree.name}` is not accepted."
case _ =>
""
(c.stringValue ++ addendum).toMessage
case _ =>
em"""Reference to ${tree.symbol.showLocated} should not have survived,
|it should have been processed and eliminated during expansion of an enclosing macro or term erasure."""
report.error(message, tree.srcPos)
case _ => // OK
checkNotErasedClass(tree)
end checkNotErased
private def checkNotErasedClass(tp: Type, tree: untpd.Tree)(using Context): Unit = tp match
case JavaArrayType(et) =>
checkNotErasedClass(et, tree)
case _ =>
if tp.isErasedClass then
val (kind, tree1) = tree match
case tree: untpd.ValOrDefDef => ("definition", tree.tpt)
case tree: untpd.DefTree => ("definition", tree)
case _ => ("expression", tree)
report.error(em"illegal reference to erased ${tp.typeSymbol} in $kind that is not itself erased", tree1.srcPos)
private def checkNotErasedClass(tree: Tree)(using Context): tree.type =
checkNotErasedClass(tree.tpe.widen.finalResultType, tree)
tree
def erasedDef(sym: Symbol)(using Context): Tree =
if sym.isClass then
// We cannot simply drop erased classes, since then they would not generate classfiles
// and would not be visible under separate compilation. So we transform them to
// empty interfaces instead.
tpd.ClassDef(sym.asClass, DefDef(sym.primaryConstructor.asTerm), Nil)
else
if sym.owner.isClass then sym.dropAfter(erasurePhase)
tpd.EmptyTree
def erasedType(tree: untpd.Tree)(using Context): Type = {
val tp = tree.typeOpt
if (tree.isTerm) erasedRef(tp) else valueErasure(tp)
}
override def promote(tree: untpd.Tree)(using Context): tree.ThisTree[Type] = {
assert(tree.hasType)
val erasedTp = erasedType(tree)
report.log(s"promoting ${tree.show}: ${erasedTp.showWithUnderlying()}")
tree.withType(erasedTp)
}
/** When erasing most TypeTrees we should not semi-erase value types.
* This is not the case for [[DefDef#tpt]], [[ValDef#tpt]] and [[Typed#tpt]], they
* are handled separately by [[typedDefDef]], [[typedValDef]] and [[typedTyped]].
*/
override def typedTypeTree(tree: untpd.TypeTree, pt: Type)(using Context): TypeTree =
checkNotErasedClass(tree.withType(erasure(tree.typeOpt)))
/** This override is only needed to semi-erase type ascriptions */
override def typedTyped(tree: untpd.Typed, pt: Type)(using Context): Tree =
val Typed(expr, tpt) = tree
if tpt.typeOpt.typeSymbol == defn.UnitClass then
typed(expr, defn.UnitType)
else
val tpt1 = tpt match
case Block(_, tpt) => tpt // erase type aliases (statements) from type block
case tpt => tpt
val tpt2 = typedType(tpt1)
val expr1 = typed(expr, tpt2.tpe)
assignType(untpd.cpy.Typed(tree)(expr1, tpt2), tpt2)
override def typedLiteral(tree: untpd.Literal)(using Context): Tree =
if (tree.typeOpt.isRef(defn.UnitClass))
tree.withType(tree.typeOpt)
else if (tree.const.tag == Constants.ClazzTag)
checkNotErasedClass(clsOf(tree.const.typeValue))
else
super.typedLiteral(tree)
override def typedIdent(tree: untpd.Ident, pt: Type)(using Context): Tree =
checkNotErased(super.typedIdent(tree, pt))
/** Type check select nodes, applying the following rewritings exhaustively
* on selections `e.m`, where `OT` is the type of the owner of `m` and `ET`
* is the erased type of the selection's original qualifier expression.
*
* e.m1 -> e.m2 if `m1` is a member of a class that erases to Object and `m2` is
* the same-named member in Object.
* e.m -> box(e).m if `e` is primitive and `m` is a member or a reference class
* or `e` has an erased value class type.
* e.m -> unbox(e).m if `e` is not primitive and `m` is a member of a primtive type.
* e.m -> cast(e, OT).m if the type of `e` does not conform to OT and `m`
* is not an array operation.
*
* If `m` is an array operation, i.e. one of the members apply, update, length, clone, and
* <init> of class Array, we additionally try the following rewritings:
*
* e.m -> runtime.array_m(e) if ET is Object
* e.m -> cast(e, ET).m if the type of `e` does not conform to ET
* e.clone -> e.clone' where clone' is Object's clone method
* e.m -> e.[]m if `m` is an array operation other than `clone`.
*/
override def typedSelect(tree: untpd.Select, pt: Type)(using Context): Tree = {
if tree.name == nme.apply && integrateSelect(tree) then
return typed(tree.qualifier, pt)
val qual1 = typed(tree.qualifier, AnySelectionProto)
def mapOwner(sym: Symbol): Symbol =
if !sym.exists && tree.name == nme.apply then
// PolyFunction apply Selects will not have a symbol, so deduce the owner
// from the typed tree of the erasure of the original qualifier's PolyFunction type.
// We cannot simply call `erasure` on the qualifier because its erasure might be
// `Object` due to how we erase intersections (see pos/i13950.scala).
// Instead, we manually lookup the type of `apply` in the qualifier.
inContext(preErasureCtx) {
val qualTp = tree.qualifier.typeOpt.widen
if qualTp.derivesFrom(defn.PolyFunctionClass) then
eraseRefinedFunctionApply(qualTp.select(nme.apply).widen).classSymbol
else
NoSymbol
}
else
val owner = sym.maybeOwner
if defn.specialErasure.contains(owner) then
assert(sym.isConstructor, s"${sym.showLocated}")
defn.specialErasure(owner).nn
else if defn.isSyntheticFunctionClass(owner) then
defn.functionTypeErasure(owner).typeSymbol
else
owner
val origSym = tree.symbol
if !origSym.exists && qual1.tpe.widen.isInstanceOf[JavaArrayType] then
return tree.asInstanceOf[Tree] // we are re-typing a primitive array op
val owner = mapOwner(origSym)
val sym =
(if (owner eq origSym.maybeOwner) origSym else owner.info.decl(tree.name).symbol)
.orElse {
// We fail the sym.exists test for pos/i15158.scala, where we pass an infinitely
// recurring match type to an overloaded constructor. An equivalent test
// with regular apply methods succeeds. It's at present unclear whether
// - the program should be rejected, or
// - there is another fix.
// Therefore, we apply the fix to use the pre-erasure symbol, but only
// for constructors, in order not to mask other possible bugs that would
// trigger the assert(sym.exists, ...) below.
val prevSym = tree.symbol(using preErasureCtx)
if prevSym.isConstructor then prevSym else NoSymbol
}
assert(sym.exists, i"no owner from $owner/${origSym.showLocated} in $tree")
if owner == defn.ObjectClass then checkValue(qual1)
def select(qual: Tree, sym: Symbol): Tree =
untpd.cpy.Select(tree)(qual, sym.name).withType(NamedType(qual.tpe, sym))
def selectArrayMember(qual: Tree, erasedPre: Type): Tree =
if erasedPre.isAnyRef then
partialApply(ref(defn.runtimeMethodRef(tree.name.genericArrayOp)), qual :: Nil)
else if !(qual.tpe <:< erasedPre) then
selectArrayMember(cast(qual, erasedPre), erasedPre)
else
assignType(untpd.cpy.Select(tree)(qual, tree.name.primitiveArrayOp), qual)
def adaptIfSuper(qual: Tree): Tree = qual match {
case Super(thisQual, untpd.EmptyTypeIdent) =>
val SuperType(thisType, supType) = qual.tpe: @unchecked
if (sym.owner.is(Flags.Trait))
cpy.Super(qual)(thisQual, untpd.Ident(sym.owner.asClass.name))
.withType(SuperType(thisType, sym.owner.typeRef))
else
qual.withType(SuperType(thisType, thisType.firstParent.typeConstructor))
case _ =>
qual
}
/** Can we safely use `cls` as a qualifier without getting a runtime error on
* the JVM due to its accessibility checks?
*/
def isJvmAccessible(cls: Symbol): Boolean =
// Scala classes are always emitted as public, unless the
// `private` modifier is used, but a non-private class can never
// extend a private class, so such a class will never be a cast target.
!cls.is(Flags.JavaDefined) || {
// We can't rely on `isContainedWith` here because packages are
// not nested from the JVM point of view.
val boundary = cls.accessBoundary(cls.owner)(using preErasureCtx)
(boundary eq defn.RootClass) ||
(ctx.owner.enclosingPackageClass eq boundary)
}
@tailrec
def recur(qual: Tree): Tree =
val qualIsPrimitive = qual.tpe.widen.isPrimitiveValueType
val symIsPrimitive = sym.owner.isPrimitiveValueClass
def originalQual: Type =
erasure(
inContext(preErasureCtx):
tree.qualifier.typeOpt.widen.finalResultType)
if qualIsPrimitive && !symIsPrimitive || qual.tpe.widenDealias.isErasedValueType then
recur(box(qual))
else if !qualIsPrimitive && symIsPrimitive then
recur(unbox(qual, sym.owner.typeRef))
else if sym.owner eq defn.ArrayClass then
selectArrayMember(qual, originalQual)
else
adaptIfSuper(qual) match
case qual1: Super =>
select(qual1, sym)
case qual1 if !isJvmAccessible(qual1.tpe.typeSymbol)
|| !qual1.tpe.derivesFrom(sym.owner) =>
val castTarget = // Avoid inaccessible cast targets, see i8661
if isJvmAccessible(sym.owner) && sym.owner.isType then
sym.owner.typeRef
else
// If the owner is inaccessible, try going through the qualifier,
// but be careful to not go in an infinite loop in case that doesn't
// work either.
val tp = originalQual
if tp =:= qual1.tpe.widen then
return errorTree(qual1,
em"Unable to emit reference to ${sym.showLocated}, ${sym.owner} is not accessible in ${ctx.owner.enclosingClass}")
tp
recur(cast(qual1, castTarget))
case qual1 =>
select(qual1, sym)
end recur
checkNotErased(recur(qual1))
}
override def typedThis(tree: untpd.This)(using Context): Tree =
if (tree.symbol == ctx.owner.lexicallyEnclosingClass || tree.symbol.isStaticOwner) promote(tree)
else {
report.log(i"computing outer path from ${ctx.owner.ownersIterator.toList}%, % to ${tree.symbol}, encl class = ${ctx.owner.enclosingClass}")
outer.path(toCls = tree.symbol)
}
override def typedTypeApply(tree: untpd.TypeApply, pt: Type)(using Context): Tree = {
val ntree = atPhase(erasurePhase){
// Use erased-type semantic to intercept TypeApply in explicit nulls
val interceptCtx = if ctx.explicitNulls then ctx.retractMode(Mode.SafeNulls) else ctx
interceptTypeApply(tree.asInstanceOf[TypeApply])(using interceptCtx)
}.withSpan(tree.span)
ntree match {
case TypeApply(fun, args) =>
val fun1 = typedExpr(fun, AnyFunctionProto)
fun1.tpe.widen match {
case funTpe: PolyType =>
val args1 = args.mapconserve(typedType(_))
untpd.cpy.TypeApply(tree)(fun1, args1).withType(funTpe.instantiate(args1.tpes))
case _ => fun1
}
case _ => typedExpr(ntree, pt)
}
}
override def typedBind(tree: untpd.Bind, pt: Type)(using Context): Bind =
atPhase(erasurePhase):
checkBind(promote(tree))
super.typedBind(tree, pt)
/** Besides normal typing, this method does uncurrying and collects parameters
* to anonymous functions of arity > 22.
*/
override def typedApply(tree: untpd.Apply, pt: Type)(using Context): Tree =
val Apply(fun, args) = tree
val origFun = fun.asInstanceOf[tpd.Tree]
val origFunType = origFun.tpe.widen(using preErasureCtx)
val ownArgs = origFunType match
case mt: MethodType if mt.hasErasedParams =>
args.zip(mt.erasedParams).collect { case (arg, false) => arg }
case _ => args
val fun1 = typedExpr(fun, AnyFunctionProto)
fun1.tpe.widen match
case mt: MethodType =>
val (xmt, // A method type like `mt` but with bunched arguments expanded to individual ones
bunchArgs, // whether arguments are bunched
outers) = // the outer reference parameter(s)
if fun1.isInstanceOf[Apply] then
(mt, fun1.removeAttachment(BunchedArgs).isDefined, Nil)
else
val xmt = expandedMethodType(mt, origFun)
(xmt, xmt ne mt, outer.args(origFun))
val args0 = outers ::: ownArgs
val args1 = args0.zipWithConserve(xmt.paramInfos)(typedExpr)
def mkApply(finalFun: Tree, finalArgs: List[Tree]) =
val app = untpd.cpy.Apply(tree)(finalFun, finalArgs)
.withType(applyResultType(xmt, args1))
if bunchArgs then app.withAttachment(BunchedArgs, ()) else app
def app(fun1: Tree): Tree = fun1 match
case Block(stats, expr) =>
cpy.Block(fun1)(stats, app(expr))
case Apply(fun2, SeqLiteral(prevArgs, argTpt) :: _) if bunchArgs =>
mkApply(fun2, JavaSeqLiteral(prevArgs ++ args1, argTpt) :: Nil)
case Apply(fun2, prevArgs) =>
mkApply(fun2, prevArgs ++ args1)
case _ if bunchArgs =>
mkApply(fun1, JavaSeqLiteral(args1, TypeTree(defn.ObjectType)) :: Nil)
case _ =>
mkApply(fun1, args1)
app(fun1)
case t =>
if ownArgs.isEmpty || t.isError then fun1
else throw new MatchError(i"tree $tree has unexpected type of function $fun/$fun1: $t, was $origFunType, args = $ownArgs")
end typedApply
// The following four methods take as the proto-type the erasure of the pre-existing type,
// if the original proto-type is not a value type.
// This makes all branches be adapted to the correct type.
override def typedSeqLiteral(tree: untpd.SeqLiteral, pt: Type)(using Context): SeqLiteral =
super.typedSeqLiteral(tree, erasure(tree.typeOpt))
// proto type of typed seq literal is original type;
override def typedIf(tree: untpd.If, pt: Type)(using Context): Tree =
super.typedIf(tree, adaptProto(tree, pt))
override def typedMatch(tree: untpd.Match, pt: Type)(using Context): Tree =
super.typedMatch(tree, adaptProto(tree, pt))
override def typedTry(tree: untpd.Try, pt: Type)(using Context): Try =
super.typedTry(tree, adaptProto(tree, pt))
private def adaptProto(tree: untpd.Tree, pt: Type)(using Context) =
if (pt.isValueType) pt else
if (tree.typeOpt.derivesFrom(ctx.definitions.UnitClass))
tree.typeOpt
else valueErasure(tree.typeOpt)
override def typedInlined(tree: untpd.Inlined, pt: Type)(using Context): Tree =
super.typedInlined(tree, pt) match {
case tree: Inlined => Inlines.dropInlined(tree)
}
override def typedValDef(vdef: untpd.ValDef, sym: Symbol)(using Context): Tree =
if (sym.isEffectivelyErased) erasedDef(sym)
else
checkNotErasedClass(sym.info, vdef)
super.typedValDef(untpd.cpy.ValDef(vdef)(
tpt = untpd.TypedSplice(TypeTree(sym.info).withSpan(vdef.tpt.span))), sym)
/** Besides normal typing, this function also compacts anonymous functions
* with more than `MaxImplementedFunctionArity` parameters to use a single
* parameter of type `[]Object`.
*/
override def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(using Context): Tree =
if sym.isEffectivelyErased || sym.name.is(BodyRetainerName) then
erasedDef(sym)
else
checkNotErasedClass(sym.info.finalResultType, ddef)
val restpe = if sym.isConstructor then defn.UnitType else sym.info.resultType
var vparams = outerParamDefs(sym)
::: ddef.paramss.collect {
case untpd.ValDefs(vparams) => vparams
}.flatten.filterConserve(!_.symbol.is(Flags.Erased))
def skipContextClosures(rhs: Tree, crCount: Int)(using Context): Tree =
if crCount == 0 then rhs
else rhs match
case closureDef(meth) =>
val contextParams = meth.termParamss.head
for param <- contextParams do
if !param.symbol.is(Flags.Erased) then
param.symbol.copySymDenotation(owner = sym).installAfter(erasurePhase)
vparams = vparams :+ param
if crCount == 1 then meth.rhs.changeOwnerAfter(meth.symbol, sym, erasurePhase)
else skipContextClosures(meth.rhs, crCount - 1)
case inlined: Inlined =>
skipContextClosures(Inlines.dropInlined(inlined), crCount)
var rhs1 = skipContextClosures(ddef.rhs.asInstanceOf[Tree], contextResultCount(sym))
if sym.isAnonymousFunction && vparams.length > MaxImplementedFunctionArity then
val bunchedParam = newSymbol(sym, nme.ALLARGS, Flags.TermParam, JavaArrayType(defn.ObjectType))
def selector(n: Int) = ref(bunchedParam)
.select(defn.Array_apply)
.appliedTo(Literal(Constant(n)))
val paramDefs = vparams.zipWithIndex.map {
case (paramDef, idx) =>
assignType(untpd.cpy.ValDef(paramDef)(rhs = selector(idx)), paramDef.symbol)
}
vparams = ValDef(bunchedParam) :: Nil
rhs1 = Block(paramDefs, rhs1)
val ddef1 = untpd.cpy.DefDef(ddef)(
paramss = vparams :: Nil,
tpt = untpd.TypedSplice(TypeTree(restpe).withSpan(ddef.tpt.span)),
rhs = rhs1)
super.typedDefDef(ddef1, sym)
end typedDefDef
/** The outer parameter definition of a constructor if it needs one */
private def outerParamDefs(constr: Symbol)(using Context): List[ValDef] =
if constr.isConstructor && needsOuterParam(constr.owner.asClass) then
constr.info match
case MethodTpe(outerName :: _, outerType :: _, _) =>
val outerSym = newSymbol(constr, outerName, Flags.Param | Flags.SyntheticArtifact, outerType)
ValDef(outerSym) :: Nil
case _ =>
// There's a possible race condition that a constructor was looked at
// after erasure before we had a chance to run ExplicitOuter on its class
// If furthermore the enclosing class does not always have constructors,
// but needs constructors in this particular case, we miss the constructor
// accessor that's produced with an `enteredAfter` in ExplicitOuter, so
// `tranformInfo` of the constructor in erasure yields a method type without
// an outer parameter. We fix this problem by adding the missing outer
// parameter here.
constr.copySymDenotation(
info = outer.addParam(constr.owner.asClass, constr.info)
).installAfter(erasurePhase)
outerParamDefs(constr)
else Nil
/** For all statements in stats: given a retained inline method and
* its retainedBody method such as
*
* inline override def f(x: T) = body1