-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Dotty with explicit nulls #6344
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 37 commits
1c5679b
07d1882
9912940
36aca35
46c3e16
e43963b
4f9a437
19c5e40
b3c85e3
3f2e8d1
e2588b7
4fd2085
a56557f
0a54fe6
902dc4a
1168856
2d5d784
f72f201
de5241b
8e900f1
92b6c5c
feb11b0
0000c28
aabb4e1
712e2ca
55db452
9595870
7663fc2
53a88cd
fdea080
19925a5
f9ca273
92095a2
0b35a43
c7396db
28552f5
d4df9e7
e2c47d7
ec880ad
513890d
6c898c7
fa43361
919f5c6
74e1ede
6b25d48
70b5df0
6e92146
9b6846e
e0bc5d3
cbb876c
750dd50
183d237
0cbfa6b
c960acf
b0acf92
9365e1b
ca62bf9
a49d4c3
8481c2a
ae4253a
ad9ae9b
9913410
313057c
3a1d17c
303d2de
9d77d64
cd623d4
54eca3c
8c195dc
946a7a9
856ba31
4cd1114
4a8b754
6d6d2ec
944f91d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,7 @@ package core | |
|
||
import scala.annotation.{threadUnsafe => tu} | ||
import Types._, Contexts._, Symbols._, SymDenotations._, StdNames._, Names._ | ||
import Flags._, Scopes._, Decorators._, NameOps._, Periods._ | ||
import Flags._, Scopes._, Decorators._, NameOps._, Periods._, NullOpsDecorator._ | ||
import unpickleScala2.Scala2Unpickler.ensureConstructor | ||
import scala.collection.mutable | ||
import collection.mutable | ||
|
@@ -280,7 +280,7 @@ class Definitions { | |
@tu lazy val ObjectClass: ClassSymbol = { | ||
val cls = ctx.requiredClass("java.lang.Object") | ||
assert(!cls.isCompleted, "race for completing java.lang.Object") | ||
cls.info = ClassInfo(cls.owner.thisType, cls, AnyClass.typeRef :: Nil, newScope) | ||
cls.info = ClassInfo(cls.owner.thisType, cls, AnyType :: Nil, newScope) | ||
cls.setFlag(NoInits) | ||
|
||
// The companion object doesn't really exist, so it needs to be marked as | ||
|
@@ -297,8 +297,16 @@ class Definitions { | |
@tu lazy val AnyRefAlias: TypeSymbol = enterAliasType(tpnme.AnyRef, ObjectType) | ||
def AnyRefType: TypeRef = AnyRefAlias.typeRef | ||
|
||
@tu lazy val Object_eq: TermSymbol = enterMethod(ObjectClass, nme.eq, methOfAnyRef(BooleanType), Final) | ||
@tu lazy val Object_ne: TermSymbol = enterMethod(ObjectClass, nme.ne, methOfAnyRef(BooleanType), Final) | ||
@tu lazy val Object_eq: TermSymbol = { | ||
// If explicit nulls is enabled, then we want to allow `(x: String).eq(null)`, so we need | ||
// to adjust the signature of `eq` accordingly. | ||
enterMethod(ObjectClass, nme.eq, methOfAnyRefOrNull(BooleanType), Final) | ||
} | ||
@tu lazy val Object_ne: TermSymbol = { | ||
// If explicit nulls is enabled, then we want to allow `(x: String).ne(null)`, so we need | ||
// to adjust the signature of `ne` accordingly. | ||
enterMethod(ObjectClass, nme.ne, methOfAnyRefOrNull(BooleanType), Final) | ||
} | ||
@tu lazy val Object_synchronized: TermSymbol = enterPolyMethod(ObjectClass, nme.synchronized_, 1, | ||
pt => MethodType(List(pt.paramRefs(0)), pt.paramRefs(0)), Final) | ||
@tu lazy val Object_clone: TermSymbol = enterMethod(ObjectClass, nme.clone_, MethodType(Nil, ObjectType), Protected) | ||
|
@@ -331,18 +339,37 @@ class Definitions { | |
pt => MethodType(List(FunctionOf(Nil, pt.paramRefs(0))), pt.paramRefs(0))) | ||
|
||
/** Method representing a throw */ | ||
@tu lazy val throwMethod: TermSymbol = enterMethod(OpsPackageClass, nme.THROWkw, | ||
MethodType(List(ThrowableType), NothingType)) | ||
@tu lazy val throwMethod: TermSymbol = { | ||
enterMethod(OpsPackageClass, nme.THROWkw, MethodType(List(ThrowableType.maybeNullable), NothingType)) | ||
} | ||
|
||
@tu lazy val NothingClass: ClassSymbol = enterCompleteClassSymbol( | ||
ScalaPackageClass, tpnme.Nothing, AbstractFinal, List(AnyClass.typeRef)) | ||
def NothingType: TypeRef = NothingClass.typeRef | ||
@tu lazy val RuntimeNothingModuleRef: TermRef = ctx.requiredModuleRef("scala.runtime.Nothing") | ||
@tu lazy val NullClass: ClassSymbol = enterCompleteClassSymbol( | ||
ScalaPackageClass, tpnme.Null, AbstractFinal, List(ObjectClass.typeRef)) | ||
@tu lazy val NullClass: ClassSymbol = { | ||
val parent = if (ctx.explicitNulls) AnyType else ObjectType | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's try AnyVal instead of Any for the parent of Null. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. An issue with Having There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hum, what There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The type could also be a primitive type like Int or Boolean, or a value class.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I see. Well TBH I'm not sure that using this kind of signature is something we would want to encourage. We want people not to use Anyway, you can still define type NotNull = AnyRef | Boolean | Char | Byte | Short | Int | Long | Float | Double | Unit Yes, that would take longer to typecheck against, but I would certainly hope that usage of (Also, yes, my There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe, but in a slightly different way. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could that be fixed with: def apply[A <: NotNull](a: A | Null): Option[A] = .. then your example would be: def f[A <: NotNull](x: A, default: A): A = {
val y = Option[A](x)
y.getOrElse(default)
}
f[String | Null](null, "default") // fails compilation: `String | Null` is not `NotNull` There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The motivating example is code like: Option(somePossiblyNullThing) match {
case Some(null) => ???
case Some(s) => ???
case None => ???
} It would be nice to not have to include the first case. More concretely, in minitest, there is the line: Option(source.getMessage).filterNot(_.isEmpty) This fails to compile because the Option(source.getMessage).filterNot(_.nn.isEmpty) I think this defeats the main purpose of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That seems to be an inference issue, not a subtyping/type hierarchy issue. We could teach type inference that when it tries to infer |
||
enterCompleteClassSymbol(ScalaPackageClass, tpnme.Null, AbstractFinal, parent :: Nil) | ||
} | ||
def NullType: TypeRef = NullClass.typeRef | ||
@tu lazy val RuntimeNullModuleRef: TermRef = ctx.requiredModuleRef("scala.runtime.Null") | ||
|
||
/** An alias for null values that originate in Java code. | ||
* This type gets special treatment in the Typer. Specifically, `JavaNull` can be selected through: | ||
* e.g. | ||
* ``` | ||
* // x: String|Null | ||
* x.length // error: `Null` has no `length` field | ||
* // x2: String|JavaNull | ||
* x2.length // allowed by the Typer, but unsound (might throw NPE) | ||
* ``` | ||
*/ | ||
lazy val JavaNullAlias: TypeSymbol = { | ||
assert(ctx.explicitNulls) | ||
enterAliasType(tpnme.JavaNull, NullType) | ||
} | ||
def JavaNullAliasType: TypeRef = JavaNullAlias.typeRef | ||
|
||
@tu lazy val ImplicitScrutineeTypeSym = | ||
newSymbol(ScalaPackageClass, tpnme.IMPLICITkw, EmptyFlags, TypeBounds.empty).entered | ||
def ImplicitScrutineeTypeRef: TypeRef = ImplicitScrutineeTypeSym.typeRef | ||
|
@@ -509,12 +536,16 @@ class Definitions { | |
@tu lazy val BoxedNumberClass: ClassSymbol = ctx.requiredClass("java.lang.Number") | ||
@tu lazy val ClassCastExceptionClass: ClassSymbol = ctx.requiredClass("java.lang.ClassCastException") | ||
@tu lazy val ClassCastExceptionClass_stringConstructor: TermSymbol = ClassCastExceptionClass.info.member(nme.CONSTRUCTOR).suchThat(_.info.firstParamTypes match { | ||
case List(pt) => (pt isRef StringClass) | ||
case List(pt) => | ||
val pt1 = if (ctx.explicitNulls) pt.stripNull else pt | ||
pt1 isRef StringClass | ||
case _ => false | ||
}).symbol.asTerm | ||
@tu lazy val ArithmeticExceptionClass: ClassSymbol = ctx.requiredClass("java.lang.ArithmeticException") | ||
@tu lazy val ArithmeticExceptionClass_stringConstructor: TermSymbol = ArithmeticExceptionClass.info.member(nme.CONSTRUCTOR).suchThat(_.info.firstParamTypes match { | ||
case List(pt) => (pt isRef StringClass) | ||
case List(pt) => | ||
val pt1 = if (ctx.explicitNulls) pt.stripNull else pt | ||
pt1 isRef StringClass | ||
case _ => false | ||
}).symbol.asTerm | ||
|
||
|
@@ -777,7 +808,7 @@ class Definitions { | |
// convenient one-parameter method types | ||
def methOfAny(tp: Type): MethodType = MethodType(List(AnyType), tp) | ||
def methOfAnyVal(tp: Type): MethodType = MethodType(List(AnyValType), tp) | ||
def methOfAnyRef(tp: Type): MethodType = MethodType(List(ObjectType), tp) | ||
def methOfAnyRefOrNull(tp: Type): MethodType = MethodType(List(ObjectType.maybeNullable), tp) | ||
|
||
// Derived types | ||
|
||
|
@@ -938,8 +969,16 @@ class Definitions { | |
name.drop(prefix.length).forall(_.isDigit)) | ||
|
||
def isBottomClass(cls: Symbol): Boolean = | ||
cls == NothingClass || cls == NullClass | ||
if (ctx.explicitNulls && !ctx.phase.erasedTypes) cls == NothingClass | ||
else isBottomClassAfterErasure(cls) | ||
|
||
def isBottomClassAfterErasure(cls: Symbol): Boolean = cls == NothingClass || cls == NullClass | ||
|
||
def isBottomType(tp: Type): Boolean = | ||
if (ctx.explicitNulls && !ctx.phase.erasedTypes) tp.derivesFrom(NothingClass) | ||
else isBottomTypeAfterErasure(tp) | ||
|
||
def isBottomTypeAfterErasure(tp: Type): Boolean = | ||
tp.derivesFrom(NothingClass) || tp.derivesFrom(NullClass) | ||
|
||
/** Is a function class. | ||
|
@@ -1283,18 +1322,22 @@ class Definitions { | |
// ----- Initialization --------------------------------------------------- | ||
|
||
/** Lists core classes that don't have underlying bytecode, but are synthesized on-the-fly in every reflection universe */ | ||
@tu lazy val syntheticScalaClasses: List[TypeSymbol] = List( | ||
AnyClass, | ||
AnyRefAlias, | ||
AnyKindClass, | ||
andType, | ||
orType, | ||
RepeatedParamClass, | ||
ByNameParamClass2x, | ||
AnyValClass, | ||
NullClass, | ||
NothingClass, | ||
SingletonClass) | ||
@tu lazy val syntheticScalaClasses: List[TypeSymbol] = { | ||
val synth = List( | ||
AnyClass, | ||
AnyRefAlias, | ||
AnyKindClass, | ||
andType, | ||
orType, | ||
RepeatedParamClass, | ||
ByNameParamClass2x, | ||
AnyValClass, | ||
NullClass, | ||
NothingClass, | ||
SingletonClass) | ||
|
||
if (ctx.explicitNulls) synth :+ JavaNullAlias else synth | ||
} | ||
abeln marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
@tu lazy val syntheticCoreClasses: List[Symbol] = syntheticScalaClasses ++ List( | ||
EmptyPackageVal, | ||
|
Uh oh!
There was an error while loading. Please reload this page.