From 17dfdc90a5a66b3824cf885c1496639361579383 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 23 Jul 2019 20:43:28 +0200 Subject: [PATCH 1/5] Fix ordering problem for applications In an application with named and default arguments, where arguments have side effects and are given out of order, and some arguments are missing, the previous algorithm worked only if typed and untyped argument trees were the same. Test i2916 started failing once literals were represented as Number trees, since then untyped and typed versions of the argument were different. --- .../dotty/tools/dotc/typer/Applications.scala | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index b60e346f6e9a..9b2a07b9cbb4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -758,23 +758,27 @@ trait Applications extends Compatibility { // lift arguments in the definition order val argDefBuf = mutable.ListBuffer.empty[Tree] typedArgs = lifter.liftArgs(argDefBuf, methType, typedArgs) - // Lifted arguments ordered based on the original order of typedArgBuf and // with all non-explicit default parameters at the end in declaration order. val orderedArgDefs = { - // List of original arguments that are lifted by liftArgs - val impureArgs = typedArgBuf.filterNot(lifter.noLift) - // Assuming stable sorting all non-explicit default parameters will remain in the end with the same order - val defaultParamIndex = args.size - // Mapping of index of each `liftable` into original args ordering - val indices = impureArgs.map { arg => - val idx = args.indexOf(arg) - if (idx >= 0) idx // original index skipping pure arguments - else defaultParamIndex + // Indices of original typed arguments that are lifted by liftArgs + val impureArgIndices = typedArgBuf.zipWithIndex.collect { + case (arg, idx) if !lifter.noLift(arg) => idx + } + def position(arg: Trees.Tree[T]) = { + val i = args.indexOf(arg) + if (i >= 0) i else orderedArgs.length } - scala.util.Sorting.stableSort[(Tree, Int), Int](argDefBuf zip indices, x => x._2).map(_._1) + // The original indices of all ordered arguments, as an array + val originalIndices = orderedArgs.map(position).toArray + // Assuming stable sorting all non-explicit default parameters will remain in the end with the same order + val defaultParamIndex = typedArgs.size + // A map from lifted argument index to the corresponding position in the original argument list + def originalIndex(n: Int) = + if (n < originalIndices.length) originalIndices(n) else orderedArgs.length + scala.util.Sorting.stableSort[(Tree, Int), Int]( + argDefBuf.zip(impureArgIndices), (arg, idx) => originalIndex(idx)).map(_._1) } - liftedDefs ++= orderedArgDefs } if (sameSeq(typedArgs, args)) // trick to cut down on tree copying From 8cf938d86a9e629cc57d9e5a01052adb934c88ed Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 30 Aug 2019 09:37:52 +0200 Subject: [PATCH 2/5] Allow inlining after errors But avoid inlining: - if the typechecking the body to inline generated errors - if checking inlined method generated errors - if we are in an inline typer and the same inline typer already generated errors. As part of this commit, merge hasBodyToInline and bodyToInline. An erroneous body can suppress inlining by returing an EmptyTree. --- .../src/dotty/tools/dotc/sbt/ExtractAPI.scala | 6 ++-- .../src/dotty/tools/dotc/typer/Inliner.scala | 35 ++++++++++++------- .../src/dotty/tools/dotc/typer/Namer.scala | 2 -- .../src/dotty/tools/dotc/typer/Typer.scala | 17 +++++---- 4 files changed, 36 insertions(+), 24 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index e39161435344..5763176b0923 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -591,8 +591,8 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder def apiAnnotations(s: Symbol): List[api.Annotation] = { val annots = new mutable.ListBuffer[api.Annotation] - - if (Inliner.hasBodyToInline(s)) { + val inlineBody = Inliner.bodyToInline(s) + if (!inlineBody.isEmpty) { // FIXME: If the body of an inlineable method changes, all the reverse // dependencies of this method need to be recompiled. sbt has no way // of tracking method bodies, so as a hack we include the pretty-printed @@ -600,7 +600,7 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder // To do this properly we would need a way to hash trees and types in // dotty itself. val printTypesCtx = ctx.fresh.setSetting(ctx.settings.XprintTypes, true) - annots += marker(Inliner.bodyToInline(s).show(printTypesCtx)) + annots += marker(inlineBody.show(printTypesCtx)) } // In the Scala2 ExtractAPI phase we only extract annotations that extend diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index fc219f485df5..b7ac128dc517 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -38,15 +38,20 @@ object Inliner { def hasBodyToInline(sym: SymDenotation)(implicit ctx: Context): Boolean = sym.isInlineMethod && sym.hasAnnotation(defn.BodyAnnot) - /** The body to inline for method `sym`. + /** The body to inline for method `sym`, or `EmptyTree` if none exists. + * Note: definitions coming from Scala2x class files might be `@forceInline`, + * but still lack that body. * @pre hasBodyToInline(sym) */ def bodyToInline(sym: SymDenotation)(implicit ctx: Context): Tree = - sym.unforcedAnnotation(defn.BodyAnnot).get.tree + if (sym.isInlineMethod && sym.hasAnnotation(defn.BodyAnnot)) + sym.getAnnotation(defn.BodyAnnot).get.tree + else + EmptyTree /** Should call to method `meth` be inlined in this context? */ def isInlineable(meth: Symbol)(implicit ctx: Context): Boolean = - meth.is(Inline) && hasBodyToInline(meth) && !ctx.inInlineMethod + meth.is(Inline) && !ctx.inInlineMethod && !bodyToInline(meth).isEmpty /** Should call be inlined in this context? */ def isInlineable(tree: Tree)(implicit ctx: Context): Boolean = tree match { @@ -105,8 +110,7 @@ object Inliner { cpy.Block(tree)(bindings.toList, inlineCall(tree1)) else if (enclosingInlineds.length < ctx.settings.XmaxInlines.value) { val body = bodyToInline(tree.symbol) // can typecheck the tree and thereby produce errors - if (ctx.reporter.hasErrors) tree - else new Inliner(tree, body).inlined(tree.sourcePos) + new Inliner(tree, body).inlined(tree.sourcePos) } else errorTree( @@ -204,7 +208,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { private val inlineCallPrefix = qualifier(methPart).orElse(This(inlinedMethod.enclosingClass.asClass)) - inlining.println("-----------------------\nInlining $call\nWith RHS $rhsToInline") + inlining.println(i"-----------------------\nInlining $call\nWith RHS $rhsToInline") // Make sure all type arguments to the call are fully determined for (targ <- callTypeArgs) fullyDefinedType(targ.tpe, "inlined type argument", targ.span) @@ -421,7 +425,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { // Compute bindings for all this-proxies, appending them to bindingsBuf computeThisBindings() - val inlineTyper = new InlineTyper + val inlineTyper = new InlineTyper(ctx.reporter.errorCount) val inlineCtx = inlineContext(call).fresh.setTyper(inlineTyper).setNewScope @@ -986,7 +990,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { * 4. Make sure inlined code is type-correct. * 5. Make sure that the tree's typing is idempotent (so that future -Ycheck passes succeed) */ - class InlineTyper extends ReTyper { + class InlineTyper(initialErrorCount: Int) extends ReTyper { import reducer._ override def ensureAccessible(tpe: Type, superAccess: Boolean, pos: SourcePosition)(implicit ctx: Context): Type = { @@ -1033,7 +1037,11 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { override def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = constToLiteral(betaReduce(super.typedApply(tree, pt))) match { - case res: Apply if res.symbol == defn.InternalQuoted_exprSplice && level == 0 && call.symbol.is(Macro) => + case res: Apply + if res.symbol == defn.InternalQuoted_exprSplice && + level == 0 && + call.symbol.is(Macro) && + !suppressInline => expandMacro(res.args.head, tree.span) case res => res } @@ -1082,7 +1090,11 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } } - override def newLikeThis: Typer = new InlineTyper + override def newLikeThis: Typer = new InlineTyper(initialErrorCount) + + /** Suppress further inlining if this inline typer has already issued errors */ + override def suppressInline given (ctx: Context) = + ctx.reporter.errorCount > initialErrorCount || super.suppressInline } /** Drop any side-effect-free bindings that are unused in expansion or other reachable bindings. @@ -1202,8 +1214,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } } val normalizedSplice = inlinedNormailizer.transform(evaluatedSplice) - - if (ctx.reporter.hasErrors) EmptyTree + if (normalizedSplice.isEmpty) normalizedSplice else normalizedSplice.withSpan(span) } } diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index bf15516c257f..b2585dc7e591 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -805,8 +805,6 @@ class Namer { typer: Typer => private def addInlineInfo(sym: Symbol) = original match { case original: untpd.DefDef if sym.isInlineMethod => - if (sym.owner.isClass && sym.owner.seesOpaques) - ctx.error(em"Implementation restriction: No inline methods allowed where opaque type aliases are in scope", sym.sourcePos) PrepareInlineable.registerInlineInfo( sym, implicit ctx => typedAheadExpr(original).asInstanceOf[tpd.DefDef].rhs diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 826e2e74e7e8..4ef8b6408dfe 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -111,6 +111,7 @@ class Typer extends Namer */ private[this] var foundUnderScala2: Type = NoType + // Overridden in derived typers def newLikeThis: Typer = new Typer /** Find the type of an identifier with given `name` in given context `ctx`. @@ -1536,10 +1537,8 @@ class Typer extends Namer if (sym.isInlineMethod) rhsCtx.addMode(Mode.InlineableBody) val rhs1 = typedExpr(ddef.rhs, tpt1.tpe.widenExpr)(rhsCtx) - if (sym.isInlineMethod) { - PrepareInlineable.checkInlineMacro(sym, rhs1, ddef.sourcePos) + if (sym.isInlineMethod) PrepareInlineable.registerInlineInfo(sym, _ => rhs1) - } if (sym.isConstructor && !sym.isPrimaryConstructor) { for (param <- tparams1 ::: vparamss1.flatten) @@ -2119,7 +2118,7 @@ class Typer extends Namer traverse(xtree :: rest) case none => typed(mdef) match { - case mdef1: DefDef if Inliner.hasBodyToInline(mdef1.symbol) => + case mdef1: DefDef if !Inliner.bodyToInline(mdef1.symbol).isEmpty => buf += inlineExpansion(mdef1) // replace body with expansion, because it will be used as inlined body // from separately compiled files - the original BodyAnnotation is not kept. @@ -2666,10 +2665,11 @@ class Typer extends Namer } else if (Inliner.isInlineable(tree) && !ctx.settings.YnoInline.value && - !ctx.isAfterTyper && - !ctx.reporter.hasErrors) { + !suppressInline) { tree.tpe <:< wildApprox(pt) - readaptSimplified(Inliner.inlineCall(tree)) + val errorCount = ctx.reporter.errorCount + val inlined = Inliner.inlineCall(tree) + if (errorCount == ctx.reporter.errorCount) readaptSimplified(inlined) else inlined } else if (tree.symbol.isScala2Macro && // raw and s are eliminated by the StringInterpolatorOpt phase @@ -2978,6 +2978,9 @@ class Typer extends Namer } } + // Overridden in InlineTyper + def suppressInline given (ctx: Context): Boolean = ctx.isAfterTyper + /** Does the "contextuality" of the method type `methType` match the one of the prototype `pt`? * This is the case if * - both are contextual, or From a276f0e6b1aedcd5a8d0dc8690b636424ad24425 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 30 Aug 2019 09:48:00 +0200 Subject: [PATCH 3/5] Implement generic number literals ### Example: ``` val x: BigInt = 111111100000022222222222 ``` ### Also allow generic literals in patterns Wrap generic literals in patterns in blocks, which force expression evaluation. This allows to match (say) a scrutinee of BigInt type against large numeric literals. But it does not work if the scrutinee is of type `Any` because then the expeected type for the pattern is missing. This would be addressed by a feature that's still missing: patterns of the form ` : `. We have to change the syntax of patterns in general for this one. --- compiler/src/dotty/tools/dotc/ast/untpd.scala | 15 +- .../dotty/tools/dotc/core/Definitions.scala | 13 +- .../src/dotty/tools/dotc/core/StdNames.scala | 1 + .../tools/dotc/parsing/JavaScanners.scala | 21 +- .../dotty/tools/dotc/parsing/Parsers.scala | 57 ++-- .../dotty/tools/dotc/parsing/Scanners.scala | 105 +------- .../src/dotty/tools/dotc/parsing/Tokens.scala | 28 +- .../tools/dotc/printing/RefinedPrinter.scala | 2 + .../tools/dotc/transform/patmat/Space.scala | 12 +- .../src/dotty/tools/dotc/typer/Typer.scala | 68 ++++- docs/docs/internals/syntax.md | 3 +- .../changed-features/numeric-literals.md | 245 ++++++++++++++++++ docs/sidebar.yml | 2 + library/src/scala/util/FromDigits.scala | 164 ++++++++++++ .../GenericNumLits/Even_1.scala | 37 +++ .../GenericNumLits/Test_2.scala | 5 + tests/neg/BigFloat/BigFloat_1.scala | 66 +++++ tests/neg/BigFloat/Test_2.scala | 4 + tests/neg/GenericNumLits/Test_3.scala | 3 + tests/neg/t6124.check | 8 +- tests/pos/patmat.scala | 9 +- tests/run-with-compiler/BigFloat.check | 5 + .../BigFloat/BigFloat_1.scala | 66 +++++ tests/run-with-compiler/BigFloat/Test_2.scala | 20 ++ tests/run/genericNumLits.check | 5 + tests/run/genericNumLits.scala | 57 ++++ 26 files changed, 859 insertions(+), 162 deletions(-) create mode 100644 docs/docs/reference/changed-features/numeric-literals.md create mode 100644 library/src/scala/util/FromDigits.scala create mode 100644 tests/neg-with-compiler/GenericNumLits/Even_1.scala create mode 100644 tests/neg-with-compiler/GenericNumLits/Test_2.scala create mode 100644 tests/neg/BigFloat/BigFloat_1.scala create mode 100644 tests/neg/BigFloat/Test_2.scala create mode 100644 tests/neg/GenericNumLits/Test_3.scala create mode 100644 tests/run-with-compiler/BigFloat.check create mode 100644 tests/run-with-compiler/BigFloat/BigFloat_1.scala create mode 100644 tests/run-with-compiler/BigFloat/Test_2.scala create mode 100644 tests/run/genericNumLits.check create mode 100644 tests/run/genericNumLits.scala diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 743b0f14cf88..f8a9093d0739 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -109,6 +109,13 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class ContextBounds(bounds: TypeBoundsTree, cxBounds: List[Tree])(implicit @constructorOnly src: SourceFile) extends TypTree case class PatDef(mods: Modifiers, pats: List[Tree], tpt: Tree, rhs: Tree)(implicit @constructorOnly src: SourceFile) extends DefTree case class Export(impliedOnly: Boolean, expr: Tree, selectors: List[Tree])(implicit @constructorOnly src: SourceFile) extends Tree + case class Number(digits: String, kind: NumberKind)(implicit @constructorOnly src: SourceFile) extends TermTree + + enum NumberKind { + case Whole(radix: Int) + case Decimal + case Floating + } /** Short-lived usage in typer, does not need copy/transform/fold infrastructure */ case class DependentTypeTree(tp: List[Symbol] => Type)(implicit @constructorOnly src: SourceFile) extends Tree @@ -558,6 +565,10 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case tree: Export if (impliedOnly == tree.impliedOnly) && (expr eq tree.expr) && (selectors eq tree.selectors) => tree case _ => finalize(tree, untpd.Export(impliedOnly, expr, selectors)(tree.source)) } + def Number(tree: Tree)(digits: String, kind: NumberKind)(implicit ctx: Context): Tree = tree match { + case tree: Number if (digits == tree.digits) && (kind == tree.kind) => tree + case _ => finalize(tree, untpd.Number(digits, kind)) + } def TypedSplice(tree: Tree)(splice: tpd.Tree)(implicit ctx: Context): ProxyTree = tree match { case tree: TypedSplice if splice `eq` tree.splice => tree case _ => finalize(tree, untpd.TypedSplice(splice)(ctx)) @@ -612,7 +623,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { cpy.PatDef(tree)(mods, transform(pats), transform(tpt), transform(rhs)) case Export(impliedOnly, expr, selectors) => cpy.Export(tree)(impliedOnly, transform(expr), selectors) - case TypedSplice(_) => + case Number(_, _) | TypedSplice(_) => tree case _ => super.transformMoreCases(tree) @@ -667,6 +678,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { this(this(this(x, pats), tpt), rhs) case Export(_, expr, _) => this(x, expr) + case Number(_, _) => + x case TypedSplice(splice) => this(x, splice) case _ => diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index f1982974ea36..0edd185db23e 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -669,7 +669,18 @@ class Definitions { @tu lazy val StatsModule: Symbol = ctx.requiredModule("dotty.tools.dotc.util.Stats") @tu lazy val Stats_doRecord: Symbol = StatsModule.requiredMethod("doRecord") - @tu lazy val XMLTopScopeModule: Symbol = ctx.requiredModule("scala.xml.TopScope") + @threadUnsafe lazy val FromDigitsClass: ClassSymbolPerRun = perRunClass(ctx.requiredClassRef("scala.util.FromDigits")) + @threadUnsafe lazy val FromDigits_WithRadixClass: ClassSymbolPerRun = perRunClass(ctx.requiredClassRef("scala.util.FromDigits.WithRadix")) + @threadUnsafe lazy val FromDigits_DecimalClass: ClassSymbolPerRun = perRunClass(ctx.requiredClassRef("scala.util.FromDigits.Decimal")) + @threadUnsafe lazy val FromDigits_FloatingClass: ClassSymbolPerRun = perRunClass(ctx.requiredClassRef("scala.util.FromDigits.Floating")) + + @threadUnsafe lazy val XMLTopScopeModule: SymbolPerRun = perRunSym(ctx.requiredModuleRef("scala.xml.TopScope")) + + @threadUnsafe lazy val CommandLineParserModule: SymbolPerRun = perRunSym(ctx.requiredModuleRef("scala.util.CommandLineParser")) + @threadUnsafe lazy val CLP_ParseError: ClassSymbolPerRun = perRunClass(CommandLineParserModule.requiredClass("ParseError").typeRef) + @threadUnsafe lazy val CLP_parseArgument: SymbolPerRun = perRunSym(CommandLineParserModule.requiredMethodRef("parseArgument")) + @threadUnsafe lazy val CLP_parseRemainingArguments: SymbolPerRun = perRunSym(CommandLineParserModule.requiredMethodRef("parseRemainingArguments")) + @threadUnsafe lazy val CLP_showError: SymbolPerRun = perRunSym(CommandLineParserModule.requiredMethodRef("showError")) @tu lazy val CommandLineParserModule: Symbol = ctx.requiredModule("scala.util.CommandLineParser") @tu lazy val CLP_ParseError: ClassSymbol = CommandLineParserModule.requiredClass("ParseError").typeRef.symbol.asClass diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 03da138f04a5..0ba484b3e248 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -442,6 +442,7 @@ object StdNames { val flagsFromBits : N = "flagsFromBits" val flatMap: N = "flatMap" val foreach: N = "foreach" + val fromDigits: N = "fromDigits" val fromProduct: N = "fromProduct" val genericArrayOps: N = "genericArrayOps" val genericClass: N = "genericClass" diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaScanners.scala b/compiler/src/dotty/tools/dotc/parsing/JavaScanners.scala index 7a8e17cbadf5..0e437d9d0a49 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaScanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaScanners.scala @@ -519,20 +519,13 @@ object JavaScanners { // Errors ----------------------------------------------------------------- override def toString(): String = token match { - case IDENTIFIER => - "id(" + name + ")" - case CHARLIT => - "char(" + intVal + ")" - case INTLIT => - "int(" + intVal + ")" - case LONGLIT => - "long(" + intVal + ")" - case FLOATLIT => - "float(" + floatVal + ")" - case DOUBLELIT => - "double(" + doubleVal + ")" - case STRINGLIT => - "string(" + name + ")" + case IDENTIFIER => s"id($name)" + case CHARLIT => s"char($strVal)" + case INTLIT => s"int($strVal, $base)" + case LONGLIT => s"long($strVal, $base)" + case FLOATLIT => s"float($strVal)" + case DOUBLELIT => s"double($strVal)" + case STRINGLIT => s"string($strVal)" case SEMI => ";" case COMMA => diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index d3077292c45c..bf6ce69a15d1 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1016,24 +1016,37 @@ object Parsers { * @param negOffset The offset of a preceding `-' sign, if any. * If the literal is not negated, negOffset = in.offset. */ - def literal(negOffset: Int = in.offset, inPattern: Boolean = false, inStringInterpolation: Boolean = false): Tree = { - - def literalOf(token: Token): Literal = { + def literal(negOffset: Int = in.offset, inPattern: Boolean = false, inType: Boolean = false, inStringInterpolation: Boolean = false): Tree = { + def literalOf(token: Token): Tree = { val isNegated = negOffset < in.offset - val value = token match { - case CHARLIT => in.charVal - case INTLIT => in.intVal(isNegated).toInt - case LONGLIT => in.intVal(isNegated) - case FLOATLIT => in.floatVal(isNegated).toFloat - case DOUBLELIT => in.doubleVal(isNegated) - case STRINGLIT | STRINGPART => in.strVal - case TRUE => true - case FALSE => false - case NULL => null - case _ => - syntaxErrorOrIncomplete(IllegalLiteral()) - null - } + def digits0 = in.removeNumberSeparators(in.strVal) + def digits = if (isNegated) "-" + digits0 else digits0 + if (!inType) + token match { + case INTLIT => return Number(digits, NumberKind.Whole(in.base)) + case DECILIT => return Number(digits, NumberKind.Decimal) + case EXPOLIT => return Number(digits, NumberKind.Floating) + case _ => + } + import scala.util.FromDigits._ + val value = + try token match { + case INTLIT => intFromDigits(digits, in.base) + case LONGLIT => longFromDigits(digits, in.base) + case FLOATLIT => floatFromDigits(digits) + case DOUBLELIT | DECILIT | EXPOLIT => doubleFromDigits(digits) + case CHARLIT => in.strVal.head + case STRINGLIT | STRINGPART => in.strVal + case TRUE => true + case FALSE => false + case NULL => null + case _ => + syntaxErrorOrIncomplete(IllegalLiteral()) + null + } + catch { + case ex: FromDigitsException => syntaxErrorOrIncomplete(ex.getMessage) + } Literal(Constant(value)) } @@ -1163,6 +1176,7 @@ object Parsers { } /* ------------- TYPES ------------------------------------------------------ */ + /** Same as [[typ]], but if this results in a wildcard it emits a syntax error and * returns a tree for type `Any` instead. */ @@ -1378,11 +1392,11 @@ object Parsers { } else if (in.token == LBRACE) atSpan(in.offset) { RefinedTypeTree(EmptyTree, refinement()) } - else if (isSimpleLiteral) { SingletonTypeTree(literal()) } + else if (isSimpleLiteral) { SingletonTypeTree(literal(inType = true)) } else if (isIdent(nme.raw.MINUS) && in.lookaheadIn(numericLitTokens)) { val start = in.offset in.nextToken() - SingletonTypeTree(literal(negOffset = start)) + SingletonTypeTree(literal(negOffset = start, inType = true)) } else if (in.token == USCORE) { if (ctx.settings.strict.value) { @@ -2329,12 +2343,11 @@ object Parsers { if (isIdent(nme.raw.BAR)) { in.nextToken(); pattern1() :: patternAlts() } else Nil - /** Pattern1 ::= PatVar Ascription - * | Pattern2 + /** Pattern1 ::= Pattern2 [Ascription] */ def pattern1(): Tree = { val p = pattern2() - if (isVarPattern(p) && in.token == COLON) { + if (in.token == COLON) { in.nextToken() ascription(p, Location.InPattern) } diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index c673360ba421..2dbcf42ab278 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -123,99 +123,10 @@ object Scanners { def setStrVal(): Unit = strVal = flushBuf(litBuf) - /** Convert current strVal to char value - */ - def charVal: Char = if (strVal.length > 0) strVal.charAt(0) else 0 - - /** Convert current strVal, base to long value - * This is tricky because of max negative value. - */ - def intVal(negated: Boolean): Long = - if (token == CHARLIT && !negated) - charVal - else { - var value: Long = 0 - val divider = if (base == 10) 1 else 2 - val limit: Long = - if (token == LONGLIT) Long.MaxValue else Int.MaxValue - var i = 0 - val len = strVal.length - while (i < len) { - val c = strVal charAt i - if (! isNumberSeparator(c)) { - val d = digit2int(c, base) - if (d < 0) { - error(s"malformed integer number") - return 0 - } - if (value < 0 || - limit / (base / divider) < value || - limit - (d / divider) < value * (base / divider) && - !(negated && limit == value * base - 1 + d)) { - error("integer number too large") - return 0 - } - value = value * base + d - } - i += 1 - } - if (negated) -value else value - } - - def intVal: Long = intVal(false) - - private val zeroFloat = raw"[0.]+(?:[eE][+-]?[0-9]+)?[fFdD]?".r - - /** Convert current strVal, base to double value - */ - def floatVal(negated: Boolean): Float = { - assert(token == FLOATLIT) - val text = removeNumberSeparators(strVal) - try { - val value: Float = java.lang.Float.valueOf(text).floatValue() - if (value > Float.MaxValue) - errorButContinue("floating point number too large") - - if (value == 0.0f && !zeroFloat.pattern.matcher(text).matches) - errorButContinue("floating point number too small") - if (negated) -value else value - } - catch { - case _: NumberFormatException => - error("malformed floating point number") - 0.0f - } - } - - def floatVal: Float = floatVal(false) - - /** Convert current strVal, base to double value - */ - def doubleVal(negated: Boolean): Double = { - assert(token == DOUBLELIT) - val text = removeNumberSeparators(strVal) - try { - val value: Double = java.lang.Double.valueOf(text).doubleValue() - if (value > Double.MaxValue) - errorButContinue("double precision floating point number too large") - - if (value == 0.0d && !zeroFloat.pattern.matcher(text).matches) - errorButContinue("double precision floating point number too small") - if (negated) -value else value - } - catch { - case _: NumberFormatException => - error("malformed floating point number") - 0.0 - } - } - - def doubleVal: Double = doubleVal(false) - @inline def isNumberSeparator(c: Char): Boolean = c == '_' @inline def removeNumberSeparators(s: String): String = - if (s.indexOf('_') > 0) s.replaceAllLiterally("_", "") /*.replaceAll("'","")*/ else s + if (s.indexOf('_') > 0) s.replaceAllLiterally("_", "") else s // disallow trailing numeric separator char, but continue lexing def checkNoTrailingSeparator(): Unit = @@ -1228,7 +1139,7 @@ object Scanners { * if one is present. */ protected def getFraction(): Unit = { - token = DOUBLELIT + token = DECILIT while ('0' <= ch && ch <= '9' || isNumberSeparator(ch)) { putChar(ch) nextChar() @@ -1252,7 +1163,7 @@ object Scanners { } checkNoTrailingSeparator() } - token = DOUBLELIT + token = EXPOLIT } if (ch == 'd' || ch == 'D') { putChar(ch) @@ -1329,11 +1240,11 @@ object Scanners { def show: String = token match { case IDENTIFIER | BACKQUOTED_IDENT => s"id($name)" - case CHARLIT => s"char($intVal)" - case INTLIT => s"int($intVal)" - case LONGLIT => s"long($intVal)" - case FLOATLIT => s"float($floatVal)" - case DOUBLELIT => s"double($doubleVal)" + case CHARLIT => s"char($strVal)" + case INTLIT => s"int($strVal, $base)" + case LONGLIT => s"long($strVal, $base)" + case FLOATLIT => s"float($strVal)" + case DOUBLELIT => s"double($strVal)" case STRINGLIT => s"string($strVal)" case STRINGPART => s"stringpart($strVal)" case INTERPOLATIONID => s"interpolationid($name)" diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index 96acb0557791..cbe9ae05c4b5 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -37,17 +37,19 @@ abstract class TokensCommon { /** literals */ final val CHARLIT = 3; enter(CHARLIT, "character literal") final val INTLIT = 4; enter(INTLIT, "integer literal") - final val LONGLIT = 5; enter(LONGLIT, "long literal") - final val FLOATLIT = 6; enter(FLOATLIT, "float literal") - final val DOUBLELIT = 7; enter(DOUBLELIT, "double literal") - final val STRINGLIT = 8; enter(STRINGLIT, "string literal") - final val STRINGPART = 9; enter(STRINGPART, "string literal", "string literal part") - //final val INTERPOLATIONID = 10; enter(INTERPOLATIONID, "string interpolator") - //final val QUOTEID = 11; enter(QUOTEID, "quoted identifier") // TODO: deprecate + final val DECILIT = 5; enter(DECILIT, "number literal") // with decimal point + final val EXPOLIT = 6; enter(EXPOLIT, "number literal with exponent") + final val LONGLIT = 7; enter(LONGLIT, "long literal") + final val FLOATLIT = 8; enter(FLOATLIT, "float literal") + final val DOUBLELIT = 9; enter(DOUBLELIT, "double literal") + final val STRINGLIT = 10; enter(STRINGLIT, "string literal") + final val STRINGPART = 11; enter(STRINGPART, "string literal", "string literal part") + //final val INTERPOLATIONID = 12; enter(INTERPOLATIONID, "string interpolator") + //final val QUOTEID = 13; enter(QUOTEID, "quoted identifier") // TODO: deprecate /** identifiers */ - final val IDENTIFIER = 12; enter(IDENTIFIER, "identifier") - //final val BACKQUOTED_IDENT = 13; enter(BACKQUOTED_IDENT, "identifier", "backquoted ident") + final val IDENTIFIER = 14; enter(IDENTIFIER, "identifier") + //final val BACKQUOTED_IDENT = 15; enter(BACKQUOTED_IDENT, "identifier", "backquoted ident") /** alphabetic keywords */ final val IF = 20; enter(IF, "if") @@ -150,10 +152,10 @@ object Tokens extends TokensCommon { final val minToken = EMPTY final def maxToken: Int = XMLSTART - final val INTERPOLATIONID = 10; enter(INTERPOLATIONID, "string interpolator") - final val QUOTEID = 11; enter(QUOTEID, "quoted identifier") // TODO: deprecate + final val INTERPOLATIONID = 12; enter(INTERPOLATIONID, "string interpolator") + final val QUOTEID = 13; enter(QUOTEID, "quoted identifier") // TODO: deprecate - final val BACKQUOTED_IDENT = 13; enter(BACKQUOTED_IDENT, "identifier", "backquoted ident") + final val BACKQUOTED_IDENT = 15; enter(BACKQUOTED_IDENT, "identifier", "backquoted ident") final val identifierTokens: TokenSet = BitSet(IDENTIFIER, BACKQUOTED_IDENT) @@ -260,7 +262,7 @@ object Tokens extends TokensCommon { final val stopScanTokens: BitSet = mustStartStatTokens | BitSet(IF, ELSE, WHILE, DO, FOR, YIELD, NEW, TRY, CATCH, FINALLY, THROW, RETURN, MATCH, SEMI, EOF) - final val numericLitTokens: TokenSet = BitSet(INTLIT, LONGLIT, FLOATLIT, DOUBLELIT) + final val numericLitTokens: TokenSet = BitSet(INTLIT, DECILIT, EXPOLIT, LONGLIT, FLOATLIT, DOUBLELIT) final val statCtdTokens: BitSet = BitSet(THEN, ELSE, DO, CATCH, FINALLY, YIELD, MATCH) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 9e4ab644b43d..24d8e5a6793a 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -604,6 +604,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { changePrec(GlobalPrec) { keywordStr("try ") ~ toText(expr) ~ " " ~ keywordStr("catch") ~ " {" ~ toText(handler) ~ "}" ~ optText(finalizer)(keywordStr(" finally ") ~ _) } + case Number(digits, kind) => + digits case Quote(tree) => if (tree.isType) keywordStr("'[") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("]") else keywordStr("'{") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("}") diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index f0cc631f1dd6..3ebc68c77833 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -339,17 +339,19 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { } else Prod(erase(pat.tpe.stripAnnots), erase(fun.tpe), fun.symbol, pats.map(project), isIrrefutableUnapply(fun)) - case Typed(pat: UnApply, _) => - project(pat) - case Typed(expr, tpt) => + case Typed(expr @ Ident(nme.WILDCARD), tpt) => Typ(erase(expr.tpe.stripAnnots), true) + case Typed(pat, _) => + project(pat) case This(_) => Typ(pat.tpe.stripAnnots, false) case EmptyTree => // default rethrow clause of try/catch, check tests/patmat/try2.scala Typ(WildcardType, false) + case Block(Nil, expr) => + project(expr) case _ => - ctx.error(s"unknown pattern: $pat", pat.sourcePos) - Empty + // Pattern is an arbitrary expression; assume a skolem (i.e. an unknown value) of the pattern type + Typ(pat.tpe.narrow, false) } private def unapplySeqInfo(resTp: Type, pos: SourcePosition)(implicit ctx: Context): (Int, Type, Type) = { diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 4ef8b6408dfe..a3dc49dd3c72 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -508,6 +508,70 @@ class Typer extends Namer } } + def typedNumber(tree: untpd.Number, pt: Type)(implicit ctx: Context): Tree = { + import scala.util.FromDigits._ + import untpd.NumberKind._ + record("typedNumber") + val digits = tree.digits + val target = pt.dealias + def lit(value: Any) = Literal(Constant(value)).withSpan(tree.span) + try { + // Special case primitive numeric types + if (target.isRef(defn.IntClass) || + target.isRef(defn.CharClass) || + target.isRef(defn.ByteClass) || + target.isRef(defn.ShortClass)) + tree.kind match { + case Whole(radix) => return lit(intFromDigits(digits, radix)) + case _ => + } + else if (target.isRef(defn.LongClass)) + tree.kind match { + case Whole(radix) => return lit(longFromDigits(digits, radix)) + case _ => + } + else if (target.isRef(defn.FloatClass)) + return lit(floatFromDigits(digits)) + else if (target.isRef(defn.DoubleClass)) + return lit(doubleFromDigits(digits)) + else if (target.isValueType && isFullyDefined(target, ForceDegree.none)) { + // If expected type is defined with a FromDigits instance, use that one + val fromDigitsCls = tree.kind match { + case Whole(10) => defn.FromDigitsClass + case Whole(_) => defn.FromDigits_WithRadixClass + case Decimal => defn.FromDigits_DecimalClass + case Floating => defn.FromDigits_FloatingClass + } + inferImplicit(fromDigitsCls.typeRef.appliedTo(target), EmptyTree, tree.span) match { + case SearchSuccess(arg, _, _) => + val fromDigits = untpd.Select(untpd.TypedSplice(arg), nme.fromDigits).withSpan(tree.span) + val firstArg = Literal(Constant(digits)) + val otherArgs = tree.kind match { + case Whole(r) if r != 10 => Literal(Constant(r)) :: Nil + case _ => Nil + } + var app: untpd.Tree = untpd.Apply(fromDigits, firstArg :: otherArgs) + if (ctx.mode.is(Mode.Pattern)) app = untpd.Block(Nil, app) + return typed(app, pt) + case _ => + } + } + // Otherwise convert to Int or Double according to digits format + tree.kind match { + case Whole(radix) => lit(intFromDigits(digits, radix)) + case _ => lit(doubleFromDigits(digits)) + } + } + catch { + case ex: FromDigitsException => + ctx.error(ex.getMessage, tree.sourcePos) + tree.kind match { + case Whole(_) => lit(0) + case _ => lit(0.0) + } + } + } + def typedLiteral(tree: untpd.Literal)(implicit ctx: Context): Tree = { val tree1 = assignType(tree) if (ctx.mode.is(Mode.Type)) tpd.SingletonTypeTree(tree1) // this ensures that tree is classified as a type tree @@ -702,7 +766,8 @@ class Typer extends Namer (index(stats), typedStats(stats, ctx.owner)) def typedBlock(tree: untpd.Block, pt: Type)(implicit ctx: Context): Tree = { - val (exprCtx, stats1) = typedBlockStats(tree.stats) + val localCtx = ctx.retractMode(Mode.Pattern) + val (exprCtx, stats1) = typedBlockStats(tree.stats) given localCtx val expr1 = typedExpr(tree.expr, pt.dropIfProto)(exprCtx) ensureNoLocalRefs( cpy.Block(tree)(stats1, expr1).withType(expr1.tpe), pt, localSyms(stats1)) @@ -1997,6 +2062,7 @@ class Typer extends Namer case tree: untpd.Apply => if (ctx.mode is Mode.Pattern) typedUnApply(tree, pt) else typedApply(tree, pt) case tree: untpd.This => typedThis(tree) + case tree: untpd.Number => typedNumber(tree, pt) case tree: untpd.Literal => typedLiteral(tree) case tree: untpd.New => typedNew(tree, pt) case tree: untpd.Typed => typedTyped(tree, pt) diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index fbd6479b3f1c..1d8888698c58 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -261,8 +261,7 @@ TypeCaseClauses ::= TypeCaseClause { TypeCaseClause } TypeCaseClause ::= ‘case’ InfixType ‘=>’ Type [nl] Pattern ::= Pattern1 { ‘|’ Pattern1 } Alternative(pats) -Pattern1 ::= PatVar ‘:’ RefinedType Bind(name, Typed(Ident(wildcard), tpe)) - | Pattern2 +Pattern1 ::= Pattern2 [‘:’ RefinedType] Bind(name, Typed(Ident(wildcard), tpe)) Pattern2 ::= [id ‘@’] InfixPattern Bind(name, pat) InfixPattern ::= SimplePattern { id [cnl] SimplePattern } InfixOp(pat, op, pat) SimplePattern ::= PatVar Ident(wildcard) diff --git a/docs/docs/reference/changed-features/numeric-literals.md b/docs/docs/reference/changed-features/numeric-literals.md new file mode 100644 index 000000000000..6be9c6e49473 --- /dev/null +++ b/docs/docs/reference/changed-features/numeric-literals.md @@ -0,0 +1,245 @@ +--- +layout: doc-page +title: Numeric Literals +--- + +In Scala 2, numeric literals were confined to the primitive numeric types `Int`, Long`, `Float`, and `Double`. Scala 3 allows to write numeric literals also for user defined types. Example: +``` +val x: Long = -10_000_000_000 +val y: BigInt = 0x123_abc_789_def_345_678_901 +val z: BigDecimal = 110_222_799_799.99 + +(y: BigInt) match { + case 123_456_789_012_345_678_901 => +} +``` +The syntax of numeric literals is the same as before, except there are no pre-set limits +how large they can be. + +### Meaning of Numeric Literals + +The meaning of a numeric literal is determined as follows: + + - If the literal ends with `l` or `L`, it is a `Long` integer (and must fit + in its legal range). + - If the literal ends with `f` or `F`, it is a single precision floating point number of type `Float`. + - If the literal ends with `d` or `D`, it is a double precision floating point number of type `Double`. + +In each of these cases the conversion to a number is exactly as in Scala 2 or in Java. If a numeric literal does _not_ end in one of these suffixes, its meaning is determined by the expected type: + + 1. If the expected type is `Int`, `Long`, `Float`, or `Double`, the literal is + treated as a standard literal of that type. + 2. If the expected type is a fully defined type `T` that has a given instance of type + `scala.util.FromDigits[T]`, the literal is converted to a value of type `T` by passing it as an argument to + the `fromDigits` method of that instance (more details below). + 3. Otherwise, the literal is treated as a `Double` literal (if it has a decimal point or an + exponent), or as an `Int` literal (if not). (This last possibility is again as in Scala 2 or Java.) + +With these rules, the definition +```scala +val x: Long = -10_000_000_000 +``` +is legal by rule (1), since the expected type is `Long`. The definitions +```scala +val y: BigInt = 0x123_abc_789_def_345_678_901 +val z: BigDecimal = 111222333444.55 +``` +are legal by rule (2), since both `BigInt` and `BigDecimal` have `FromDigits` instances +(which implement the `FromDigits` subclasses `FromDigits.WithRadix` and `FromDigits.Decimal`, respectively). +On the other hand, +```scala +val x = -10_000_000_000 +``` +gives a type error, since without an expected type `-10_000_000_000` is treated by rule (3) as an `Int` literal, but it is too large for that type. + +### The FromDigits Class + +To allow numeric literals, a type simply has to define a given instance of the +`scala.util.FromDigits` typeclass, or one of its subclasses. `FromDigits` is defined +as follows: +```scala +trait FromDigits[T] { + def fromDigits(digits: String): T +} +``` +Implementations of the `fromDigits` convert strings of digits to the values of the +implementation type `T`. +The `digits` string consists of digits between `0` and `9`, possibly preceded by a +sign ("+" or "-"). Number separator characters `_` are filtered out before +the string is passed to `fromDigits`. + +The companion object `FromDigits` also defines subclasses of `FromDigits` for +whole numbers with a given radix, for numbers with a decimal point, and for +numbers that can have both a decimal point and an exponent: +```scala +object FromDigits { + + /** A subclass of `FromDigits` that also allows to convert whole number literals + * with a radix other than 10 + */ + trait WithRadix[T] extends FromDigits[T] { + def fromDigits(digits: String): T = fromDigits(digits, 10) + def fromDigits(digits: String, radix: Int): T + } + + /** A subclass of `FromDigits` that also allows to convert number + * literals containing a decimal point ".". + */ + trait Decimal[T] extends FromDigits[T] + + /** A subclass of `FromDigits`that allows also to convert number + * literals containing a decimal point "." or an + * exponent `('e' | 'E')['+' | '-']digit digit*`. + */ + trait Floating[T] extends Decimal[T] + ... +} +``` +A user-defined number type can implement one of those, which signals to the compiler +that hexadecimal numbers, decimal points, or exponents are also accepted in literals +for this type. + +### Error Handling + +`FromDigits` implementations can signal errors by throwing exceptions of some subtype +of `FromDigitsException`. `FromDigitsException` is defined with three subclasses in the +`FromDigits` object as follows: +```scala + abstract class FromDigitsException(msg: String) extends NumberFormatException(msg) + + class NumberTooLarge (msg: String = "number too large") extends FromDigitsException(msg) + class NumberTooSmall (msg: String = "number too small") extends FromDigitsException(msg) + class MalformedNumber(msg: String = "malformed number literal") extends FromDigitsException(msg) +``` + +### Example + +As a fully worked out example, here is an implementation of a new numeric class, `BigFloat`, that accepts numeric literals. `BigFloat` is defined in terms of a `BigInt` mantissa and an `Int` exponent: +```scala +case class BigFloat(mantissa: BigInt, exponent: Int) { + override def toString = s"${mantissa}e${exponent}" +} +``` +`BigFloat` literals can have a decimal point as well as an exponent. E.g. the following expression +should produce the `BigFloat` number `BigFloat(-123, 997)`: +```scala +-0.123E+1000: BigFloat +``` +The companion object of `BigFloat` defines an `apply` constructor method to construct a `BigFloat` +from a `digits` string. Here is a possible implementation: +```scala +object BigFloat { + import scala.util.FromDigits + + def apply(digits: String): BigFloat = { + val (mantissaDigits, givenExponent) = digits.toUpperCase.split('E') match { + case Array(mantissaDigits, edigits) => + val expo = + try FromDigits.intFromDigits(edigits) + catch { + case ex: FromDigits.NumberTooLarge => + throw FromDigits.NumberTooLarge(s"exponent too large: $edigits") + } + (mantissaDigits, expo) + case Array(mantissaDigits) => + (mantissaDigits, 0) + } + val (intPart, exponent) = mantissaDigits.split('.') match { + case Array(intPart, decimalPart) => + (intPart ++ decimalPart, givenExponent - decimalPart.length) + case Array(intPart) => + (intPart, givenExponent) + } + BigFloat(BigInt(intPart), exponent) + } +``` +To accept `BigFloat` literals, all that's needed in addition is a given instance of type +`FromDigits.Floating[BigFloat]`: +```scala + given FromDigits as FromDigits.Floating[BigFloat] { + def fromDigits(digits: String) = apply(digits) + } +} // end BigFloat +``` +Note that the `apply` method does not check the format of the `digits` argument. It is +assumed that only valid arguments are passed. For calls coming from the compiler +that assumption is valid, since the compiler will first check whether a numeric +literal has the correct format before it gets passed on to a conversion method. + +### Compile-Time Errors + +With the setup of the previous section, a literal like +```scala +1e10_0000_000_000: BigFloat +``` +would be expanded by the compiler to +```scala +BigFloat.FromDigits.fromDigits("1e100000000000") +``` +Evaluating this expression throws a `NumberTooLarge` exception at run time. We would like it to +produce a compile-time error instead. We can achieve this by tweaking the `BigFloat` class +with a small dose of meta-programming. The idea is to turn the `fromDigits` method +into a macro, i.e. make it an inline method with a splice as right hand side. +To do this, replace the `FromDigits` instance in the `BigFloat` object by the following two definitions: +```scala +object BigFloat { + ... + + class FromDigits extends FromDigits.Floating[BigFloat] { + def fromDigits(digits: String) = apply(digits) + } + + given as FromDigits { + override inline def fromDigits(digits: String) = ${ + fromDigitsImpl('digits) + } + } +``` +Note that an inline method cannot directly fill in for an abstract method, since it produces +no code that can be executed at runtime. That's why we define an intermediary class +`FromDigits` that contains a fallback implementation which is then overridden by the inline +method in the `FromDigits` given instance. That method is defined in terms of a macro +implementation method `fromDigitsImpl`. Here is its definition: +```scala + private def fromDigitsImpl(digits: Expr[String]) given (ctx: QuoteContext): Expr[BigFloat] = + digits match { + case Const(ds) => + try { + val BigFloat(m, e) = apply(ds) + '{BigFloat(${m.toExpr}, ${e.toExpr})} + } + catch { + case ex: FromDigits.FromDigitsException => + ctx.error(ex.getMessage) + '{BigFloat(0, 0)} + } + case digits => + '{apply($digits)} + } +} // end BigFloat +``` +The macro implementation takes an argument of type `Expr[String]` and yields +a result of type `Expr[BigFloat]`. It tests whether its argument is a constant +string. If that's the case, it converts the string using the `apply` method +and lifts the resulting `BigFloat` back to `Expr` level. For non-constant +strings `fromDigitsImpl(digits)` is simply `apply(digits)`, i.e. everything is +evaluated at runtime in this case. + +The interesting part is the `catch` part of the case where `digits` is constant. +If the `apply` method throws a `FromDigitsException`, the exception's message is issued as a compile time error +in the `ctx.error(ex.getMessage)` call. + +With this new implementation, a definition like +```scala + val x: BigFloat = 1234.45e3333333333 +``` +would give a compile time error message: +```scala +3 | val x: BigFloat = 1234.45e3333333333 + | ^^^^^^^^^^^^^^^^^^ + | exponent too large: 3333333333 +``` + + + + diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 62c1f3d1825c..33e9921a6bb9 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -107,6 +107,8 @@ sidebar: url: docs/reference/other-new-features/indentation.html - title: Other Changed Features subsection: + - title: Numeric Literals + url: docs/reference/changed-features/numeric-literals.html - title: Structural Types url: docs/reference/changed-features/structural-types.html - title: Operators diff --git a/library/src/scala/util/FromDigits.scala b/library/src/scala/util/FromDigits.scala new file mode 100644 index 000000000000..62e9843c56bd --- /dev/null +++ b/library/src/scala/util/FromDigits.scala @@ -0,0 +1,164 @@ +package scala.util +import scala.math.{BigInt} +import quoted._ +import quoted.matching._ +import internal.Chars.digit2int +import annotation.internal.sharable + +/** A typeclass for types that admit numeric literals. + */ +trait FromDigits[T] { + + /** Convert `digits` string to value of type `T` + * `digits` can contain + * - sign `+` or `-` + * - sequence of digits between 0 and 9 + * + * @throws MalformedNumber if digit string is not legal for the given type + * @throws NumberTooLarge if value of result does not fit into `T`'s range + * @throws NumberTooSmall in case of numeric underflow (e.g. a non-zero + * floating point literal that produces a zero value) + */ + def fromDigits(digits: String): T +} + +object FromDigits { + + /** A subclass of `FromDigits` that also allows to convert whole number literals + * with a radix other than 10 + */ + trait WithRadix[T] extends FromDigits[T] { + def fromDigits(digits: String): T = fromDigits(digits, 10) + + /** Convert digits string with given radix to numberof type `T`. + * E.g. if radix is 16, digits `a..f` and `A..F` are also allowed. + */ + def fromDigits(digits: String, radix: Int): T + } + + /** A subclass of `FromDigits` that also allows to convert number + * literals containing a decimal point ".". + */ + trait Decimal[T] extends FromDigits[T] + + /** A subclass of `FromDigits`that allows also to convert number + * literals containing a decimal point "." or an + * exponent `('e' | 'E')['+' | '-']digit digit*`. + */ + trait Floating[T] extends Decimal[T] + + /** The base type for exceptions that can be thrown from + * `fromDigits` conversions + */ + abstract class FromDigitsException(msg: String) extends NumberFormatException(msg) + + /** Thrown if value of result does not fit into result type's range */ + class NumberTooLarge(msg: String = "number too large") extends FromDigitsException(msg) + + /** Thrown in case of numeric underflow (e.g. a non-zero + * floating point literal that produces a zero value) + */ + class NumberTooSmall(msg: String = "number too small") extends FromDigitsException(msg) + + /** Thrown if digit string is not legal for the given type */ + class MalformedNumber(msg: String = "malformed number literal") extends FromDigitsException(msg) + + /** Convert digits and radix to integer value (either int or Long) + * This is tricky because of the max negative value. + * Note: We cannot use java.lang.Integer.valueOf or java.lang.Long.valueOf + * since these do not handle unsigned hex numbers greater than the maximal value + * correctly. + */ + private def integerFromDigits(digits: String, radix: Int, limit: Long): Long = { + var value: Long = 0 + val divider = if (radix == 10) 1 else 2 + var i = 0 + var negated = false + val len = digits.length + if (0 < len && (digits(0) == '-' || digits(0) == '+')) { + negated = digits(0) == '-' + i += 1 + } + if (i == len) throw MalformedNumber() + while (i < len) { + val c = digits(i) + val d = digit2int(c, radix) + if (d < 0) throw MalformedNumber() + if (value < 0 || + limit / (radix / divider) < value || + limit - (d / divider) < value * (radix / divider) && + !(negated && limit == value * radix - 1 + d)) throw NumberTooLarge() + value = value * radix + d + i += 1 + } + if (negated) -value else value + } + + /** Convert digit string to Int number + * @param digits The string to convert + * @param radix The radix + * @throws NumberTooLarge if number does not fit within Int range + * @throws MalformedNumber if digits is not a legal digit string. + * Legal strings consist only of digits conforming to radix, + * possibly preceded by a "-" sign. + */ + def intFromDigits(digits: String, radix: Int = 10): Int = + integerFromDigits(digits, radix, Int.MaxValue).toInt + + /** Convert digit string to Long number + * @param digits The string to convert + * @param radix The radix + * @throws NumberTooLarge if the resulting number does not fit within Long range + * @throws MalformedNumber if digits is not a legal digit string. + * Legal strings consist only of digits conforming to radix, + * possibly preceded by a "-" sign. + */ + def longFromDigits(digits: String, radix: Int = 10): Long = + integerFromDigits(digits, radix, Long.MaxValue) + + @sharable private val zeroFloat = raw"-?[0.]+(?:[eE][+-]?[0-9]+)?[fFdD]?".r + + /** Convert digit string to Float number + * @param digits The string to convert + * @throws NumberTooLarge if the resulting number is infinite + * @throws NumberTooSmall if the resulting number is 0.0f, yet the digits + * string contains non-zero digits before the exponent. + * @throws MalformedNumber if digits is not a legal digit string for floating point numbers. + */ + def floatFromDigits(digits: String): Float = { + val x: Float = + try java.lang.Float.parseFloat(digits) + catch { + case ex: NumberFormatException => throw MalformedNumber() + } + if (x.isInfinite) throw NumberTooLarge() + if (x == 0.0f && !zeroFloat.pattern.matcher(digits).matches) throw NumberTooSmall() + x + } + + /** Convert digit string to Double number + * @param digits The string to convert + * @throws NumberTooLarge if the resulting number is infinite + * @throws NumberTooSmall if the resulting number is 0.0d, yet the digits + * string contains non-zero digits before the exponent. + * @throws MalformedNumber if digits is not a legal digit string for floating point numbers.. + */ + def doubleFromDigits(digits: String): Double = { + val x: Double = + try java.lang.Double.parseDouble(digits) + catch { + case ex: NumberFormatException => throw MalformedNumber() + } + if (x.isInfinite) throw NumberTooLarge() + if (x == 0.0d && !zeroFloat.pattern.matcher(digits).matches) throw NumberTooSmall() + x + } + + given BigIntFromDigits as FromDigits.WithRadix[BigInt] { + def fromDigits(digits: String, radix: Int): BigInt = BigInt(digits, radix) + } + + given BigDecimalFromDigits as FromDigits.Decimal[BigDecimal] { + def fromDigits(digits: String): BigDecimal = BigDecimal(digits) + } +} diff --git a/tests/neg-with-compiler/GenericNumLits/Even_1.scala b/tests/neg-with-compiler/GenericNumLits/Even_1.scala new file mode 100644 index 000000000000..be5a8b466381 --- /dev/null +++ b/tests/neg-with-compiler/GenericNumLits/Even_1.scala @@ -0,0 +1,37 @@ +import scala.util.FromDigits +import scala.quoted._ +import scala.quoted.matching._ + +case class Even(n: Int) +object Even { + + private def evenFromDigits(digits: String): Even = { + val intValue = FromDigits.intFromDigits(digits) + if (intValue % 2 == 0) Even(intValue) + else throw FromDigits.MalformedNumber(s"$digits is odd") + } + + private def evenFromDigitsImpl(digits: Expr[String]) given (ctx: QuoteContext): Expr[Even] = digits match { + case Const(ds) => + val ev = + try evenFromDigits(ds) + catch { + case ex: FromDigits.FromDigitsException => + ctx.error(ex.getMessage) + Even(0) + } + '{Even(${ev.n.toExpr})} + case _ => + '{evenFromDigits($digits)} + } + + class EvenFromDigits extends FromDigits[Even] { + def fromDigits(digits: String) = evenFromDigits(digits) + } + + given as EvenFromDigits { + override inline def fromDigits(digits: String) = ${ + evenFromDigitsImpl('digits) + } + } +} diff --git a/tests/neg-with-compiler/GenericNumLits/Test_2.scala b/tests/neg-with-compiler/GenericNumLits/Test_2.scala new file mode 100644 index 000000000000..783ec1bf3e53 --- /dev/null +++ b/tests/neg-with-compiler/GenericNumLits/Test_2.scala @@ -0,0 +1,5 @@ +object Test extends App { + + val e1: Even = 1234 + val e2: Even = 123 // error: 123 is odd +} \ No newline at end of file diff --git a/tests/neg/BigFloat/BigFloat_1.scala b/tests/neg/BigFloat/BigFloat_1.scala new file mode 100644 index 000000000000..ad8439368670 --- /dev/null +++ b/tests/neg/BigFloat/BigFloat_1.scala @@ -0,0 +1,66 @@ +package test +import scala.util.FromDigits +import scala.quoted._ +import scala.quoted.matching._ + +case class BigFloat(mantissa: BigInt, exponent: Int) { + override def toString = s"${mantissa}e${exponent}" +} + +object BigFloat extends App { + def apply(digits: String): BigFloat = { + val (mantissaDigits, givenExponent) = digits.toUpperCase.split('E') match { + case Array(mantissaDigits, edigits) => + val expo = + try FromDigits.intFromDigits(edigits) + catch { + case ex: FromDigits.NumberTooLarge => + throw FromDigits.NumberTooLarge(s"exponent too large: $edigits") + } + (mantissaDigits, expo) + case Array(mantissaDigits) => + (mantissaDigits, 0) + } + val (intPart, exponent) = mantissaDigits.split('.') match { + case Array(intPart, decimalPart) => + (intPart ++ decimalPart, givenExponent - decimalPart.length) + case Array(intPart) => + (intPart, givenExponent) + } + BigFloat(BigInt(intPart), exponent) + } + + private def fromDigitsImpl(digits: Expr[String]) given (ctx: QuoteContext): Expr[BigFloat] = + digits match { + case Const(ds) => + try { + val BigFloat(m, e) = apply(ds) + '{BigFloat(${m.toExpr}, ${e.toExpr})} + } + catch { + case ex: FromDigits.FromDigitsException => + ctx.error(ex.getMessage) + '{BigFloat(0, 0)} + } + case digits => + '{apply($digits)} + } + + class BigFloatFromDigits extends FromDigits.Floating[BigFloat] { + def fromDigits(digits: String) = apply(digits) + } + + given as BigFloatFromDigits { + override inline def fromDigits(digits: String) = ${ + fromDigitsImpl('digits) + } + } + + // Should be in StdLib: + + given as Liftable[BigInt] { + def toExpr(x: BigInt) = + '{BigInt(${x.toString.toExpr})} + } +} + diff --git a/tests/neg/BigFloat/Test_2.scala b/tests/neg/BigFloat/Test_2.scala new file mode 100644 index 000000000000..fa7e6a8380e2 --- /dev/null +++ b/tests/neg/BigFloat/Test_2.scala @@ -0,0 +1,4 @@ +import test.BigFloat +object Test extends App { + val x: BigFloat = 1234.45e3333333333 // error: exponent too large +} \ No newline at end of file diff --git a/tests/neg/GenericNumLits/Test_3.scala b/tests/neg/GenericNumLits/Test_3.scala new file mode 100644 index 000000000000..ee3f799b7b42 --- /dev/null +++ b/tests/neg/GenericNumLits/Test_3.scala @@ -0,0 +1,3 @@ +object Test extends App { + val e3: Even = 123456789101111 // error: number too large +} \ No newline at end of file diff --git a/tests/neg/t6124.check b/tests/neg/t6124.check index a03500d944a8..7368a40c9131 100644 --- a/tests/neg/t6124.check +++ b/tests/neg/t6124.check @@ -30,10 +30,6 @@ 10 | def s = 3_.1 // error | ^ | trailing separator is not allowed --- Error: tests/neg/t6124.scala:12:17 ---------------------------------------------------------------------------------- -12 | def tooSmall = 1.0E-325 // error - | ^ - | double precision floating point number too small -- Error: tests/neg/t6124.scala:17:13 ---------------------------------------------------------------------------------- 17 | val pi1 = 3_.1415F // error | ^ @@ -58,6 +54,10 @@ 26 | val x8 = 0x52_ // error | ^ | trailing separator is not allowed +-- Error: tests/neg/t6124.scala:12:17 ---------------------------------------------------------------------------------- +12 | def tooSmall = 1.0E-325 // error + | ^^^^^^^^ + | number too small -- [E008] Member Not Found Error: tests/neg/t6124.scala:18:14 ---------------------------------------------------------- 18 | val pi2 = 3._1415F // error | ^^^^^^^^ diff --git a/tests/pos/patmat.scala b/tests/pos/patmat.scala index 94d4580c5267..c55308d9810a 100644 --- a/tests/pos/patmat.scala +++ b/tests/pos/patmat.scala @@ -8,7 +8,7 @@ object Test { xs.length match { case 0 => println("0") - case 1 => println("1") + case 1: Int => println("1") case 2 => println("2") case 3 => println("3") case 4 => println("4") @@ -16,7 +16,7 @@ object Test { } (xs.length, xs) match { - case (0, Nil) => println("1") + case (0, Nil: List[Int]) => println("1") case (_, Nil) => println("2") case (0, _) => println("3") case (x, y) => println("4") @@ -46,4 +46,9 @@ object Test { case Some(s) => println(s) case None => println("nothing") } + + type IntPair = (Int, Int) + ??? match { + case (x, y): IntPair => x * y + } } diff --git a/tests/run-with-compiler/BigFloat.check b/tests/run-with-compiler/BigFloat.check new file mode 100644 index 000000000000..b5c00805e346 --- /dev/null +++ b/tests/run-with-compiler/BigFloat.check @@ -0,0 +1,5 @@ +123445e-2 +123445678e-5 +123445e1 +-123e997 +too large diff --git a/tests/run-with-compiler/BigFloat/BigFloat_1.scala b/tests/run-with-compiler/BigFloat/BigFloat_1.scala new file mode 100644 index 000000000000..ad8439368670 --- /dev/null +++ b/tests/run-with-compiler/BigFloat/BigFloat_1.scala @@ -0,0 +1,66 @@ +package test +import scala.util.FromDigits +import scala.quoted._ +import scala.quoted.matching._ + +case class BigFloat(mantissa: BigInt, exponent: Int) { + override def toString = s"${mantissa}e${exponent}" +} + +object BigFloat extends App { + def apply(digits: String): BigFloat = { + val (mantissaDigits, givenExponent) = digits.toUpperCase.split('E') match { + case Array(mantissaDigits, edigits) => + val expo = + try FromDigits.intFromDigits(edigits) + catch { + case ex: FromDigits.NumberTooLarge => + throw FromDigits.NumberTooLarge(s"exponent too large: $edigits") + } + (mantissaDigits, expo) + case Array(mantissaDigits) => + (mantissaDigits, 0) + } + val (intPart, exponent) = mantissaDigits.split('.') match { + case Array(intPart, decimalPart) => + (intPart ++ decimalPart, givenExponent - decimalPart.length) + case Array(intPart) => + (intPart, givenExponent) + } + BigFloat(BigInt(intPart), exponent) + } + + private def fromDigitsImpl(digits: Expr[String]) given (ctx: QuoteContext): Expr[BigFloat] = + digits match { + case Const(ds) => + try { + val BigFloat(m, e) = apply(ds) + '{BigFloat(${m.toExpr}, ${e.toExpr})} + } + catch { + case ex: FromDigits.FromDigitsException => + ctx.error(ex.getMessage) + '{BigFloat(0, 0)} + } + case digits => + '{apply($digits)} + } + + class BigFloatFromDigits extends FromDigits.Floating[BigFloat] { + def fromDigits(digits: String) = apply(digits) + } + + given as BigFloatFromDigits { + override inline def fromDigits(digits: String) = ${ + fromDigitsImpl('digits) + } + } + + // Should be in StdLib: + + given as Liftable[BigInt] { + def toExpr(x: BigInt) = + '{BigInt(${x.toString.toExpr})} + } +} + diff --git a/tests/run-with-compiler/BigFloat/Test_2.scala b/tests/run-with-compiler/BigFloat/Test_2.scala new file mode 100644 index 000000000000..cae28d1ab700 --- /dev/null +++ b/tests/run-with-compiler/BigFloat/Test_2.scala @@ -0,0 +1,20 @@ +import test.BigFloat +import scala.util.FromDigits +object Test extends App { + + + println(BigFloat("1234.45")) + println(BigFloat("1234.45" ++ "678")) + println(BigFloat("1234.45e3")) + println(BigFloat("-0.123E+1000")) + try println(BigFloat("1234.45e3333333333")) + catch { + case ex: FromDigits.FromDigitsException => println("too large") + } + + def check(x: BigFloat, digits: String) = + assert(x == BigFloat(digits), x) + + check(1234.45, "1234.45") + check(1234.45e3, "1234.45e3") +} \ No newline at end of file diff --git a/tests/run/genericNumLits.check b/tests/run/genericNumLits.check new file mode 100644 index 000000000000..9376befb1c30 --- /dev/null +++ b/tests/run/genericNumLits.check @@ -0,0 +1,5 @@ +13232202002020202020202 +-50390822187678765893036 +132322020020.223 +Even(1234) +malformed diff --git a/tests/run/genericNumLits.scala b/tests/run/genericNumLits.scala new file mode 100644 index 000000000000..6cb657d692da --- /dev/null +++ b/tests/run/genericNumLits.scala @@ -0,0 +1,57 @@ +import scala.util.FromDigits +object Test extends App { + + val x: BigInt = 13232202002020202020202 + val y: BigInt = -0xaabb12345ACF12345AC + val z: BigDecimal = 132322020020.223 + + case class Even(n: Int) + + given as FromDigits[Even] { + def fromDigits(digits: String): Even = { + val intValue = digits.toInt + if (intValue % 2 == 0) Even(intValue) + else throw FromDigits.MalformedNumber() + } + } + + val e: Even = 1234 + + println(x) + println(y) + println(z) + println(e) + + try println(123: Even) + catch { + case ex: FromDigits.MalformedNumber => println("malformed") + } + + val N = 10 + + x match { + case 13_232_202_002_020_202_020_202 => () + } + (x: Any) match { + case 13232202002020202020202: BigInt => () + } + y match { + case 13232202002020202020202 => assert(false) + case -0xaabb12345ACF12345AC => () + } + z match { + case 132322020020.223 => () + } + (z: Any) match { + case 132_322_020_020.223: BigDecimal => () + } + + e match { + case 1234 => + } + (e: Any) match { + case 12: Even => assert(false) + case 1234: Even => + case _: Even => + } +} \ No newline at end of file From 93291a446c067fc804c673efe9ab54e8d4032541 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 30 Aug 2019 11:20:21 +0200 Subject: [PATCH 4/5] Fix rebase breakage --- .../dotty/tools/dotc/core/Definitions.scala | 18 +++----- .../tools/dotc/typer/PrepareInlineable.scala | 45 ++++++++++--------- tests/neg/GenericNumLits/Even_1.scala | 37 +++++++++++++++ .../{Test_3.scala => Test_2.scala} | 3 ++ 4 files changed, 70 insertions(+), 33 deletions(-) create mode 100644 tests/neg/GenericNumLits/Even_1.scala rename tests/neg/GenericNumLits/{Test_3.scala => Test_2.scala} (56%) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 0edd185db23e..80254f917f6c 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -669,18 +669,12 @@ class Definitions { @tu lazy val StatsModule: Symbol = ctx.requiredModule("dotty.tools.dotc.util.Stats") @tu lazy val Stats_doRecord: Symbol = StatsModule.requiredMethod("doRecord") - @threadUnsafe lazy val FromDigitsClass: ClassSymbolPerRun = perRunClass(ctx.requiredClassRef("scala.util.FromDigits")) - @threadUnsafe lazy val FromDigits_WithRadixClass: ClassSymbolPerRun = perRunClass(ctx.requiredClassRef("scala.util.FromDigits.WithRadix")) - @threadUnsafe lazy val FromDigits_DecimalClass: ClassSymbolPerRun = perRunClass(ctx.requiredClassRef("scala.util.FromDigits.Decimal")) - @threadUnsafe lazy val FromDigits_FloatingClass: ClassSymbolPerRun = perRunClass(ctx.requiredClassRef("scala.util.FromDigits.Floating")) - - @threadUnsafe lazy val XMLTopScopeModule: SymbolPerRun = perRunSym(ctx.requiredModuleRef("scala.xml.TopScope")) - - @threadUnsafe lazy val CommandLineParserModule: SymbolPerRun = perRunSym(ctx.requiredModuleRef("scala.util.CommandLineParser")) - @threadUnsafe lazy val CLP_ParseError: ClassSymbolPerRun = perRunClass(CommandLineParserModule.requiredClass("ParseError").typeRef) - @threadUnsafe lazy val CLP_parseArgument: SymbolPerRun = perRunSym(CommandLineParserModule.requiredMethodRef("parseArgument")) - @threadUnsafe lazy val CLP_parseRemainingArguments: SymbolPerRun = perRunSym(CommandLineParserModule.requiredMethodRef("parseRemainingArguments")) - @threadUnsafe lazy val CLP_showError: SymbolPerRun = perRunSym(CommandLineParserModule.requiredMethodRef("showError")) + @tu lazy val FromDigitsClass: ClassSymbol = ctx.requiredClass("scala.util.FromDigits") + @tu lazy val FromDigits_WithRadixClass: ClassSymbol = ctx.requiredClass("scala.util.FromDigits.WithRadix") + @tu lazy val FromDigits_DecimalClass: ClassSymbol = ctx.requiredClass("scala.util.FromDigits.Decimal") + @tu lazy val FromDigits_FloatingClass: ClassSymbol = ctx.requiredClass("scala.util.FromDigits.Floating") + + @tu lazy val XMLTopScopeModule: Symbol = ctx.requiredModule("scala.xml.TopScope") @tu lazy val CommandLineParserModule: Symbol = ctx.requiredModule("scala.util.CommandLineParser") @tu lazy val CLP_ParseError: ClassSymbol = CommandLineParserModule.requiredClass("ParseError").typeRef.symbol.asClass diff --git a/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala b/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala index 50252f08bbd0..2df161364bac 100644 --- a/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala +++ b/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala @@ -222,39 +222,42 @@ object PrepareInlineable { val inlineCtx = ctx inlined.updateAnnotation(LazyBodyAnnotation { _ => implicit val ctx = inlineCtx - val rawBody = treeExpr(ctx) - val typedBody = - if (ctx.reporter.hasErrors) rawBody - else ctx.compilationUnit.inlineAccessors.makeInlineable(rawBody) - checkInlineMethod(inlined, typedBody) - val inlineableBody = typedBody - inlining.println(i"Body to inline for $inlined: $inlineableBody") - inlineableBody + val initialErrorCount = ctx.reporter.errorCount + var inlinedBody = treeExpr(ctx) + if (ctx.reporter.errorCount == initialErrorCount) { + inlinedBody = ctx.compilationUnit.inlineAccessors.makeInlineable(inlinedBody) + checkInlineMethod(inlined, inlinedBody) + if (ctx.reporter.errorCount != initialErrorCount) + inlinedBody = EmptyTree + } + inlining.println(i"Body to inline for $inlined: $inlinedBody") + inlinedBody }) } } def checkInlineMethod(inlined: Symbol, body: Tree)(implicit ctx: Context): Unit = { + if (inlined.owner.isClass && inlined.owner.seesOpaques) + ctx.error(em"Implementation restriction: No inline methods allowed where opaque type aliases are in scope", inlined.sourcePos) if (ctx.outer.inInlineMethod) ctx.error(ex"implementation restriction: nested inline methods are not supported", inlined.sourcePos) if (inlined.name.isUnapplyName && tupleArgs(body).isEmpty) ctx.warning( em"inline unapply method can be rewritten only if its right hand side is a tuple (e1, ..., eN)", body.sourcePos) - } + if (inlined.is(Macro) && !ctx.isAfterTyper) { - def checkInlineMacro(sym: Symbol, rhs: Tree, pos: SourcePosition)(implicit ctx: Context) = - if (sym.is(Macro) && !ctx.isAfterTyper) { - def isValidMacro(tree: Tree)(implicit ctx: Context): Unit = tree match { + def checkMacro(tree: Tree): Unit = tree match { case Spliced(code) => + if (code.symbol.flags.is(Inline)) + ctx.error("Macro cannot be implemented with an `inline` method", code.sourcePos) Splicer.checkValidMacroBody(code) - new PCPCheckAndHeal(freshStagingContext).transform(rhs) // Ignore output, only check PCP - - case Block(List(stat), Literal(Constants.Constant(()))) => isValidMacro(stat) - case Block(Nil, expr) => isValidMacro(expr) - case Typed(expr, _) => isValidMacro(expr) + new PCPCheckAndHeal(freshStagingContext).transform(body) // Ignore output, only check PCP + case Block(List(stat), Literal(Constants.Constant(()))) => checkMacro(stat) + case Block(Nil, expr) => checkMacro(expr) + case Typed(expr, _) => checkMacro(expr) case Block(DefDef(nme.ANON_FUN, _, _, _, _) :: Nil, Closure(_, fn, _)) if fn.symbol.info.isImplicitMethod => - // TODO Suppot this pattern + // TODO Support this pattern ctx.error( """Macros using a return type of the form `foo(): given X => Y` are not yet supported. | @@ -269,9 +272,9 @@ object PrepareInlineable { | | * The contents of the splice must call a static method | * All arguments must be quoted or inline - """.stripMargin, pos) + """.stripMargin, inlined.sourcePos) } - isValidMacro(rhs) + checkMacro(body) } + } } - diff --git a/tests/neg/GenericNumLits/Even_1.scala b/tests/neg/GenericNumLits/Even_1.scala new file mode 100644 index 000000000000..be5a8b466381 --- /dev/null +++ b/tests/neg/GenericNumLits/Even_1.scala @@ -0,0 +1,37 @@ +import scala.util.FromDigits +import scala.quoted._ +import scala.quoted.matching._ + +case class Even(n: Int) +object Even { + + private def evenFromDigits(digits: String): Even = { + val intValue = FromDigits.intFromDigits(digits) + if (intValue % 2 == 0) Even(intValue) + else throw FromDigits.MalformedNumber(s"$digits is odd") + } + + private def evenFromDigitsImpl(digits: Expr[String]) given (ctx: QuoteContext): Expr[Even] = digits match { + case Const(ds) => + val ev = + try evenFromDigits(ds) + catch { + case ex: FromDigits.FromDigitsException => + ctx.error(ex.getMessage) + Even(0) + } + '{Even(${ev.n.toExpr})} + case _ => + '{evenFromDigits($digits)} + } + + class EvenFromDigits extends FromDigits[Even] { + def fromDigits(digits: String) = evenFromDigits(digits) + } + + given as EvenFromDigits { + override inline def fromDigits(digits: String) = ${ + evenFromDigitsImpl('digits) + } + } +} diff --git a/tests/neg/GenericNumLits/Test_3.scala b/tests/neg/GenericNumLits/Test_2.scala similarity index 56% rename from tests/neg/GenericNumLits/Test_3.scala rename to tests/neg/GenericNumLits/Test_2.scala index ee3f799b7b42..9ba8aec01b9e 100644 --- a/tests/neg/GenericNumLits/Test_3.scala +++ b/tests/neg/GenericNumLits/Test_2.scala @@ -1,3 +1,6 @@ object Test extends App { + + val e1: Even = 1234 + val e2: Even = 123 // error: 123 is odd val e3: Even = 123456789101111 // error: number too large } \ No newline at end of file From e0f2ea0be1419cc6f779acce53e99e5e1427c67e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 30 Aug 2019 12:12:49 +0200 Subject: [PATCH 5/5] Fix typo as suggested in #7134 by @vn971 --- docs/docs/reference/contextual/given-clauses.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/reference/contextual/given-clauses.md b/docs/docs/reference/contextual/given-clauses.md index 8ecc479cfa8b..020fa95ef7e9 100644 --- a/docs/docs/reference/contextual/given-clauses.md +++ b/docs/docs/reference/contextual/given-clauses.md @@ -13,7 +13,7 @@ For example, with the [given instances](./delegates.md) defined previously, a maximum function that works for any arguments for which an ordering exists can be defined as follows: ```scala def max[T](x: T, y: T) given (ord: Ord[T]): T = - if (ord.compare(x, y) < 1) y else x + if (ord.compare(x, y) < 0) y else x ``` Here, `ord` is an _implicit parameter_ introduced with a `given` clause. The `max` method can be applied as follows: