Skip to content
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

Allow applications in export qualifiers #14468

Closed
wants to merge 2 commits into from

Conversation

odersky
Copy link
Contributor

@odersky odersky commented Feb 13, 2022

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.

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.

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.
@soronpo
Copy link
Contributor

soronpo commented Feb 13, 2022

I tested the following in REPL and it fails:

class C(x: Int) { type T; def m = x }
export C(2).m

@soronpo
Copy link
Contributor

soronpo commented Feb 14, 2022

Fails compilation:

class Container(chamber: Int):
  object T:
    object TT

export Container(1).T
import T.* //Not found: T

@soronpo
Copy link
Contributor

soronpo commented Feb 14, 2022

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.

@soronpo
Copy link
Contributor

soronpo commented Feb 14, 2022

Compiler crash when we combine this feature with transparent inline and varargs:

class Container(args: Any*):
  object Internal
transparent inline def myC: Container = Container(1)

export myC.*

@soronpo
Copy link
Contributor

soronpo commented Feb 14, 2022

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!

@soronpo
Copy link
Contributor

soronpo commented Feb 14, 2022

Macros fail pretty strangely, as if both macro and test are the same source file (which they are not):
Main.scala

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

@odersky
Copy link
Contributor Author

odersky commented Feb 14, 2022

Fails compilation:

class Container(chamber: Int):
  object T:
    object TT

That's nothing new. It fails also if Container is an object (and we explain why).

@odersky
Copy link
Contributor Author

odersky commented Feb 14, 2022

Compiler crash when we combine this feature with transparent inline and varargs:

Can you be more specific? I could not reproduce a crash.

@soronpo
Copy link
Contributor

soronpo commented Feb 14, 2022

Compiler crash when we combine this feature with transparent inline and varargs:

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)

@odersky
Copy link
Contributor Author

odersky commented Feb 14, 2022

Macros fail pretty strangely, as if both macro and test are the same source file (which they are not):

Well, they are, since export copies the qualifier myC. EDIT: No, in fact was the same problem that caused the crash. Fixed now.

Apologies. I included the wrong snippet. This should cause the crash:

Thanks! I could reproduce and fix this one.

@soronpo
Copy link
Contributor

soronpo commented Feb 14, 2022

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.

Just to clarify the string-interpolation use-case.
I wanted to have the ability to include objects derived from an external-file at compile-time in a single command. For example:

export json"my_config.cfg".*

In this case json is a transparent inline macro that reads the file and populates an object.

@odersky
Copy link
Contributor Author

odersky commented Feb 15, 2022

@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.

@soronpo
Copy link
Contributor

soronpo commented Feb 15, 2022

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.

@odersky
Copy link
Contributor Author

odersky commented Feb 16, 2022

Withdrawn in favor of #14497.

@odersky odersky closed this Feb 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants