@@ -275,6 +275,11 @@ abstract class GenICode extends SubComponent {
275
275
ctx1 = genLoad(args.head, ctx1, INT )
276
276
generatedType = elem
277
277
ctx1.bb.emit(LOAD_ARRAY_ITEM (elementType), tree.pos)
278
+ // it's tempting to just drop array loads of type Null instead
279
+ // of adapting them but array accesses can cause
280
+ // ArrayIndexOutOfBounds so we can't. Besides, Array[Null]
281
+ // probably isn't common enough to figure out an optimization
282
+ adaptNullRef(generatedType, expectedType, ctx1, tree.pos)
278
283
}
279
284
else if (scalaPrimitives.isArraySet(code)) {
280
285
debugassert(args.length == 2 ,
@@ -790,7 +795,9 @@ abstract class GenICode extends SubComponent {
790
795
}
791
796
generatedType =
792
797
if (sym.isClassConstructor) UNIT
793
- else toTypeKind(sym.info.resultType);
798
+ else toTypeKind(sym.info.resultType)
799
+ // deal with methods that return Null
800
+ adaptNullRef(generatedType, expectedType, ctx1, tree.pos)
794
801
ctx1
795
802
}
796
803
}
@@ -842,14 +849,15 @@ abstract class GenICode extends SubComponent {
842
849
843
850
if (sym.isModule) {
844
851
genLoadModule(genLoadQualUnlessElidable, tree)
845
- }
846
- else if (sym.isStaticMember) {
847
- val ctx1 = genLoadQualUnlessElidable
848
- ctx1.bb.emit(LOAD_FIELD (sym, true ) setHostClass hostClass, tree.pos)
849
- ctx1
850
852
} else {
851
- val ctx1 = genLoadQualifier(tree, ctx)
852
- ctx1.bb.emit(LOAD_FIELD (sym, false ) setHostClass hostClass, tree.pos)
853
+ val isStatic = sym.isStaticMember
854
+ val ctx1 = if (isStatic) genLoadQualUnlessElidable
855
+ else genLoadQualifier(tree, ctx)
856
+ ctx1.bb.emit(LOAD_FIELD (sym, isStatic) setHostClass hostClass, tree.pos)
857
+ // it's tempting to drop field accesses of type Null instead of adapting them,
858
+ // but field access can cause static class init so we can't. Besides, fields
859
+ // of type Null probably aren't common enough to figure out an optimization
860
+ adaptNullRef(generatedType, expectedType, ctx1, tree.pos)
853
861
ctx1
854
862
}
855
863
}
@@ -997,22 +1005,56 @@ abstract class GenICode extends SubComponent {
997
1005
998
1006
resCtx
999
1007
}
1008
+
1009
+ /**
1010
+ * If we have a method call, field load, or array element load of type Null then
1011
+ * we need to convince the JVM that we have a null value because in Scala
1012
+ * land Null is a subtype of all ref types, but in JVM land scala.runtime.Null$
1013
+ * is not. Note we don't have to adapt loads of locals because the JVM type
1014
+ * system for locals does have a null type which it tracks internally. As
1015
+ * long as we adapt these other things, the JVM will know that a Scala local of
1016
+ * type Null is holding a null.
1017
+ */
1018
+ private def adaptNullRef (from : TypeKind , to : TypeKind , ctx : Context , pos : Position ) {
1019
+ log(s " GenICode#adaptNullRef( $from, $to, $ctx, $pos) " )
1020
+
1021
+ // Don't need to adapt null to unit because we'll just drop it anyway. Don't
1022
+ // need to adapt to Object or AnyRef because the JVM is happy with
1023
+ // upcasting Null to them.
1024
+ // We do have to adapt from NullReference to NullReference because we could be storing
1025
+ // this value into a local of type Null and we want the JVM to see that it's
1026
+ // a null value so we don't have to also adapt local loads.
1027
+ if (from == NullReference && to != UNIT && to != ObjectReference && to != AnyRefReference ) {
1028
+ assert(to.isReferenceType, " Attempt to adapt a null to a non reference type" )
1029
+ // adapt by dropping what we've got and pushing a null which
1030
+ // will convince the JVM we really do have null
1031
+ ctx.bb.emit(DROP (from), pos)
1032
+ ctx.bb.emit(CONSTANT (Constant (null )), pos)
1033
+ }
1034
+ }
1000
1035
1001
1036
private def adapt (from : TypeKind , to : TypeKind , ctx : Context , pos : Position ) {
1002
1037
// An awful lot of bugs explode here - let's leave ourselves more clues.
1003
1038
// A typical example is an overloaded type assigned after typer.
1004
1039
log(s " GenICode#adapt( $from, $to, $ctx, $pos) " )
1005
1040
1006
- val conforms = (from <:< to) || (from == NullReference && to == NothingReference )
1041
+ val conforms = (from <:< to) || (from == NullReference && to == NothingReference ) // TODO why would we have null where we expect nothing?
1007
1042
def coerce (from : TypeKind , to : TypeKind ) = ctx.bb.emit(CALL_PRIMITIVE (Conversion (from, to)), pos)
1008
1043
def checkAssertions () {
1009
1044
def msg = s " Can't convert from $from to $to in unit ${unit.source} at $pos"
1010
1045
debugassert(from != UNIT , msg)
1011
1046
assert(! from.isReferenceType && ! to.isReferenceType, msg)
1012
1047
}
1013
1048
if (conforms) from match {
1049
+ // The JVM doesn't have a Nothing equivalent, so it doesn't know that a method of type Nothing can't actually return. So for instance, with
1050
+ // def f: String = ???
1051
+ // we need
1052
+ // 0: getstatic #25; //Field scala/Predef$.MODULE$:Lscala/Predef$;
1053
+ // 3: invokevirtual #29; //Method scala/Predef$.$qmark$qmark$qmark:()Lscala/runtime/Nothing$;
1054
+ // 6: athrow
1055
+ // So this case tacks on the ahtrow which makes the JVM happy because class Nothing is declared as a subclass of Throwable
1014
1056
case NothingReference => ctx.bb.emit(THROW (ThrowableClass )) ; ctx.bb.enterIgnoreMode
1015
- case NullReference => ctx.bb.emit( Seq ( DROP (from), CONSTANT ( Constant ( null ))))
1057
+ // TODO why do we have this case? It's saying if we have a throwable and a non-throwable is expected then we should emit a cast? Why would we get here?
1016
1058
case ThrowableReference if ! (ThrowableClass .tpe <:< to.toType) => ctx.bb.emit(CHECK_CAST (to)) // downcast throwables
1017
1059
case _ =>
1018
1060
// widen subrange types
0 commit comments