[Macro] How to traverse AST to match println and readLine?

346 views
Skip to first unread message

marvin...@gmail.com

unread,
Jun 20, 2013, 4:37:02 AM6/20/13
to scala...@googlegroups.com
Hi,

I'm  writing currently a  macro (Scala 2.10) to guard pure functions by disabling side effects.
This is an ongoing effort but so far, what's working is
1) Null / Nothing is disabled
2) Mutable fields (var) are disabled

At the moment, on my todo list are:
1) Console IO
2) File IO

For the long run, I'm looking forward to disable exception throwing as I prefer Option[T]
for handling unexpected results.

However, I'm not particular sure how to traverse the AST to match a method or
functions name from the standard library. My first idea was to search for  a
specific literal like so.

  /** FIXME check for console output */
    object NoPrintln extends Traverser {
      override def traverse(tree: Tree) {
        tree match {
          case Literal(Constant("println")) // Doesn't work :
            c.error(tree.pos, "println is disabledin a pure function")
          case _ =>
        }
        super.traverse(tree)
      }
    }
    NoPrintln.traverse(expr.tree)

Okay, that doesn't work. Next, I thought using a type tag may help but
accessing the type tag of println like so:

         def getTypeTag[T: ru.TypeTag](obj: T) = ru.typeTag[T]
        
         println("Type tag is: " + getTypeTag(println()).tpe.toString)

Returns, Unit, the return type.  Okay, lesson learned;

Currently, I think there should be a way by using a MethodSymbol but while reading the documentation,
I was not really able to understand how to apply it to my case of traversing the AST
to match for a particular method name.


Any advice?

Thank you for any help
m







Roman Janusz

unread,
Jun 20, 2013, 10:38:42 AM6/20/13
to scala...@googlegroups.com
How about something like this?

def detectPrintln_impl(c: Context)(expr: c.Expr[Any]) = {
  import c.universe._

  // list containing symbols representing all overloaded variants of method "println" from Predef
  val printlnSymbols = typeOf[Predef.type].member(newTermName("println")).asTerm.alternatives

  expr.tree.find { subtree => printlnSymbols contains subtree.symbol } match {
    case Some(subtree) => c.error(subtree.pos, "You cannot use println")
    case None => ()
  }
 
  expr
}

Johannes Rudolph

unread,
Jun 20, 2013, 10:50:57 AM6/20/13
to Roman Janusz, scala-user
You can use universe.showRaw to print out the AST that gets passed into your macro.


--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 



--
Johannes

-----------------------------------------------
Johannes Rudolph
http://virtual-void.net

marvin...@gmail.com

unread,
Jun 21, 2013, 12:38:06 AM6/21/13
to scala...@googlegroups.com, Roman Janusz, johannes...@googlemail.com
Thank you,

that worked well. I've implemented checks for console IO and file IO.
In case someone is interested, code is on github:

https://github.com/marvin-hansen/Scalarius

However, I have one more question. Spotting IO streams efficiently
would require to detect the shared super-type such as InputStream.
Or maybe the other way around, can I find all implementations of
an interface?

Is there a way to do that in Macros?

I'm asking because it's kinda tedious to match each and every
potential IO stream. Same applies to Exceptions, there are
plenty of them but all I need is to match the super type shared amongst
all exceptions?

Any advise?

Thank you

Roman Janusz

unread,
Jun 21, 2013, 4:44:39 AM6/21/13
to scala...@googlegroups.com, Roman Janusz, johannes...@googlemail.com
Quick hint: you can use allOverriddenSymbols method on a symbol. I don't have time now to elaborate on this, but I think its usage should be straightforward.

marvin...@gmail.com

unread,
Jun 21, 2013, 4:56:41 AM6/21/13
to scala...@googlegroups.com, Roman Janusz, johannes...@googlemail.com
Thank you,

I'm reading through the ScalaDoc right now and it seems like it does the job I need. I give it
a try tomorrow morning;

Overall, I'm pretty impressed how straightforward reflection in Scala is. 

Marvin Hansen

unread,
Jun 22, 2013, 12:47:59 AM6/22/13
to scala...@googlegroups.com
Well,

I'm not so sure how to use that. A simple:

 val excSymb = typeOf[scala.type].member(newTermName("Exception")).allOverriddenSymbols

throws a type mismatch error:

[error] found : scala.type [error] required: AnyRef [error] Note that scala extends Any, not AnyRef. [error] Such types can participate in value classes, but instances [error] cannot appear in singleton types or in reference comparisons.


Accessing it through the context gives even more errors. 
I've googled around but wasn't able to figure out how to get a list of
all overriden symbols for
Exception.

Any advice? 

Thank you



marvin


You received this message because you are subscribed to a topic in the Google Groups "scala-user" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/scala-user/o0gm-KM-Wu0/unsubscribe.
To unsubscribe from this group and all its topics, send an email to scala-user+...@googlegroups.com.

