From 10aba6a2cd81a14663e08f03b78e04828482dd1b Mon Sep 17 00:00:00 2001 From: spoon Date: Mon, 18 Feb 2008 21:22:08 +0000 Subject: [PATCH] Generalizes the allowed inputs to the interpreter to be any combination of phrases that can be placed inside a template. Instead of having one "request" object try to understand the entire combination of phrases, the code now uses multiple "member handlers" for each request, one member handler for each phrase. git-svn-id: http://lampsvn.epfl.ch/svn-repos/scala/scala/trunk@14054 5e8d7ff9-d8ef-0310-90f0-a4852d11357a --- .../scala/tools/nsc/Interpreter.scala | 522 ++++++++---------- .../scala/tools/nsc/symtab/StdNames.scala | 1 + test/files/run/interpreter.check | 22 +- test/files/run/interpreter.scala | 8 + 4 files changed, 274 insertions(+), 279 deletions(-) diff --git a/src/compiler/scala/tools/nsc/Interpreter.scala b/src/compiler/scala/tools/nsc/Interpreter.scala index 254c30708..ba79b9a8d 100644 --- a/src/compiler/scala/tools/nsc/Interpreter.scala +++ b/src/compiler/scala/tools/nsc/Interpreter.scala @@ -10,6 +10,7 @@ import java.io.{File, PrintWriter, StringWriter, Writer} import java.lang.{Class, ClassLoader} import java.net.{URL, URLClassLoader} +import scala.collection.immutable.ListSet import scala.collection.mutable import scala.collection.mutable.{ListBuffer, HashSet, ArrayBuffer} @@ -78,6 +79,8 @@ class Interpreter(val settings: Settings, out: PrintWriter) { import compiler.CompilationUnit import compiler.{Symbol,Name,Type} import compiler.nme + import compiler.newTermName + import compiler.nme.{INTERPRETER_VAR_PREFIX, INTERPRETER_SYNTHVAR_PREFIX} import Interpreter.string2code /** construct an interpreter that reports to Console */ @@ -181,9 +184,27 @@ class Interpreter(val settings: Settings, out: PrintWriter) { private def newVarName() = { val num = nextVarNameNo nextVarNameNo += 1 - compiler.nme.INTERPRETER_VAR_PREFIX + num + INTERPRETER_VAR_PREFIX + num } + /** next internal variable number to use */ + private var nextInternalVarNo = 0 + + /** allocate a fresh internal variable name */ + private def newInternalVarName() = { + val num = nextVarNameNo + nextVarNameNo += 1 + INTERPRETER_SYNTHVAR_PREFIX + num + } + + + /** Check if a name looks like it was generated by newVarName */ + private def isGeneratedVarName(name: String): Boolean = + name.startsWith(INTERPRETER_VAR_PREFIX) && { + val suffix = name.drop(INTERPRETER_VAR_PREFIX.length) + suffix.forall(_.isDigit) + } + /** generate a string using a routine that wants to write on a stream */ private def stringFrom(writer: PrintWriter => Unit): String = { @@ -259,34 +280,31 @@ class Interpreter(val settings: Settings, out: PrintWriter) { * should be taken. Removes requests which cannot contribute * useful imports for the specified set of wanted names. */ - def reqsToUse: List[Request] = { - /** Loop through the requests in reverse and select + def reqsToUse: List[(Request,MemberHandler)] = { + /** Loop through a list of MemberHandlers and select * which ones to keep. 'wanted' is the set of * names that need to be imported, and * 'shadowed' is the list of names useless to import * because a later request will re-import it anyway. */ - def select(reqs: List[Request], wanted: Set[Name]): List[Request] = { + def select(reqs: List[(Request,MemberHandler)], wanted: Set[Name]): + List[(Request,MemberHandler)] = { reqs match { case Nil => Nil - case req::rest => - val keepit = req.definesImplicit || (req match { - case req:ImportReq => - req.importsWildcard || - req.importedNames.exists(wanted.contains) - case _ => - req.boundNames.exists(wanted.contains) - }) + case (req,handler)::rest => + val keepit = + (handler.definesImplicit || + handler.importsWildcard || + handler.importedNames.exists(wanted.contains(_)) || + handler.boundNames.exists(wanted.contains(_))) val newWanted = if (keepit) { - req match { - case req:ImportReq => - wanted -- req.importedNames ++ req.usedNames - - case _ => wanted -- req.boundNames - } + (wanted + ++ handler.usedNames + -- handler.boundNames + -- handler.importedNames) } else { wanted } @@ -294,13 +312,18 @@ class Interpreter(val settings: Settings, out: PrintWriter) { val restToKeep = select(rest, newWanted) if(keepit) - req :: restToKeep + (req,handler) :: restToKeep else restToKeep } } - select(prevRequests.toList.reverse, wanted).reverse + val rhpairs = for { + req <- prevRequests.toList.reverse + handler <- req.handlers + } yield (req, handler) + + select(rhpairs, wanted).reverse } val code = new StringBuffer @@ -321,40 +344,37 @@ class Interpreter(val settings: Settings, out: PrintWriter) { // loop through previous requests, adding imports // for each one - for (req <- reqsToUse) { - req match { - case req:ImportReq => + for ((req,handler) <- reqsToUse) { // If the user entered an import, then just use it // add an import wrapping level if the import might // conflict with some other import - if(req.importsWildcard || - currentImps.exists(req.importedNames.contains)) + if(handler.importsWildcard || + currentImps.exists(handler.importedNames.contains)) if(!currentImps.isEmpty) addWrapper() - - code.append(req.line + ";\n") + + if (handler.member.isInstanceOf[Import]) + code.append(handler.member.toString + ";\n") // give wildcard imports a import wrapper all to their own - if(req.importsWildcard) + if(handler.importsWildcard) addWrapper() else - currentImps ++= req.importedNames + currentImps ++= handler.importedNames - case req => // For other requests, import each bound variable. // import them explicitly instead of with _, so that // ambiguity errors will not be generated. Also, quote // the name of the variable, so that we don't need to // handle quoting keywords separately. - for (imv <- req.boundNames) { + for (imv <- handler.boundNames) { if (currentImps.contains(imv)) addWrapper() code.append("import ") code.append(req.objectName + req.accessPath + ".`" + imv + "`;\n") currentImps += imv } - } } addWrapper() // Add one extra wrapper, to prevent warnings @@ -411,26 +431,20 @@ class Interpreter(val settings: Settings, out: PrintWriter) { * after being parsed. */ private def buildRequest(trees: List[Tree], line: String, lineName: String): Request = - trees match { - /* This case for assignments is more specialized than desirable: it only - handles assignments to an identifier. It would be better to support - arbitrary paths being assigned, but that is technically difficult - because of the way objectSourceCode and resultObjectSourceCode are - implemented in class Request. */ - case List(Assign(Ident(lhs), _)) => - new AssignReq(lhs, line, lineName) - case _ if trees.forall(t => t.isInstanceOf[ValOrDefDef]) => - new DefReq(line, lineName) - case List(_:TermTree) | List(_:Ident) | List(_:Select) => - new ExprReq(line, lineName) - case List(_:ModuleDef) => new ModuleReq(line, lineName) - case List(_:ClassDef) => new ClassReq(line, lineName) - case List(t:TypeDef) if compiler.treeInfo.isAliasTypeDef(t) => - new TypeAliasReq(line, lineName) - case List(_:Import) => new ImportReq(line, lineName) - case _ => - reporter.error(null, "That kind of statement combination is not supported by the interpreter.") - null + new Request(line, lineName) + + private def chooseHandler(member: Tree): Option[MemberHandler] = + member match { + case member: DefDef => + Some(new DefHandler(member)) + case member: ValDef => + Some(new ValHandler(member)) + case member@Assign(Ident(_), _) => Some(new AssignHandler(member)) + case member: ModuleDef => Some(new ModuleHandler(member)) + case member: ClassDef => Some(new ClassHandler(member)) + case member: TypeDef => Some(new TypeAliasHandler(member)) + case member: Import => Some(new ImportHandler(member)) + case _ => None } /**

