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

Provide way to annotate method as compile-time-only #6539

Closed
scabug opened this issue Oct 18, 2012 · 27 comments
Closed

Provide way to annotate method as compile-time-only #6539

scabug opened this issue Oct 18, 2012 · 27 comments

Comments

@scabug
Copy link

@scabug scabug commented Oct 18, 2012

One pattern used with macros is to have an unimplemented stub method that is valid only in the context of a macro and is then removed by that macro:

trait Task[T] {
  def value: T = ???
}

val foo: Task[Int] = ...

val t: Task[Int] = task {
  foo.value + 1
}

The problem is that use of value outside of the macro is not caught at compile time, but at runtime. For example, the following throws an exception at runtime because value is not used within the task macro that would otherwise remove the call to it.

val foo: Task[Int] = ...
val i: Int = foo.value

The proposed solution is to have an annotation or some other way of telling the compiler that calls to value that survive typechecking are an error. For example,

   @compileTimeOnly("Calls to `value` must be made inside the the task macro.")
   def value: T = ???

This would be useful for standard macro-related methods like splice as well.

@scabug
Copy link
Author

@scabug scabug commented Oct 18, 2012

Loading

@scabug
Copy link
Author

@scabug scabug commented Oct 18, 2012

@paulp said:
+1 for something like this.

Loading

@scabug
Copy link
Author

@scabug scabug commented Oct 18, 2012

Loading

@scabug
Copy link
Author

@scabug scabug commented Nov 5, 2012

@gkossakowski said:
Until that feature is implemented, what's the problem with using runtime exception contains the same error message as proposed for compile time error?

Loading

@scabug
Copy link
Author

@scabug scabug commented Nov 6, 2012

@retronym said:
As a workaround, you could deprecate that method.

  scala> @deprecated("must be enclosed in M.m") def foo = 0
  warning: there were 1 deprecation warnings; re-run with -deprecation for details
  foo: Int
  
  scala> import language.experimental.macros
  import language.experimental.macros
  
  scala> def mImpl(c: Context)(a: c.Expr[Any]) = c.universe.reify(())
  mImpl: (c: scala.reflect.macros.Context)(a: c.Expr[Any])c.universe.Expr[Unit]
  
  scala> def m(a: Any) = macro mImpl
  m: (a: Any)Unit
  
  scala> foo
  <console>:38: warning: method foo is deprecated: must be enclosed in M.m
                foo
                ^
  res5: Int = 0
  
  scala> m(foo)

Loading

@scabug
Copy link
Author

@scabug scabug commented Nov 6, 2012

@xeno-by said:
My current idea is to make value a macro itself, which checks whether it's enclosed by task. This completely satisfies the requirements of the feature request and doesn't require any additions to the compiler.

Loading

@scabug
Copy link
Author

@scabug scabug commented Nov 6, 2012

@retronym said:
The problem here is that when the inner macro explores its enclosing context to see if it is enclosed by the required outer macro, the outer macro is not yet typed. I discussed this with Eugene, and he agrees that we can go for the proposed annotation. We will include it under the reflect package, meaning it is experimental. If we can resolve this chicken/egg problem in a more elegant way down the track with a macro-only solution, it could be removed.

Loading

@scabug
Copy link
Author

@scabug scabug commented Nov 6, 2012

@harrah said:
Also, you may not know the name of the enclosing macro. In my case, the enclosing macro is viral in the sense that you find the need to use the syntax everywhere. So, you abstract the outer macro implementation so that you can call it from other macros.

Loading

@scabug
Copy link
Author

@scabug scabug commented Nov 6, 2012

@xeno-by said:
All this would be much simpler when we have untyped macros. Then expansion of arguments will be triggered explicitly from an enclosing macro, and that enclosing macro would probably be able to elide the calls to value, so that they don't expand into compilation errors.

Loading

@scabug
Copy link
Author

@scabug scabug commented Nov 6, 2012

@gkossakowski said:
I just realized that it looks like this can be implemented as a compiler plug-in easily, right? I think that's the best way to move forward with this.

Loading

@scabug
Copy link
Author

@scabug scabug commented Nov 6, 2012

@retronym said (edited on Nov 6, 2012 5:21:39 PM UTC):
That would negate a key benefits of macros: not needing to ship a compiler plugin.

Loading

