-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Allow applications in export qualifiers #14468
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
Conversation
This is a PR for a language extension to allow applications in export qualifiers. ## Motivation The main motivation is to replace a common use case where today we still need an implicit class: bulk decoration of method definitions from some template class. To do this we'd need a wildcard export in an extension method clause. E.g. ```scala class Ops(x: C) { ... } extension (x: C) export Ops(x).* ... ``` The export clause here has a qualifier that is syntactically an application. Such qualifiers are supported by this PR.
I tested the following in REPL and it fails: class C(x: Int) { type T; def m = x }
export C(2).m |
Fails compilation: class Container(chamber: Int):
object T:
object TT
export Container(1).T
import T.* //Not found: T |
Currently applying export on a string interpolation is not supported. class Container(args: Any*):
object Internal
extension (c: StringContext) def myC(args: Any*): Container = Container(args*)
export myC"Hi".* //error: an identifier expected, but string interpolator found I have a use-case for this, but I understand if this not something we want to support. |
Compiler crash when we combine this feature with class Container(args: Any*):
object Internal
transparent inline def myC: Container = Container(1)
export myC.* |
I was pleasantly surprised this worked as expected: class Container1(arg: Int):
object Internal1:
def exec: Unit = println("this is Internal1")
class Container2(arg: Int):
object Internal2:
def exec: Unit = println("this is Internal2")
transparent inline def myC(inline cond : Boolean): Any =
inline if (cond) Container1(1) else Container2(2)
export myC(true).*
Internal1.exec //prints "this is Internal1"
Internal2.exec //Internal2 not found error, as expected Well done! |
Macros fail pretty strangely, as if both macro and test are the same source file (which they are not): class Container1(arg: Int):
object Internal1:
def exec: Unit = println("this is Internal1")
transparent inline def myC: Any = ${ macroCrap }
def macroCrap(using Quotes): Expr[Any] =
'{ Container1(1) } Test.scala export myC.* //Cannot call macro method $anonfun defined in the same source file |
class Container(chamber: Int):
object T:
object TT That's nothing new. It fails also if |
Can you be more specific? I could not reproduce a crash. |
Apologies. I included the wrong snippet. This should cause the crash: class Container(args: Any*):
object Internal
transparent inline def myC(args: Any*): Container = Container(args)
export myC(1).* [error] java.lang.IllegalArgumentException: Could not find proxy for private val args$proxy1: scala.collection.immutable.Seq in List(val args$proxy1, method Internal, module class Example$package$, module class <empty>, module class <root>), encl = getter args$proxy1, owners = getter args$proxy1, package object Example$package, package <empty>, package <root>; enclosures = getter args$proxy1, package object Example$package, package <empty>, package <root>
[error] dotty.tools.dotc.transform.LambdaLift$Lifter.searchIn$1(LambdaLift.scala:149)
[error] dotty.tools.dotc.transform.LambdaLift$Lifter.proxy(LambdaLift.scala:162)
[error] dotty.tools.dotc.transform.LambdaLift$Lifter.proxyRef(LambdaLift.scala:180)
[error] dotty.tools.dotc.transform.LambdaLift.transformIdent(LambdaLift.scala:299)
[error] dotty.tools.dotc.transform.MegaPhase.goIdent(MegaPhase.scala:578)
[error] dotty.tools.dotc.transform.MegaPhase.transformNamed$1(MegaPhase.scala:224)
[error] dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:427)
[error] dotty.tools.dotc.transform.MegaPhase.mapDefDef$1(MegaPhase.scala:249)
[error] dotty.tools.dotc.transform.MegaPhase.transformNamed$1(MegaPhase.scala:252)
[error] dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:427)
[error] dotty.tools.dotc.transform.MegaPhase.loop$1(MegaPhase.scala:437)
[error] dotty.tools.dotc.transform.MegaPhase.transformStats(MegaPhase.scala:437)
[error] dotty.tools.dotc.transform.MegaPhase.transformUnnamed$1(MegaPhase.scala:299)
[error] dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:429)
[error] dotty.tools.dotc.transform.MegaPhase.transformNamed$1(MegaPhase.scala:228)
[error] dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:427)
[error] dotty.tools.dotc.transform.MegaPhase.transformUnnamed$1(MegaPhase.scala:279)
[error] dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:429)
[error] dotty.tools.dotc.transform.MegaPhase.mapDefDef$1(MegaPhase.scala:249)
[error] dotty.tools.dotc.transform.MegaPhase.transformNamed$1(MegaPhase.scala:252)
[error] dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:427)
[error] dotty.tools.dotc.transform.MegaPhase.loop$1(MegaPhase.scala:437)
[error] dotty.tools.dotc.transform.MegaPhase.transformStats(MegaPhase.scala:437)
[error] dotty.tools.dotc.transform.MegaPhase.transformUnnamed$1(MegaPhase.scala:362)
[error] dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:429)
[error] dotty.tools.dotc.transform.MegaPhase.transformNamed$1(MegaPhase.scala:256)
[error] dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:427)
[error] dotty.tools.dotc.transform.MegaPhase.loop$1(MegaPhase.scala:437)
[error] dotty.tools.dotc.transform.MegaPhase.transformStats(MegaPhase.scala:437)
[error] dotty.tools.dotc.transform.MegaPhase.mapPackage$1(MegaPhase.scala:382)
[error] dotty.tools.dotc.transform.MegaPhase.transformUnnamed$1(MegaPhase.scala:385)
[error] dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:429)
[error] dotty.tools.dotc.transform.MegaPhase.transformUnit(MegaPhase.scala:442)
[error] dotty.tools.dotc.transform.MegaPhase.run(MegaPhase.scala:454)
[error] dotty.tools.dotc.core.Phases$Phase.runOn$$anonfun$1(Phases.scala:311)
[error] scala.collection.immutable.List.map(List.scala:246)
[error] dotty.tools.dotc.core.Phases$Phase.runOn(Phases.scala:312)
[error] dotty.tools.dotc.Run.runPhases$1$$anonfun$1(Run.scala:259)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
[error] scala.collection.ArrayOps$.foreach$extension(ArrayOps.scala:1328)
[error] dotty.tools.dotc.Run.runPhases$1(Run.scala:270)
[error] dotty.tools.dotc.Run.compileUnits$$anonfun$1(Run.scala:278)
[error] dotty.tools.dotc.Run.compileUnits$$anonfun$adapted$1(Run.scala:287)
[error] dotty.tools.dotc.util.Stats$.maybeMonitored(Stats.scala:68)
[error] dotty.tools.dotc.Run.compileUnits(Run.scala:287)
[error] dotty.tools.dotc.Run.compileSources(Run.scala:220)
[error] dotty.tools.dotc.Run.compile(Run.scala:204)
[error] dotty.tools.dotc.Driver.doCompile(Driver.scala:39)
[error] dotty.tools.xsbt.CompilerBridgeDriver.run(CompilerBridgeDriver.java:88)
[error] dotty.tools.xsbt.CompilerBridge.run(CompilerBridge.java:22)
[error] sbt.internal.inc.AnalyzingCompiler.compile(AnalyzingCompiler.scala:91)
[error] sbt.internal.inc.MixedAnalyzingCompiler.$anonfun$compile$7(MixedAnalyzingCompiler.scala:192)
[error] scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)
[error] sbt.internal.inc.MixedAnalyzingCompiler.timed(MixedAnalyzingCompiler.scala:247)
[error] sbt.internal.inc.MixedAnalyzingCompiler.$anonfun$compile$4(MixedAnalyzingCompiler.scala:182)
[error] sbt.internal.inc.MixedAnalyzingCompiler.$anonfun$compile$4$adapted(MixedAnalyzingCompiler.scala:163)
[error] sbt.internal.inc.JarUtils$.withPreviousJar(JarUtils.scala:239)
[error] sbt.internal.inc.MixedAnalyzingCompiler.compileScala$1(MixedAnalyzingCompiler.scala:163)
[error] sbt.internal.inc.MixedAnalyzingCompiler.compile(MixedAnalyzingCompiler.scala:210)
[error] sbt.internal.inc.IncrementalCompilerImpl.$anonfun$compileInternal$1(IncrementalCompilerImpl.scala:528)
[error] sbt.internal.inc.IncrementalCompilerImpl.$anonfun$compileInternal$1$adapted(IncrementalCompilerImpl.scala:528)
[error] sbt.internal.inc.Incremental$.$anonfun$apply$5(Incremental.scala:177)
[error] sbt.internal.inc.Incremental$.$anonfun$apply$5$adapted(Incremental.scala:175)
[error] sbt.internal.inc.Incremental$$anon$2.run(Incremental.scala:461)
[error] sbt.internal.inc.IncrementalCommon$CycleState.next(IncrementalCommon.scala:116)
[error] sbt.internal.inc.IncrementalCommon$$anon$1.next(IncrementalCommon.scala:56)
[error] sbt.internal.inc.IncrementalCommon$$anon$1.next(IncrementalCommon.scala:52)
[error] sbt.internal.inc.IncrementalCommon.cycle(IncrementalCommon.scala:263)
[error] sbt.internal.inc.Incremental$.$anonfun$incrementalCompile$8(Incremental.scala:416)
[error] sbt.internal.inc.Incremental$.withClassfileManager(Incremental.scala:503)
[error] sbt.internal.inc.Incremental$.incrementalCompile(Incremental.scala:403)
[error] sbt.internal.inc.Incremental$.apply(Incremental.scala:169)
[error] sbt.internal.inc.IncrementalCompilerImpl.compileInternal(IncrementalCompilerImpl.scala:528)
[error] sbt.internal.inc.IncrementalCompilerImpl.$anonfun$compileIncrementally$1(IncrementalCompilerImpl.scala:482)
[error] sbt.internal.inc.IncrementalCompilerImpl.handleCompilationError(IncrementalCompilerImpl.scala:332)
[error] sbt.internal.inc.IncrementalCompilerImpl.compileIncrementally(IncrementalCompilerImpl.scala:420)
[error] sbt.internal.inc.IncrementalCompilerImpl.compile(IncrementalCompilerImpl.scala:137)
[error] sbt.Defaults$.compileIncrementalTaskImpl(Defaults.scala:2366)
[error] sbt.Defaults$.$anonfun$compileIncrementalTask$2(Defaults.scala:2316)
[error] sbt.internal.server.BspCompileTask$.$anonfun$compute$1(BspCompileTask.scala:30)
[error] sbt.internal.io.Retry$.apply(Retry.scala:46)
[error] sbt.internal.io.Retry$.apply(Retry.scala:28)
[error] sbt.internal.io.Retry$.apply(Retry.scala:23)
[error] sbt.internal.server.BspCompileTask$.compute(BspCompileTask.scala:30)
[error] sbt.Defaults$.$anonfun$compileIncrementalTask$1(Defaults.scala:2314)
[error] scala.Function1.$anonfun$compose$1(Function1.scala:49)
[error] sbt.internal.util.$tilde$greater.$anonfun$$u2219$1(TypeFunctions.scala:62)
[error] sbt.std.Transform$$anon$4.work(Transform.scala:68)
[error] sbt.Execute.$anonfun$submit$2(Execute.scala:282)
[error] sbt.internal.util.ErrorHandling$.wideConvert(ErrorHandling.scala:23)
[error] sbt.Execute.work(Execute.scala:291)
[error] sbt.Execute.$anonfun$submit$1(Execute.scala:282)
[error] sbt.ConcurrentRestrictions$$anon$4.$anonfun$submitValid$1(ConcurrentRestrictions.scala:265)
[error] sbt.CompletionService$$anon$2.call(CompletionService.scala:64)
[error] java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
[error] java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
[error] java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
[error] java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
[error] java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
[error] java.base/java.lang.Thread.run(Thread.java:829) |
Well, they are, since export copies the qualifier
Thanks! I could reproduce and fix this one. |
Just to clarify the string-interpolation use-case. export json"my_config.cfg".* In this case |
@soronpo Is string interpolation the primary use case or are there others? I am asking because it looks like we won't need full qualifier expressions for exports in extension methods. So I am not sure we should go ahead with this PR. It does complicate things quite a bit and it loosens the analogy between imports and exports, at least on the syntactic level. As an alternative, we can keep the restriction that the qualifier must be a path, but just drop the requirement that it must be stable. |
For me the only use-case I had planned for this feature was string interpolation for including custom external files. If it complicates things too much and you won't be needing it for extension methods, then yeah dropping it could be the right thing. |
Withdrawn in favor of #14497. |
This is a PR for a language extension to allow applications in export qualifiers.
Motivation
The main motivation is to replace a common use case where today we still need an
implicit class: bulk decoration of method definitions from some template class.
To do this we'd need a wildcard export in an extension method clause. E.g.
The export clause here has a qualifier that is syntactically an application. Such
qualifiers are supported by this PR.