Basic macro for line number

Attempting to write a simple macro in scala-3 that returns the line number at the calling site and am running into some trouble. Most likely something simple I am missing.

Following are my two attempts and the resulting error messages.

class A {
  object Source {
    // ERROR: malformed macro
    // Expected the splice ${...} to contain a single call to a static method.
    inline def line = ${ lineImpl() } 

    def lineImpl()(using scala.quoted.Quotes): scala.quoted.Expr[Int] = {
      scala.quoted.Expr(scala.quoted.quotes.reflect.Position.ofMacroExpansion.startLine + 1)
    }
  }
}

I assumed Source.lineImpl was a static method and that there is only a single call. Maybe this has something to do with the nested object? My second attempt and error message:

object Source {
  inline def line = ${ lineImpl() } 
  def lineImpl()(using scala.quoted.Quotes): scala.quoted.Expr[Int] = {
    scala.quoted.Expr(scala.quoted.quotes.reflect.Position.ofMacroExpansion.startLine + 1)
  }
} 
class B {
  def fn(): Unit = {
    // ERROR: Cannot call macro method lineImpl defined in the same source file
    // This location contains code that was inlined from test.scala:(next line)
    val ln = Source.line
  }
}

Any suggestions?

1 Like

Maybe macros aren’t the right choice for this task? Is there a better way to obtain the line number from the compiler?

Thanks!

I implemented this stuff here if you’d like to use it or copy/paste.

My implementation is here.

import quoted.*

/** Simple macro, prints out string preceded by source code position. */
inline def deb(str: String): Unit = ${ debImpl('str) }
def debImpl(expr: Expr[String])(using Quotes) = '{ println($posnStrImpl + " " + $expr) }

/** Simplest Macro that shows source code position. Must include parenthesis debb(). Without the parenthesis the macro will not print. */
inline def debb(): Unit = ${ debbImpl }
def debbImpl(using Quotes) = '{ println($posnStrImpl) }

/** An expression debug macro. Prints out source code position followed by expression name, followed by expression value. */
inline def debvar(expr: Any): Unit = ${ debvarImpl('expr) }
def debvarImpl(expr: Expr[Any])(using Quotes) = '{ println($posnStrImpl + " " + ${Expr(expr.show)} + " = " + $expr) }

/** Macro for getting the file name and line number of the source code position. */
inline def posnStr(): String = ${ posnStrImpl }
def posnStrImpl(using Quotes): Expr[String] =
{ val pos = quotes.reflect.Position.ofMacroExpansion
  Expr(pos.sourceFile.getJPath.fold("")(_.toString) + ":" + pos.startLine)
}

The slightly more convoluted getJPath.fold("")(_.toString) stops the compiler complaining about the deprecation of jpath.

@tpolecat There doesn’t seem to be a way to get the editor to recognise the column of the source. Maybe its not particularly useful, but I would like to have included it for completeness if the editor didn’t ignore it.

If you only need a string for display purpose, you should use path, not getJPath.

@smarter Thanks corrected.