@@ -458,6 +472,19 @@ class Interpreter(val settings: Settings, out: PrintWriter) { case Some(trees) => trees } + trees match { + case List(_:Assign) => () + + case List(_:TermTree) | List(_:Ident) | List(_:Select) => + // Treat a single bare expression specially. + // This is necessary due to it being hard to modify + // code at a textual level, and it being hard to + // submit an AST to the compiler. + return interpret("val "+newVarName()+" = \n"+line) + + case _ => () + } + val lineName = newLineName // figure out what kind of request @@ -472,10 +499,6 @@ class Interpreter(val settings: Settings, out: PrintWriter) { if (printResults || !succeeded) { // print the result out.print(clean(interpreterResultString)) - - // print out types of functions; they are not printed in the - // request printout - out.print(clean(req.defTypesSummary)) } // book-keeping @@ -556,8 +579,159 @@ class Interpreter(val settings: Settings, out: PrintWriter) { } + /** Class to handle one member among all the members included + * in a single interpreter request. + */ + private sealed abstract class MemberHandler(val member: Tree) { + val usedNames: List[Name] = { + val ivt = new ImportVarsTraverser(boundNames) + ivt.traverseTrees(List(member)) + ivt.importVars.toList + } + val boundNames: List[Name] = Nil + def valAndVarNames: List[Name] = Nil + def defNames: List[Name] = Nil + val importsWildcard = false + val importedNames: Seq[Name] = Nil + val definesImplicit = + member match { + case tree:MemberDef => + tree.mods.hasFlag(symtab.Flags.IMPLICIT) + case _ => false + } + + def extraCodeToEvaluate(req: Request, code: PrintWriter) { } + def resultExtractionCode(req: Request, code: PrintWriter) { } + } + + private class ValHandler(member: ValDef) extends MemberHandler(member) { + override val boundNames = List(member.name) + override def valAndVarNames = boundNames + + override def resultExtractionCode(req: Request, code: PrintWriter) { + val vname = member.name + if (member.mods.isPublic && + !(isGeneratedVarName(vname) && + req.typeOf(compiler.encode(vname)) == "Unit")) + { + code.print(" + \"" + vname + ": " + + string2code(req.typeOf(vname)) + + " = \" + " + + " (if(" + + req.fullPath(vname) + + ".asInstanceOf[AnyRef] != null) " + + " ((if(" + + req.fullPath(vname) + + ".toString.contains('\\n')) " + + " \"\\n\" else \"\") + " + + req.fullPath(vname) + ".toString + \"\\n\") else \"null\\n\") ") + } + } + } + + private class DefHandler(defDef: DefDef) extends MemberHandler(defDef) { + override val boundNames = List(defDef.name) + override def defNames = boundNames + + override def resultExtractionCode(req: Request, code: PrintWriter) { + if (defDef.mods.isPublic) + code.print("+\""+string2code(defDef.name)+": "+ + string2code(req.typeOf(defDef.name))+"\\n\"") + } + } + + private class AssignHandler(member: Assign) extends MemberHandler(member) { + val lhs = member. lhs.asInstanceOf[Ident] // an unfortunate limitation + + val helperName = newTermName(newInternalVarName()) + override val valAndVarNames = List(helperName) + + override def extraCodeToEvaluate(req: Request, code: PrintWriter) { + code.println("val "+helperName+" = "+member.lhs+";") + } + + /** Print out lhs instead of the generated varName */ + override def resultExtractionCode(req: Request, code: PrintWriter) { + code.print(" + \"" + lhs + ": " + + string2code(req.typeOf(compiler.encode(helperName))) + + " = \" + " + + string2code(req.fullPath(helperName)) + + " + \"\\n\"") + } + } + + private class ModuleHandler(module: ModuleDef) extends MemberHandler(module) { + override val boundNames = List(module.name) + + override def resultExtractionCode(req: Request, code: PrintWriter) { + code.println(" + \"defined module " + + string2code(module.name) + + "\\n\"") + } + } + + private class ClassHandler(classdef: ClassDef) + extends MemberHandler(classdef) + { + override val boundNames = + List(classdef.name) ::: + (if (classdef.mods.hasFlag(Flags.CASE)) + List(classdef.name.toTermName) + else + Nil) + + // TODO: MemberDef.keyword does not include "trait"; + // otherwise it could be used here + def keyword: String = + if (classdef.mods.isTrait) "trait" else "class" + + override def resultExtractionCode(req: Request, code: PrintWriter) { + code.print( + " + \"defined " + + keyword + + " " + + string2code(classdef.name) + + "\\n\"") + } + } + + private class TypeAliasHandler(typeDef: TypeDef) + extends MemberHandler(typeDef) + { + override val boundNames = + if (typeDef.mods.isPublic && compiler.treeInfo.isAliasTypeDef(typeDef)) + List(typeDef.name) + else + Nil + + override def resultExtractionCode(req: Request, code: PrintWriter) { + code.println(" + \"defined type alias " + + string2code(typeDef.name) + "\\n\"") + } + } + + private class ImportHandler(imp: Import) extends MemberHandler(imp) { + override def resultExtractionCode(req: Request, code: PrintWriter) { + code.println("+ \"" + imp.toString + "\\n\"") + } + + /** Whether this import includes a wildcard import */ + override val importsWildcard = + imp.selectors.map(_._1).contains(nme.USCOREkw) + + /** The individual names imported by this statement */ + override val importedNames: Seq[Name] = + for { + val (_,sel) <- imp.selectors + sel != null + sel != nme.USCOREkw + val name <- List(sel.toTypeName, sel.toTermName) + } + yield name + } + /** One line of code submitted by the user for interpretation */ - private abstract class Request(val line: String, val lineName: String) { + private class Request(val line: String, val lineName: String) { val trees = parse(line) match { case Some(ts) => ts case None => Nil @@ -569,80 +743,13 @@ class Interpreter(val settings: Settings, out: PrintWriter) { /** name of the object that retrieves the result from the above object */ def resultObjectName = "RequestResult$" + objectName - /** whether the trees need a variable name, as opposed to standing - alone */ - val needsVarName: Boolean = false - - /** A cache for the chosen variable name, if one has been calculated */ - var varNameCache: Option[String] = None - - /** A computed variable name, if one is needed */ - def varName = varNameCache match { - case None => - varNameCache = Some(newVarName) - varNameCache.get - case Some(name) => - name - } - - /** list of methods defined */ - val defNames = - for (DefDef(mods, name, _, _, _, _) <- trees if mods.isPublic) - yield name - - /** list of val's and var's defined */ - val valAndVarNames = { - val baseNames = - for (ValDef(mods, name, _, _) <- trees if mods.isPublic) - yield name - - if (needsVarName) - compiler.encode(varName) :: baseNames // add a var name - else - baseNames - } - - /** list of modules defined */ - val moduleNames = { - val explicit = - for (ModuleDef(mods, name, _) <- trees if mods.isPublic) - yield name - val caseClasses = - for {val ClassDef(mods, name, _, _) <- trees - mods.isPublic - mods.hasFlag(Flags.CASE)} - yield name.toTermName - explicit ::: caseClasses - } - - /** list of classes defined */ - val classNames = - for (ClassDef(mods, name, _, _) <- trees if mods.isPublic) - yield name - - /** list of type aliases defined */ - val typeNames = - for (t @ TypeDef(mods, name, _, _) <- trees - if mods.isPublic && compiler.treeInfo.isAliasTypeDef(t)) - yield name + val handlers: List[MemberHandler] = trees.flatMap(chooseHandler(_)) /** all (public) names defined by these statements */ - val boundNames = - defNames ::: valAndVarNames ::: moduleNames ::: classNames ::: typeNames + val boundNames = (ListSet() ++ handlers.flatMap(_.boundNames)).toList /** list of names used by this expression */ - val usedNames: List[Name] = { - val ivt = new ImportVarsTraverser(boundNames) - ivt.traverseTrees(trees) - ivt.importVars.toList - } - - /** Whether this request defines an implicit. */ - def definesImplicit = trees.exists { - case tree:MemberDef => - tree.mods.hasFlag(symtab.Flags.IMPLICIT) - case _ => false - } + val usedNames: List[Name] = handlers.flatMap(_.usedNames) def myImportsCode = importsCode(Set.empty ++ usedNames) @@ -671,11 +778,9 @@ class Interpreter(val settings: Settings, out: PrintWriter) { code.print(importsPreamble) - // the variable to compute, if any - if (needsVarName) - code.println(" val " + varName + " = ") - code.println(indentCode(toCompute)) + + handlers.foreach(_.extraCodeToEvaluate(this,code)) code.println(importsTrailer) @@ -694,28 +799,13 @@ class Interpreter(val settings: Settings, out: PrintWriter) { code.println("object " + resultObjectName) code.println("{ val result: String = {") code.println(objectName + accessPath + ";") // evaluate the object, to make sure its constructor is run - code.print("\"\"") // print an initial empty string, so later code can + code.print("(\"\"") // print an initial empty string, so later code can // uniformly be: + morestuff - resultExtractionCode(code) - code.println("}") + handlers.foreach(_.resultExtractionCode(this, code)) + code.println("\n)}") code.println(";}") }) - def resultExtractionCode(code: PrintWriter) { - for (vname <- valAndVarNames) { - code.print(" + \"" + vname + ": " + - string2code(typeOf(vname)) + - " = \" + " + - " (if(" + - fullPath(vname) + - ".asInstanceOf[AnyRef] != null) " + - " ((if(" + - fullPath(vname) + - ".toString.contains('\\n')) " + - " \"\\n\" else \"\") + " + - fullPath(vname) + ".toString + \"\\n\") else \"null\\n\") ") - } - } /** Compile the object file. Returns whether the compilation succeeded. * If all goes well, the "types" map is computed. */ @@ -750,11 +840,14 @@ class Interpreter(val settings: Settings, out: PrintWriter) { * @return ... */ def findTypes(objRun: compiler.Run): Map[Name, String] = { + def valAndVarNames = handlers.flatMap(_.valAndVarNames) + def defNames = handlers.flatMap(_.defNames) + def getTypes(names: List[Name], nameMap: Name=>Name): Map[Name, String] = { /** the outermost wrapper object */ val outerResObjSym: Symbol = compiler.definitions.getMember(compiler.definitions.EmptyPackage, - compiler.newTermName(objectName)) + newTermName(objectName)) /** the innermost object inside the wrapper, found by * following accessPath into the outer one. */ @@ -762,7 +855,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) { (accessPath.split("\\.")).foldLeft(outerResObjSym)((sym,name) => if(name == "") sym else compiler.atPhase(objRun.typerPhase.next) { - sym.info.member(compiler.newTermName(name)) }) + sym.info.member(newTermName(name)) }) names.foldLeft(Map.empty[Name,String])((map, name) => { val rawType = @@ -802,139 +895,14 @@ class Interpreter(val settings: Settings, out: PrintWriter) { (stringFrom(str => orig.printStackTrace(str)), false) } } - - /** return a summary of the defined methods */ - def defTypesSummary: String = - stringFrom(summ => { - for (methname <- defNames) - summ.println("" + methname + ": " + - string2code(typeOf(methname))) - }) } - /** A sequence of definition's. val's, var's, def's. */ - private class DefReq(line: String, lineName: String) - extends Request(line, lineName) - - /** Assignment of a single variable: lhs = exp */ - private class AssignReq(val lhs: Name, line: String, lineName: String) - extends Request(line, lineName) { - override val needsVarName = true - - /** Perform the assignment, and then return the new value */ - override def toCompute = "{\n" + line + "\n;\n" + lhs + "\n}" - - /** Print out lhs instead of the generated varName */ - override def resultExtractionCode(code: PrintWriter) { - code.print(" + \"" + lhs + ": " + - string2code(typeOf(compiler.encode(varName))) + - " = \" + " + - string2code(fullPath(varName)) - + " + \"\\n\"") -// override def resultExtractionCode(code: PrintWriter) { -// {wrapperObj; lhs} -// } - } - } - - /** A single expression */ - private class ExprReq(line: String, lineName: String) - extends Request(line, lineName) { - override val needsVarName = true - - /** Skip the printout if the expression has type Unit */ - override def resultExtractionCode(code: PrintWriter) { - if (typeOf(compiler.encode(varName)) != "Unit") - super.resultExtractionCode(code) - } - } - - /** A module definition */ - private class ModuleReq(line: String, lineName: String) - extends Request(line, lineName) { - def moduleName = trees match { - case List(ModuleDef(_, name, _)) => name - } - override def resultExtractionCode(code: PrintWriter) { - super.resultExtractionCode(code) - code.println(" + \"defined module " + - string2code(moduleName) - + "\\n\"") - } - } - - /** A class definition */ - private class ClassReq(line: String, lineName: String) - extends Request(line, lineName) { - def newClassName = trees match { - case List(ClassDef(_, name, _, _)) => name - } - - def classdef = trees.head.asInstanceOf[ClassDef] - - // TODO: MemberDef.keyword does not include "trait"; - // otherwise it could be used here - def keyword: String = - if (classdef.mods.isTrait) "trait" else "class" - - override def resultExtractionCode(code: PrintWriter) { - super.resultExtractionCode(code) - code.print( - " + \"defined " + - keyword + - " " + - string2code(newClassName) + - "\\n\"") - } - } - - /** a type alias */ - private class TypeAliasReq(line: String, lineName: String) - extends Request(line, lineName) { - def newTypeName = trees match { - case List(TypeDef(_, name, _, _)) => name - } - - override def resultExtractionCode(code: PrintWriter) { - super.resultExtractionCode(code) - code.println(" + \"defined type alias " + newTypeName + "\\n\"") - } - } - - /** an import */ - private class ImportReq(line: String, lineName: String) - extends Request(line, lineName) { - override val boundNames = Nil - override def resultExtractionCode(code: PrintWriter) { - code.println("+ \"" + trees.head.toString + "\\n\"") - } - - /** Whether this import includes a wildcard import */ - def importsWildcard = - trees.exists { - case Import(_, selectors) => - selectors.map(_._1).contains(nme.USCOREkw) - case _ => false - } - - /** The individual names imported by this statement */ - def importedNames: Seq[Name] = - for { - val Import(_, selectors) <- trees - val (_,sel) <- selectors - sel != null - sel != nme.USCOREkw - val name <- List(sel.toTypeName, sel.toTermName) - } - yield name - } -} - class NewLinePrintWriter(out: Writer, autoFlush: Boolean) extends PrintWriter(out, autoFlush) { def this(out: Writer) = this(out, false) override def println() { print("\n"); flush() } } +} /** Utility methods for the Interpreter. */ object Interpreter { diff --git a/src/compiler/scala/tools/nsc/symtab/StdNames.scala b/src/compiler/scala/tools/nsc/symtab/StdNames.scala index b0e858db9..e194da0ef 100644 --- a/src/compiler/scala/tools/nsc/symtab/StdNames.scala +++ b/src/compiler/scala/tools/nsc/symtab/StdNames.scala @@ -78,6 +78,7 @@ trait StdNames { val INTERPRETER_LINE_PREFIX = "line" val INTERPRETER_VAR_PREFIX = "res" val INTERPRETER_IMPORT_WRAPPER = "$iw" + val INTERPRETER_SYNTHVAR_PREFIX = "synthvar$" def LOCAL(clazz: Symbol) = newTermName(LOCALDUMMY_PREFIX_STRING + clazz.name+">") def TUPLE_FIELD(index: Int) = newTermName(TUPLE_FIELD_PREFIX_STRING + index) diff --git a/test/files/run/interpreter.check b/test/files/run/interpreter.check index 7623eb2fb..1bb6c2848 100644 --- a/test/files/run/interpreter.check +++ b/test/files/run/interpreter.check @@ -27,6 +27,11 @@ scala> :5: error: type mismatch; scala> defined trait PointlessTrait +scala> x: Int = 2 +y: Int = 3 + +scala> hello + scala> scala> scala> defined class Foo @@ -151,7 +156,7 @@ scala> :1: error: identifier expected but integer literal found. scala> scala> scala> -scala> | | | | res2: scala.xml.Elem = +scala> | | | | res3: scala.xml.Elem = @@ -161,7 +166,7 @@ scala> | | | | scala> scala> scala> -scala> | | | res3: java.lang.String = +scala> | | | res4: java.lang.String = hello there @@ -177,3 +182,16 @@ scala> x: Int = 1 scala> scala> +scala> defined class Exp +defined class Fact +defined class Term + +scala> | | :15: warning: match is not exhaustive! +missing combination Term + + def f(e: Exp) = e match { // non-exhaustive warning here + ^ +f: (Exp)Int + +scala> +scala> diff --git a/test/files/run/interpreter.scala b/test/files/run/interpreter.scala index 125b43264..391d48b5d 100644 --- a/test/files/run/interpreter.scala +++ b/test/files/run/interpreter.scala @@ -19,6 +19,8 @@ type anotherint = Int val four: anotherint = 4 val bogus: anotherint = "hello" trait PointlessTrait +val (x,y) = (2,3) +println("hello") // implicit conversions case class Foo(n: int) @@ -111,6 +113,12 @@ there def `match` = 1 val x = `match` +// multiple classes defined on one line +sealed class Exp; class Fact extends Exp; class Term extends Exp +def f(e: Exp) = e match {{ // non-exhaustive warning here + case _:Fact => 3 +}} + .text /** A writer that skips the first line of text. The first