You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scala中如何用!符号自动生成非空断言?是否有简易宏?

Auto-Generating Null Checks with String! Syntax in Scala

Great question—repeating assert(name != null) for every non-nullable parameter gets tedious fast, and macros are exactly the tool to automate this. Here’s how to implement your desired String! syntax to auto-inject null checks:

For Scala 2

Scala 2 uses macro annotations (requires the Macro Paradise plugin) to modify class definitions at compile time. Here’s a step-by-step implementation:

1. Set Up Dependencies

First, add the necessary dependencies to your build.sbt:

libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value
addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full)

2. Define the Macro Annotation

This macro will scan your class constructor parameters, look for types ending with ! (like String!), and auto-generate null checks for them:

import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.reflect.macros.whitebox
import scala.language.experimental.macros

@compileTimeOnly("Enable Macro Paradise to expand this annotation")
class generateNullChecks extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro GenerateNullChecksMacro.impl
}

object GenerateNullChecksMacro {
  def impl(c: whitebox.Context)(annottees: c.Tree*): c.Tree = {
    import c.universe._

    annottees.head match {
      case classDef: ClassDef =>
        // Grab all constructor parameters
        val constructorParams = classDef.impl.body.collect {
          case ctor: DefDef if ctor.name == termNames.CONSTRUCTOR => ctor.vparamss.flatten
        }.headOption.getOrElse(Nil)

        // Generate assert statements for parameters with !-suffixed types
        val nullChecks = constructorParams.flatMap { param =>
          if (param.tpt.toString.endsWith("!")) {
            Some(q"assert(${param.name} != null, ${s"${param.name} must not be null"})")
          } else None
        }

        // Inject the checks into the constructor body
        val updatedClass = classDef.copy(
          impl = classDef.impl.copy(
            body = classDef.impl.body.map {
              case ctor: DefDef if ctor.name == termNames.CONSTRUCTOR =>
                ctor.copy(body = Block(nullChecks ++ ctor.body.children, ctor.body.expr))
              case other => other
            }
          )
        )

        c.untypecheck(updatedClass)
      case _ =>
        c.abort(c.enclosingPosition, "This annotation only works on classes")
    }
  }
}

3. Define the String! Type Alias

Since String! isn’t a native Scala type, define a simple alias to use as our marker:

type String! = String

4. Use It!

Annotate your class with @generateNullChecks and use String! for non-nullable parameters:

@generateNullChecks
class Person(val name: String!) {
  // The macro auto-injects: assert(name != null, "name must not be null")
}

For Scala 3

Scala 3’s metaprogramming is more streamlined (no extra plugins needed!) thanks to inline macros and the Quotes API. Here’s how to adapt the solution:

1. Implement the Inline Annotation

import scala.annotation.StaticAnnotation
import scala.quoted.*

class generateNullChecks extends StaticAnnotation:
  def transform(using Quotes)(tree: quotes.reflect.Definition): quotes.reflect.Definition =
    import quotes.reflect.*
    tree match
      case cls: ClassDef =>
        // Get the class constructor
        val constructor = cls.constructor.getOrElse(
          report.errorAndAbort("Class must have a constructor", cls.pos)
        )
        // Generate checks for !-suffixed parameters
        val nullChecks = constructor.paramss.flatten.flatMap { param =>
          if (param.tpt.tpe.show.endsWith("!")) {
            val paramName = param.name
            Some('{ assert(${Expr(paramName)} != null, ${Expr(s"$paramName must not be null")}).asTerm })
          } else None
        }
        // Update constructor with checks
        val updatedConstructor = constructor.copy(
          body = Some(Block(nullChecks, constructor.body.get))
        )
        cls.copy(constructor = Some(updatedConstructor))
      case _ =>
        report.errorAndAbort("Annotation only applies to classes", tree.pos)

2. Define the Type Alias

Same as Scala 2:

type String! = String

3. Use the Annotation

@generateNullChecks
class Person(val name: String!)
// Auto-generates: assert(name != null, "name must not be null") in the constructor

Notes & Alternatives

  • Type Safety: The String! alias is just a marker—if you want stricter compile-time checks, you could define a wrapper type (like case class NotNullString(value: String)) and use macros to auto-wrap/unwrap it, but that adds more complexity.
  • Scala 3 Option/NonNull: For native non-null handling, Scala 3 has NonNull types (via -Yexplicit-nulls flag), but that’s a different approach than your desired String! syntax.
  • Runtime Overhead: Assertions are enabled by default in Scala (disable with -Xdisable-assertions), so this adds minimal runtime cost when enabled.

内容的提问来源于stack exchange,提问作者Dmitry Nogin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 03:31:50