From a0001fcfd06fc2eca9ee965aeb766c832797aa9e Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Wed, 21 Nov 2012 14:01:04 +0100 Subject: Adds a margin stripping string interpolator. Currently only for compiler internal use. Designed to avoid surprises if the interpolated values themselves contain the margin delimiter. Before: val bip = "\n |.." s"""fooo |bar $bip |baz""".stripMargin "fooo bar .. baz" After: sm"""fooo |bar $bip |baz""" "fooo bar |.. baz" --- .../scala/reflect/internal/SymbolTable.scala | 6 ++++ .../internal/util/StripMarginInterpolator.scala | 40 +++++++++++++++++++++ .../scala/reflect/internal/util/package.scala | 9 +++++ test/files/run/sm-interpolator.scala | 41 ++++++++++++++++++++++ 4 files changed, 96 insertions(+) create mode 100644 src/reflect/scala/reflect/internal/util/StripMarginInterpolator.scala create mode 100644 src/reflect/scala/reflect/internal/util/package.scala create mode 100644 test/files/run/sm-interpolator.scala diff --git a/src/reflect/scala/reflect/internal/SymbolTable.scala b/src/reflect/scala/reflect/internal/SymbolTable.scala index 554acf9c0b..02ac59a461 100644 --- a/src/reflect/scala/reflect/internal/SymbolTable.scala +++ b/src/reflect/scala/reflect/internal/SymbolTable.scala @@ -345,6 +345,12 @@ abstract class SymbolTable extends macros.Universe /** Is this symbol table a part of a compiler universe? */ def isCompilerUniverse = false + + /** + * Adds the `sm` String interpolator to a [[scala.StringContext]]. + */ + implicit val StringContextStripMarginOps: StringContext => StringContextStripMarginOps = util.StringContextStripMarginOps + } object SymbolTableStats { diff --git a/src/reflect/scala/reflect/internal/util/StripMarginInterpolator.scala b/src/reflect/scala/reflect/internal/util/StripMarginInterpolator.scala new file mode 100644 index 0000000000..e7579229b2 --- /dev/null +++ b/src/reflect/scala/reflect/internal/util/StripMarginInterpolator.scala @@ -0,0 +1,40 @@ +package scala.reflect +package internal +package util + +trait StripMarginInterpolator { + def stringContext: StringContext + + /** + * A safe combination of `[[scala.collection.immutable.StringLike#stripMargin]] + * and [[scala.StringContext#raw]]. + * + * The margin of each line is defined by whitespace leading up to a '|' character. + * This margin is stripped '''before''' the arguments are interpolated into to string. + * + * String escape sequences are '''not''' processed; this interpolater is designed to + * be used with triple quoted Strings. + * + * {{{ + * scala> val foo = "f|o|o" + * foo: String = f|o|o + * scala> sm"""|${foo} + * |""" + * res0: String = + * "f|o|o + * " + * }}} + */ + final def sm(args: Any*): String = { + def isLineBreak(c: Char) = c == '\n' || c == '\f' // compatible with StringLike#isLineBreak + def stripTrailingPart(s: String) = { + val (pre, post) = s.span(c => !isLineBreak(c)) + pre + post.stripMargin + } + val stripped: List[String] = stringContext.parts.toList match { + case head :: tail => head.stripMargin :: (tail map stripTrailingPart) + case Nil => Nil + } + new StringContext(stripped: _*).raw(args: _*) + } +} diff --git a/src/reflect/scala/reflect/internal/util/package.scala b/src/reflect/scala/reflect/internal/util/package.scala new file mode 100644 index 0000000000..6d77235db6 --- /dev/null +++ b/src/reflect/scala/reflect/internal/util/package.scala @@ -0,0 +1,9 @@ +package scala.reflect +package internal + +package object util { + /** + * Adds the `sm` String interpolator to a [[scala.StringContext]]. + */ + implicit class StringContextStripMarginOps(val stringContext: StringContext) extends StripMarginInterpolator +} diff --git a/test/files/run/sm-interpolator.scala b/test/files/run/sm-interpolator.scala new file mode 100644 index 0000000000..7f7b9f061a --- /dev/null +++ b/test/files/run/sm-interpolator.scala @@ -0,0 +1,41 @@ +object Test extends App { + import scala.reflect.internal.util.StringContextStripMarginOps + def check(actual: Any, expected: Any) = if (actual != expected) sys.error(s"expected: [$expected], actual: [$actual])") + + val bar = "|\n ||" + + check( + sm"""|ab + |de + |${bar} | ${1}""", + "ab \nde\n|\n || | 1") + + check( + sm"|", + "") + + check( + sm"${0}", + "0") + + check( + sm"${0}", + "0") + + check( + sm"""${0}|${1} + |""", + "0|1\n") + + check( + sm""" ||""", + "|") + + check( + sm""" ${" "} ||""", + " ||") + + check( + sm"\n", + raw"\n".stripMargin) +} -- cgit v1.2.3 From 8fcbee5e2d0fcff2f42c94b9ecf6599e586c8166 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Wed, 21 Nov 2012 14:21:46 +0100 Subject: Take advantage of the margin stripping interpolator. Safer and shorter. --- src/compiler/scala/reflect/reify/Errors.scala | 8 +++---- .../scala/tools/nsc/transform/Erasure.scala | 18 +++++++------- .../tools/nsc/typechecker/ContextErrors.scala | 28 +++++++++++----------- .../scala/tools/nsc/typechecker/Implicits.scala | 16 ++++++------- .../scala/tools/nsc/typechecker/RefChecks.scala | 4 ++-- .../tools/nsc/typechecker/SuperAccessors.scala | 4 ++-- .../scala/tools/nsc/typechecker/TreeCheckers.scala | 8 +++---- .../scala/tools/nsc/typechecker/Typers.scala | 4 ++-- .../scala/reflect/runtime/JavaMirrors.scala | 17 +++++++------ 9 files changed, 52 insertions(+), 55 deletions(-) diff --git a/src/compiler/scala/reflect/reify/Errors.scala b/src/compiler/scala/reflect/reify/Errors.scala index 3d7cb95792..7c66d5b9eb 100644 --- a/src/compiler/scala/reflect/reify/Errors.scala +++ b/src/compiler/scala/reflect/reify/Errors.scala @@ -33,10 +33,10 @@ trait Errors { } def CannotConvertManifestToTagWithoutScalaReflect(tpe: Type, manifestInScope: Tree) = { - val msg = s""" - |to create a type tag here, it is necessary to interoperate with the manifest `$manifestInScope` in scope. - |however manifest -> typetag conversion requires Scala reflection, which is not present on the classpath. - |to proceed put scala-reflect.jar on your compilation classpath and recompile.""".trim.stripMargin + val msg = + sm"""to create a type tag here, it is necessary to interoperate with the manifest `$manifestInScope` in scope. + |however manifest -> typetag conversion requires Scala reflection, which is not present on the classpath. + |to proceed put scala-reflect.jar on your compilation classpath and recompile.""" throw new ReificationException(defaultErrorPosition, msg) } diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala index 7b45b3efe5..92743919ec 100644 --- a/src/compiler/scala/tools/nsc/transform/Erasure.scala +++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala @@ -437,19 +437,19 @@ abstract class Erasure extends AddInterfaces noclash = false unit.error( if (member.owner == root) member.pos else root.pos, - s"""bridge generated for member ${fulldef(member)} - |which overrides ${fulldef(other)} - |clashes with definition of $what; - |both have erased type ${afterPostErasure(bridge.tpe)}""".stripMargin) + sm"""bridge generated for member ${fulldef(member)} + |which overrides ${fulldef(other)} + |clashes with definition of $what; + |both have erased type ${afterPostErasure(bridge.tpe)}""") } for (bc <- root.baseClasses) { if (settings.debug.value) afterPostErasure(println( - s"""check bridge overrides in $bc - ${bc.info.nonPrivateDecl(bridge.name)} - ${site.memberType(bridge)} - ${site.memberType(bc.info.nonPrivateDecl(bridge.name) orElse IntClass)} - ${(bridge.matchingSymbol(bc, site))}""".stripMargin)) + sm"""check bridge overrides in $bc + |${bc.info.nonPrivateDecl(bridge.name)} + |${site.memberType(bridge)} + |${site.memberType(bc.info.nonPrivateDecl(bridge.name) orElse IntClass)} + |${(bridge.matchingSymbol(bc, site))}""")) def overriddenBy(sym: Symbol) = sym.matchingSymbol(bc, site).alternatives filter (sym => !sym.isBridge) diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index 9e9b8b995b..faacb60d75 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala @@ -107,9 +107,9 @@ trait ContextErrors { s"$name extends Any, not AnyRef" ) if (isPrimitiveValueType(found) || isTrivialTopType(tp)) "" else "\n" + - s"""|Note that $what. - |Such types can participate in value classes, but instances - |cannot appear in singleton types or in reference comparisons.""".stripMargin + sm"""|Note that $what. + |Such types can participate in value classes, but instances + |cannot appear in singleton types or in reference comparisons.""" } import ErrorUtils._ @@ -1128,9 +1128,9 @@ trait ContextErrors { (isView: Boolean, pt: Type, tree: Tree)(implicit context0: Context) = { if (!info1.tpe.isErroneous && !info2.tpe.isErroneous) { def coreMsg = - s"""| $pre1 ${info1.sym.fullLocationString} of type ${info1.tpe} - | $pre2 ${info2.sym.fullLocationString} of type ${info2.tpe} - | $trailer""".stripMargin + sm"""| $pre1 ${info1.sym.fullLocationString} of type ${info1.tpe} + | $pre2 ${info2.sym.fullLocationString} of type ${info2.tpe} + | $trailer""" def viewMsg = { val found :: req :: _ = pt.typeArgs def explanation = { @@ -1141,19 +1141,19 @@ trait ContextErrors { // involving Any, are further explained from foundReqMsg. if (AnyRefClass.tpe <:< req) ( if (sym == AnyClass || sym == UnitClass) ( - s"""|Note: ${sym.name} is not implicitly converted to AnyRef. You can safely - |pattern match `x: AnyRef` or cast `x.asInstanceOf[AnyRef]` to do so.""".stripMargin + sm"""|Note: ${sym.name} is not implicitly converted to AnyRef. You can safely + |pattern match `x: AnyRef` or cast `x.asInstanceOf[AnyRef]` to do so.""" ) else boxedClass get sym map (boxed => - s"""|Note: an implicit exists from ${sym.fullName} => ${boxed.fullName}, but - |methods inherited from Object are rendered ambiguous. This is to avoid - |a blanket implicit which would convert any ${sym.fullName} to any AnyRef. - |You may wish to use a type ascription: `x: ${boxed.fullName}`.""".stripMargin + sm"""|Note: an implicit exists from ${sym.fullName} => ${boxed.fullName}, but + |methods inherited from Object are rendered ambiguous. This is to avoid + |a blanket implicit which would convert any ${sym.fullName} to any AnyRef. + |You may wish to use a type ascription: `x: ${boxed.fullName}`.""" ) getOrElse "" ) else - s"""|Note that implicit conversions are not applicable because they are ambiguous: - |${coreMsg}are possible conversion functions from $found to $req""".stripMargin + sm"""|Note that implicit conversions are not applicable because they are ambiguous: + |${coreMsg}are possible conversion functions from $found to $req""" } typeErrorMsg(found, req, infer.isPossiblyMissingArgs(found, req)) + ( if (explanation == "") "" else "\n" + explanation diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index 547d756888..fc10f68454 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala @@ -1306,17 +1306,17 @@ trait Implicits { else { if (ReflectRuntimeUniverse == NoSymbol) { // todo. write a test for this - context.error(pos, s""" - |to create a manifest here, it is necessary to interoperate with the type tag `$tagInScope` in scope. - |however typetag -> manifest conversion requires Scala reflection, which is not present on the classpath. - |to proceed put scala-reflect.jar on your compilation classpath and recompile.""".trim.stripMargin) + context.error(pos, + sm"""to create a manifest here, it is necessary to interoperate with the type tag `$tagInScope` in scope. + |however typetag -> manifest conversion requires Scala reflection, which is not present on the classpath. + |to proceed put scala-reflect.jar on your compilation classpath and recompile.""") return SearchFailure } if (resolveClassTag(pos, tp, allowMaterialization = true) == EmptyTree) { - context.error(pos, s""" - |to create a manifest here, it is necessary to interoperate with the type tag `$tagInScope` in scope. - |however typetag -> manifest conversion requires a class tag for the corresponding type to be present. - |to proceed add a class tag to the type `$tp` (e.g. by introducing a context bound) and recompile.""".trim.stripMargin) + context.error(pos, + sm"""to create a manifest here, it is necessary to interoperate with the type tag `$tagInScope` in scope. + |however typetag -> manifest conversion requires a class tag for the corresponding type to be present. + |to proceed add a class tag to the type `$tp` (e.g. by introducing a context bound) and recompile.""") return SearchFailure } val cm = typed(Ident(ReflectRuntimeCurrentMirror)) diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index ee7805cb3d..78ec6508ed 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -1379,8 +1379,8 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans private def checkCompileTimeOnly(sym: Symbol, pos: Position) = { if (sym.isCompileTimeOnly) { def defaultMsg = - s"""|Reference to ${sym.fullLocationString} should not have survived past type checking, - |it should have been processed and eliminated during expansion of an enclosing macro.""".stripMargin + sm"""Reference to ${sym.fullLocationString} should not have survived past type checking, + |it should have been processed and eliminated during expansion of an enclosing macro.""" // The getOrElse part should never happen, it's just here as a backstop. unit.error(pos, sym.compileTimeOnlyMessage getOrElse defaultMsg) } diff --git a/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala b/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala index c9e45b6348..fb8a111da1 100644 --- a/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala @@ -527,8 +527,8 @@ abstract class SuperAccessors extends transform.Transform with transform.TypingT } def isJavaProtected = host.isTrait && sym.isJavaDefined && { restrictionError(pos, unit, - s"""|$clazz accesses protected $sym inside a concrete trait method. - |Add an accessor in a class extending ${sym.enclClass} as a workaround.""".stripMargin + sm"""$clazz accesses protected $sym inside a concrete trait method. + |Add an accessor in a class extending ${sym.enclClass} as a workaround.""" ) true } diff --git a/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala b/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala index b4cdad70e2..48a5a36b00 100644 --- a/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/TreeCheckers.scala @@ -280,11 +280,9 @@ abstract class TreeCheckers extends Analyzer { if (sym.owner != currentOwner) { val expected = currentOwner.ownerChain find (x => cond(x)) getOrElse fail("DefTree can't find owner: ") if (sym.owner != expected) - fail("""| - | currentOwner chain: %s - | symbol chain: %s""".stripMargin.format( - currentOwner.ownerChain take 3 mkString " -> ", - sym.ownerChain mkString " -> ") + fail(sm"""| + | currentOwner chain: ${currentOwner.ownerChain take 3 mkString " -> "} + | symbol chain: ${sym.ownerChain mkString " -> "}""" ) } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index d3847de894..034de203c9 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -4863,12 +4863,12 @@ trait Typers extends Modes with Adaptations with Tags { defSym = pre.member(defEntry.sym.name) if (defSym ne defEntry.sym) { qual = gen.mkAttributedQualifier(pre) - log(s""" + log(sm""" | !!! Overloaded package object member resolved incorrectly. | prefix: $pre | Discarded: ${defEntry.sym.defString} | Using: ${defSym.defString} - """.stripMargin) + """) } } else diff --git a/src/reflect/scala/reflect/runtime/JavaMirrors.scala b/src/reflect/scala/reflect/runtime/JavaMirrors.scala index 2d08cd887b..ab93d7033a 100644 --- a/src/reflect/scala/reflect/runtime/JavaMirrors.scala +++ b/src/reflect/scala/reflect/runtime/JavaMirrors.scala @@ -129,11 +129,10 @@ private[reflect] trait JavaMirrors extends internal.SymbolTable with api.JavaUni private def ErrorStaticModule(sym: Symbol) = throw new ScalaReflectionException(s"$sym is a static module, use reflectModule on a RuntimeMirror to obtain its ModuleMirror") private def ErrorNotMember(sym: Symbol, owner: Symbol) = throw new ScalaReflectionException(s"expected a member of $owner, you provided ${sym.kindString} ${sym.fullName}") private def ErrorNotField(sym: Symbol) = throw new ScalaReflectionException(s"expected a field or an accessor method symbol, you provided $sym") - private def ErrorNonExistentField(sym: Symbol) = throw new ScalaReflectionException(s""" - |Scala field ${sym.name} isn't represented as a Java field, neither it has a Java accessor method - |note that private parameters of class constructors don't get mapped onto fields and/or accessors, - |unless they are used outside of their declaring constructors. - """.trim.stripMargin) + private def ErrorNonExistentField(sym: Symbol) = throw new ScalaReflectionException( + sm"""Scala field ${sym.name} isn't represented as a Java field, neither it has a Java accessor method + |note that private parameters of class constructors don't get mapped onto fields and/or accessors, + |unless they are used outside of their declaring constructors.""") private def ErrorSetImmutableField(sym: Symbol) = throw new ScalaReflectionException(s"cannot set an immutable field ${sym.name}") private def ErrorNotConstructor(sym: Symbol, owner: Symbol) = throw new ScalaReflectionException(s"expected a constructor of $owner, you provided $sym") private def ErrorFree(member: Symbol, freeType: Symbol) = throw new ScalaReflectionException(s"cannot reflect ${member.kindString} ${member.name}, because it's a member of a weak type ${freeType.name}") @@ -541,8 +540,8 @@ private[reflect] trait JavaMirrors extends internal.SymbolTable with api.JavaUni val result = anns find (_.annotationType == annotClass) if (result.isEmpty && (anns exists (_.annotationType.getName == name))) throw new ClassNotFoundException( - s"""Mirror classloader mismatch: $jclazz (loaded by ${ReflectionUtils.show(jclazz.getClassLoader)}) - |is unrelated to the mirror's classloader: (${ReflectionUtils.show(classLoader)})""".stripMargin) + sm"""Mirror classloader mismatch: $jclazz (loaded by ${ReflectionUtils.show(jclazz.getClassLoader)}) + |is unrelated to the mirror's classloader: (${ReflectionUtils.show(classLoader)})""") result } def loadBytes[T: ClassTag](name: String): Option[T] = @@ -955,8 +954,8 @@ private[reflect] trait JavaMirrors extends internal.SymbolTable with api.JavaUni javaTypeToValueClass(jclazz) orElse lookupClass assert (cls.isType, - s"""${if (cls == NoSymbol) "not a type: symbol" else "no symbol could be"} - | loaded from $jclazz in $owner with name $simpleName and classloader $classLoader""".stripMargin) + sm"""${if (cls == NoSymbol) "not a type: symbol" else "no symbol could be"} + | loaded from $jclazz in $owner with name $simpleName and classloader $classLoader""") cls.asClass } -- cgit v1.2.3