-
-
Notifications
You must be signed in to change notification settings - Fork 368
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
Make mill.define.Module
a trait to allow abstract/virtual module lazy val
s to be trait
s rather than class
es
#2536
Conversation
mill.define.Module
a trait to allow abstract module val
s to work without classesmill.define.Module
a trait to allow abstract module lazy val
s to work without classes
mill.define.Module
a trait to allow abstract module lazy val
s to work without classesmill.define.Module
a trait to allow abstract/virtual module lazy val
s to work without classes
mill.define.Module
a trait to allow abstract/virtual module lazy val
s to work without classesmill.define.Module
a trait to allow abstract/virtual module lazy val
s to be trait
s rather than class
es
This is binary-incompatible with 0.11.0-M9, but probably worth getting in before 0.11.0-final. It fixes I believe the last edge case where someone would be forced to define a |
@@ -171,9 +171,9 @@ trait ScoverageModule extends ScalaModule { outer: ScalaModule => | |||
) | |||
} | |||
|
|||
val scoverage: ScoverageData = new ScoverageData(implicitly) | |||
lazy val scoverage = new ScoverageData {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this only to be lazy, or is it fixing some early initialization stuff? If this is a (required) pattern, we should probably document it in Module
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't need to be lazy, but I thought that's probably what we should tell people to do. After all, object
s are lazy, so using lazy val
for these would avoid unnecessarily changing the evaluation model. It also helps avoid NullPointerException
s from popping up in surprising places, especially once multiple trait inheritance and overriding is involved. Not a problem with this specific definition, but definitely a potential problem in general
* module as a child-module of the first. | ||
*/ | ||
case class ModuleRef[+T <: mill.define.Module](t: T) { | ||
def apply() = t |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Explicit return type would be nice. Wouldn't be a normal (non-case) class enough?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would. It would just be a bit more boilerplate to define
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this change. It especially improves the case where one needs to define some inner workers which typically work out of the box but need some customization in some rare cases, like ScoverageData
. I think we should be clear whether the holder needs to be lazy
to work correctly (avoid null
s) or if this is only for optimization.
6d130b5
to
fa7f579
Compare
* Used to refer to a module from another module without including the target | ||
* module as a child-module of the first. | ||
*/ | ||
case class ModuleRef[+T <: mill.define.Module](t: T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small suggestion, but could you be more meaningful with some of these names? For example in mill-scip I have some diffs now that look like
- val zincWorker = sm.zincWorker.worker()
+ val zincWorker = sm.zincWorker.t.worker()
And it's really odd to have just t
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think, the envisioned usage is to use the def apply() = t
.
- val zincWorker = sm.zincWorker.worker()
+ val zincWorker = sm.zincWorker().worker()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh duh,that makes more sense indeed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't t
be private then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't it invalidate the case class
contract then? E.g. when pattern matching.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this doesn't need to be a case class i think, just need a trait with an apply method
The basic problem is that when
Module
was a class, and you hadtrait FooModule extends Module
, Java reflection would not see thatFooModule <: Module
, and thusReflect.reflectNestedObjects0[Module]
which relies on java reflect would not detectdef foo: FooModule
as a module. This is becausetrait extends class
is a Scala compiler concept, and does not exist in the JVM bytecode.However, we do need
Module
to be a class, because we rely on implicit parameters to propagate context down the module tree.trait
s cannot take implicit parametersThe solution here is to make
Module
a trait, and make it inherit from aModule.BaseClass
class that takes the implicit parameter and passes it toModule
. Thus whenever we do reflection to findFooModule <: Module
, it behaves expected, and we are still able to receive implicit parameters viaModule.BaseClass
One side effect is that we need a way to mark things like
JavaModule#zincWorker
as not a child module. Previously, we skipped such modules during target resolution due to the buggy behavior above. Now, such modules get picked up, which is not what we want: in the common case,zincWorker
is just a reference to the shared external modulemill.scalalib.ZincWorkerModule
, and is not a child module of each individualJavaModule
. To do this, I introduce amill.define.ModuleRef
wrapper that simply provides a thin layer of indirection, letting us refer to the module viazincWorker()
but blocking identification as a child-module sinceModuleRef
is not a sub-type ofModule
.Tested via the
mill.resolve.ResolveTests.overridenModule
tests, which now pass when the abstract/overriden module is a trait, whereas previously they required that it be a class. Should also fix the flakiness we've been seeing inmill.resolve.ResolveTests.overridenModule.0
.