Eugene Burmako

unread,
Jun 22, 2013, 7:12:10 AM6/22/13
to Marvin Hansen, scala-user
scala.type is not a valid Scala expression, because scala is not a value, but rather a package. But in any case, allOverriddenSymbols wouldn't work, because subtyping is not overriding. Please use sym.asClass.baseClasses instead.

Marvin Hansen

unread,
Jun 24, 2013, 3:20:47 AM6/24/13
to scala...@googlegroups.com, Eugene Burmako
Well,

I've found a super  simple solution by using generalized type constraints
like so: stat.tpe <:< typeOf[Exception]

However, for whatever reason, my custom traverser does not inspect val's and else-if branches....

My test case looks like this

 class ChuchNorrisException extends Exception
  def exceptionTest(x : Int) = pure {
   
   // detected
    new NumberFormatException
    new RuntimeException
    new ChuchNorrisException
 
 // not detected but it's ignorable...
 val rfe = new RuntimeException

    if (x < 2) {x * x}     // NOT detected
    else if(x < 42){   throw new NumberFormatException}
    else if(x > 23 ){  throw new ChuchNorrisException }
    else { throw new Exception }
 }

The check looks like this:

 def checkExceptions(statements: List[Tree]){
        statements.foreach {
          stat =>
           val isExc: Boolean = stat.tpe <:< typeOf[Exception]
            if(isExc){
              c.error(stat.pos, excErrMsg)
            }
        }
      }

It really all comes down to the simple sub-type relation check stat.tpe <:< typeOf[T] where T is supposed to be the super-type.
Oddly, the traverser does not even enter if & else if statements.

It could be something wrong with pattern matching because I don't get any statements in branches matched
with my current traverser.

     override def traverse(tree: Tree) {
        tree match {

          case Block(statements,_) =>
             println(statements.toString()) // This confirms that branches are skipped
            checkExceptions(statements)
          case _ =>
        }
        super.traverse(tree)
      }
    }
    NoException.traverse(expr.tree)


 I admit I do  something wrong here....

Any idea how do I get all branches covered?


Thank you.


marvin


On 24 June 2013 03:52, Eugene Burmako <eugene....@epfl.ch> wrote:
Sure, just wanted to suggest that to you. You can take a symbol, figure out its baseClasses and then check whether the result contains the symbol of Exception.


On 23 June 2013 06:36, Marvin Hansen <marvin...@gmail.com> wrote:
That's very interesting.

Thank you for the important detail, I just realized I've asked the wrong way.
Essentially, what I was looking for was a compact way to traverse all kind of exceptions using just one pattern. 
Instead of searching for each and every possible exception, I was just looking for a way to get them all.

So I though grabbing all subtypes of the shared supertype Exception would be a good starting point
but as you say, there is no way to do that at the moment. That's okay. 

On the other hand, can I match if a symbol contains supertype Exception?
That would solve the issue the other way around, which would be equally valid.

I don't have to solve that at the moment so I thing its best to leave it for later.


Thank you anyway, that was incredible useful for me.




marvin


On 23 June 2013 16:21, Eugene Burmako <eugene....@epfl.ch> wrote:
baseClass is supposed to return all supertypes of Exception, not all subtypes (btw there's no way of doing the latter in the reflection API):

scala> typeOf[Exception].typeSymbol.asClass.baseClasses
res0: List[reflect.runtime.universe.Symbol] = List(class Exception, class Throwable, trait Serializable, class Object, class Any)



On 23 June 2013 04:27, Marvin Hansen <marvin...@gmail.com> wrote:
Well,

your right, that makes sense. However, trying to get the sub-types of excections like so:

     val excSymb = typeOf[Exception].typeSymbol.asClass.baseClasses
     //
     expr.tree.find {
      subtree => excSymb  contains subtree.symbol
     } match {
         case Some(subtree) => c.error(subtree.pos, "Error: Exceptions are disabled" )
        case None => ()
    }

does not return anything.  Is type symbol the right path to go?

Thanks for any advice


marvin

Jason Zaugg

unread,
Jun 24, 2013, 5:18:54 AM6/24/13
to Marvin Hansen, scala-user, Eugene Burmako
On Mon, Jun 24, 2013 at 9:20 AM, Marvin Hansen <marvin...@gmail.com> wrote:

     override def traverse(tree: Tree) {
        tree match {

          case Block(statements,_) =>
             println(statements.toString()) // This confirms that branches are skipped
            checkExceptions(statements)
          case _ =>
        }
        super.traverse(tree)
      }
    }
    NoException.traverse(expr.tree)

I haven't followed this thread, but I suspect you'll need to add `statements foreach super.traverse` after the call to `checkExeptions` if you want the traversal to descend further. Same for the `case _`.

-jason 

Reply all
Reply to author
Forward
0 new messages