@scabug
Copy link
Author

@scabug scabug commented Nov 6, 2012

@gkossakowski said:
Sure, but this is not meant as a long-term solution but a way to flesh out and stabilize the feature before it hits Scala repository. The advantage of Scala compiler plug-in is also that you can iterate more quickly than Scala releases which is useful when you work on a new feature.

Once annotation mechanism described in this ticket is used in the wild we can sit down and write SIP for it and consider the design for inclusion in Scala some release. WDYT?

Loading

@scabug
Copy link
Author

@scabug scabug commented Nov 6, 2012

@retronym said:
Mini-SIP:

  • Add annotation scala.reflect.macros.compileTimeOnly. One string constructor argument.
  • In the same place (RefChecks) and manner as deprecation warnings, issue an error.

Given that we can put the annotation under an experimental package, I don't buy the argument about allowing the feature to evolve before inclusion. Doubly so given that the impact on compiler complexity is so small.

Loading

@scabug
Copy link
Author

@scabug scabug commented Nov 6, 2012

@adriaanm said:
You make a compelling case, Sir Zaugg. Can we leverage that it's a generalization of deprecation?
As in, actually generalizing the deprecation mechanism. I'd rather not complicate the implementation/work involved,
but it's hard to resist exploring the elegance of that thought.

Loading

@scabug
Copy link
Author

@scabug scabug commented Nov 6, 2012

@retronym said:

-class deprecated(message: String = "", since: String = "") extends scala.annotation.StaticAnnotation
+class deprecated(message: String = "", since: String = "", fatal: Boolean = false) extends scala.annotation.StaticAnnotation

Loading

@scabug
Copy link
Author

@scabug scabug commented Nov 6, 2012

@xeno-by said (edited on Nov 6, 2012 8:10:17 PM UTC):
Guys I agree this is important to have, but I would like to suggest that we treat this feature as a temporary thing (i.e. no SIPs or long-term plans). Once we have untyped macros, there won't be any necessity in having the @compileTimeOnly annotation.

Loading

@scabug
Copy link
Author

@scabug scabug commented Nov 6, 2012

@retronym said:
Except: IDEs (at least, IntelliJ) will highlight the calls any deprecated method with scary a strike-through font, so I would prefer not to cram it into @deprecated.

Perhaps it could be a particular brand of macro annotation that executes in the context of the call-site, and is able to choose to do it's work post typer.

But I think we're trying too hard to find some beauty/generality here.

Loading

@scabug
Copy link
Author

@scabug scabug commented Nov 6, 2012

@gkossakowski said:
I agree with Jason. Just showing that implementation is similar to @deprecated is compelling enough.

Loading

@scabug
Copy link
Author

@scabug scabug commented Nov 6, 2012

Loading

@scabug
Copy link
Author

@scabug scabug commented Nov 6, 2012

@paulp said:
Spare me the angst of boolean parameters... if you are ever tempted to go that route, please prefer

class fatallyDeprecated(...) extends deprecated

Loading

@scabug
Copy link
Author

@scabug scabug commented Nov 14, 2012

@xeno-by said:
The discussed annotation has been merged: scala/scala@6902da3.

I'm not closing the bug yet, since the annotation is deemed to be a temporary measure until we have macro tools that will let us solve this problem without any changes to the compiler.

Loading

@scabug
Copy link
Author

@scabug scabug commented Jan 29, 2013

Loading

@scabug
Copy link
Author

@scabug scabug commented Jan 29, 2013

@xeno-by said:
I have an idea, which I would like to try. It involves removing @compileTimeOnly altogether, but first I need to experiment a bit (after I fix reflection sync).

Loading

@scabug
Copy link
Author

@scabug scabug commented Jan 29, 2013

@retronym said:
We could also make this work by making the compiler tolerant of the absence of this annotation. SBT could then need to provide the annotation, in it's source code. But I also look forward to your idea..

Loading

@scabug
Copy link
Author

@scabug scabug commented Jan 29, 2013

@xeno-by said:
As we discussed on the core meeting, Mark is okay with having the annotation in scala-compiler.jar. Therefore I think we should go for this option for now.

Loading

@scabug
Copy link
Author

@scabug scabug commented Jan 30, 2013

Loading

@scabug
Copy link
Author

@scabug scabug commented Feb 2, 2013

Loading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
2 participants