From 5951f69a74f5faeff1e19d52da08701786a3def5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 1 May 2020 12:13:30 +0200 Subject: [PATCH 1/9] Simplify treatment of single-token lookahead in Scanner No need to start a separate scanner; we can simply use the existing prev/next logic. --- .../dotty/tools/dotc/parsing/Parsers.scala | 20 ++++---- .../dotty/tools/dotc/parsing/Scanners.scala | 46 +++++++++---------- 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 7c1b2c522895..50e8ad11d24d 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -206,7 +206,9 @@ object Parsers { def isBindingIntro: Boolean = { in.token match { case USCORE => true - case IDENTIFIER | BACKQUOTED_IDENT => in.lookaheadIn(BitSet(ARROW, CTXARROW)) + case IDENTIFIER | BACKQUOTED_IDENT => + val nxt = in.lookahead.token + nxt == ARROW || nxt == CTXARROW case LPAREN => val lookahead = in.LookaheadScanner() lookahead.skipParens() @@ -235,7 +237,7 @@ object Parsers { */ def isSplice: Boolean = in.token == IDENTIFIER && in.name(0) == '$' && { - if (in.name.length == 1) in.lookaheadIn(BitSet(LBRACE)) + if in.name.length == 1 then in.lookahead.token == LBRACE else (staged & StageKind.Quoted) != 0 } @@ -1506,7 +1508,10 @@ object Parsers { /** Is current ident a `*`, and is it followed by a `)` or `,`? */ def isPostfixStar: Boolean = - in.name == nme.raw.STAR && in.lookaheadIn(BitSet(RPAREN, COMMA)) + in.name == nme.raw.STAR && { + val nxt = in.lookahead.token + nxt == RPAREN || nxt == COMMA + } def infixTypeRest(t: Tree): Tree = infixOps(t, canStartTypeTokens, refinedType, isType = true, isOperator = !isPostfixStar) @@ -1588,11 +1593,10 @@ object Parsers { else if (in.token == LBRACE) atSpan(in.offset) { RefinedTypeTree(EmptyTree, refinement()) } else if (isSimpleLiteral) { SingletonTypeTree(literal(inType = true)) } - else if (isIdent(nme.raw.MINUS) && in.lookaheadIn(numericLitTokens)) { + else if isIdent(nme.raw.MINUS) && numericLitTokens.contains(in.lookahead.token) then val start = in.offset in.nextToken() SingletonTypeTree(literal(negOffset = start, inType = true)) - } else if (in.token == USCORE) { if sourceVersion.isAtLeast(`3.1`) then deprecationWarning(em"`_` is deprecated for wildcard arguments of types: use `?` instead") @@ -2012,7 +2016,7 @@ object Parsers { case _ => if isIdent(nme.inline) && !in.inModifierPosition() - && in.lookaheadIn(in.canStartExprTokens) + && in.canStartExprTokens.contains(in.lookahead.token) then val start = in.skipToken() in.token match @@ -3005,7 +3009,7 @@ object Parsers { val isParams = !impliedMods.is(Given) || startParamTokens.contains(in.token) - || isIdent && (in.name == nme.inline || in.lookaheadIn(BitSet(COLON))) + || isIdent && (in.name == nme.inline || in.lookahead.token == COLON) if isParams then commaSeparated(() => param()) else contextTypes(ofClass, nparams) checkVarArgsRules(clause) @@ -3567,7 +3571,7 @@ object Parsers { val tparams = typeParamClauseOpt(ParamOwner.Def) newLineOpt() val vparamss = - if in.token == LPAREN && in.lookaheadIn(nme.using) + if in.token == LPAREN && in.lookahead.isIdent(nme.using) then paramClauses(givenOnly = true) else Nil newLinesOpt() diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 85894324aac4..d818d43e7132 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -98,6 +98,13 @@ object Scanners { this.strVal = td.strVal this.base = td.base } + + def isNewLine = token == NEWLINE || token == NEWLINES + def isIdent = token == IDENTIFIER || token == BACKQUOTED_IDENT + def isIdent(name: Name) = token == IDENTIFIER && this.name == name + + def isNestedStart = token == LBRACE || token == INDENT + def isNestedEnd = token == RBRACE || token == OUTDENT } abstract class ScannerCommon(source: SourceFile)(implicit ctx: Context) extends CharArrayReader with TokenData { @@ -535,7 +542,7 @@ object Scanners { def observeColonEOL(): Unit = if token == COLON then - lookahead() + lookAhead() val atEOL = isAfterLineEnd || token == EOF reset() if atEOL then token = COLONEOL @@ -562,7 +569,7 @@ object Scanners { insert(OUTDENT, offset) case _ => - def lookahead() = { + def lookAhead() = { prev.copyFrom(this) lastOffset = lastCharOffset fetchToken() @@ -591,12 +598,12 @@ object Scanners { } token match { case CASE => - lookahead() + lookAhead() if (token == CLASS) fuse(CASECLASS) else if (token == OBJECT) fuse(CASEOBJECT) else reset() case SEMI => - lookahead() + lookAhead() if (token != ELSE) reset() case COMMA => def isEnclosedInParens(r: Region): Boolean = r match @@ -608,7 +615,7 @@ object Scanners { insert(OUTDENT, offset) currentRegion = r.outer case _ => - lookahead() + lookAhead() if isAfterLineEnd && (token == RPAREN || token == RBRACKET || token == RBRACE || token == OUTDENT) then @@ -892,6 +899,15 @@ object Scanners { // Lookahead --------------------------------------------------------------- + /** The next token after this one. + * Newlines and indent/unindent tokens are skipped. + */ + def lookahead: TokenData = + if next.token == EMPTY then + lookAhead() + reset() + next + class LookaheadScanner() extends Scanner(source, offset) /** Skip matching pairs of `(...)` or `[...]` parentheses. @@ -904,17 +920,6 @@ object Scanners { if token == opening && multiple then skipParens() else nextToken() nextToken() - /** Is the token following the current one in `tokens`? */ - def lookaheadIn(follow: BitSet | TermName): Boolean = - val lookahead = LookaheadScanner() - while - lookahead.nextToken() - lookahead.isNewLine - do () - follow match - case tokens: BitSet => tokens.contains(lookahead.token) - case name: TermName => lookahead.token == IDENTIFIER && lookahead.name == name - /** Is the current token in a position where a modifier is allowed? */ def inModifierPosition(): Boolean = { val lookahead = LookaheadScanner() @@ -1010,14 +1015,7 @@ object Scanners { isSoftModifier && inModifierPosition() def isSoftModifierInParamModifierPosition: Boolean = - isSoftModifier && !lookaheadIn(BitSet(COLON)) - - def isNewLine = token == NEWLINE || token == NEWLINES - def isIdent = token == IDENTIFIER || token == BACKQUOTED_IDENT - def isIdent(name: Name) = token == IDENTIFIER && this.name == name - - def isNestedStart = token == LBRACE || token == INDENT - def isNestedEnd = token == RBRACE || token == OUTDENT + isSoftModifier && lookahead.token != COLON def canStartStatTokens = if migrateTo3 then canStartStatTokens2 else canStartStatTokens3 From 5fb7c4edb9f9b9cc9d4f25a249447235c61ac7c9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 1 May 2020 14:14:23 +0200 Subject: [PATCH 2/9] Refactor handling of paths Instead of passing a closure down, use one-token lookahead going froward. This simplifies the logic of parsing paths. --- .../dotty/tools/dotc/parsing/Parsers.scala | 155 +++++++++--------- .../dotty/tools/dotc/parsing/Scanners.scala | 3 + .../dotty/tools/repl/TabcompleteTests.scala | 2 +- 3 files changed, 82 insertions(+), 78 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 50e8ad11d24d..4df5ebe9404a 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1045,6 +1045,9 @@ object Parsers { if in.token == MATCH then matchClause(t) else Select(t, ident()) } + def idSelector(t: Tree): Tree = + atSpan(startOffset(t), in.offset) { Select(t, ident()) } + /** Selectors ::= id { `.' id } * * Accept `.' separated identifiers acting as a selectors on given tree `t`. @@ -1066,45 +1069,46 @@ object Parsers { if (in.token == DOT) { in.nextToken(); selectors(t, finish) } else t + def dotSelections(t: Tree): Tree = + if (in.token == DOT) { in.nextToken(); dotSelections(t) } + else t + private val id: Tree => Tree = x => x - /** Path ::= StableId - * | [id `.'] this - * - * @param thisOK If true, the path can end with the keyword `this`. - * If false, another selection is required after the `this`. - * @param finish An alternative parse in case the token following a `.' is not an identifier. - * If the alternative does not apply, its tree argument is returned unchanged. + /** SimpleRef ::= id + * | [id ‘.’] ‘this’ + * | [id ‘.’] ‘super’ [ClassQualifier] ‘.’ id */ - def path(thisOK: Boolean, finish: Tree => Tree = id): Tree = { + def simpleRef(): Tree = val start = in.offset - def handleThis(qual: Ident) = { + + def handleThis(qual: Ident) = in.nextToken() - val t = atSpan(start) { This(qual) } - if (!thisOK && in.token != DOT) syntaxError(DanglingThisInPath(), t.span) - dotSelectors(t, finish) - } - def handleSuper(qual: Ident) = { + atSpan(start) { This(qual) } + + def handleSuper(qual: Ident) = in.nextToken() val mix = mixinQualifierOpt() val t = atSpan(start) { Super(This(qual), mix) } accept(DOT) - dotSelectors(selector(t), finish) - } - if (in.token == THIS) handleThis(EmptyTypeIdent) - else if (in.token == SUPER) handleSuper(EmptyTypeIdent) - else { + idSelector(t) + + if in.token == THIS then handleThis(EmptyTypeIdent) + else if in.token == SUPER then handleSuper(EmptyTypeIdent) + else val t = termIdent() - if (in.token == DOT) { + if in.token == DOT then def qual = cpy.Ident(t)(t.name.toTypeName) - in.nextToken() - if (in.token == THIS) handleThis(qual) - else if (in.token == SUPER) handleSuper(qual) - else selectors(t, finish) - } + in.lookahead.token match + case THIS => + in.nextToken() + handleThis(qual) + case SUPER => + in.nextToken() + handleSuper(qual) + case _ => t else t - } - } + end simpleRef /** MixinQualifier ::= `[' id `]' */ @@ -1112,13 +1116,6 @@ object Parsers { if (in.token == LBRACKET) inBrackets(atSpan(in.offset) { typeIdent() }) else EmptyTypeIdent - /** StableId ::= id - * | Path `.' id - * | [id '.'] super [`[' id `]']`.' id - */ - def stableId(): Tree = - path(thisOK = false) - /** QualId ::= id {`.' id} */ def qualId(): Tree = dotSelectors(termIdent()) @@ -1577,8 +1574,8 @@ object Parsers { /** SimpleType ::= SimpleType TypeArgs * | SimpleType `#' id - * | StableId - * | Path `.' type + * | Singleton `.' id + * | Singleton `.' type * | `(' ArgTypes `)' * | `_' TypeBounds * | Refinement @@ -1613,18 +1610,22 @@ object Parsers { } else if (isSplice) splice(isType = true) - else path(thisOK = false, handleSingletonType) match { - case r @ SingletonTypeTree(_) => r - case r => convertToTypeId(r) - } + else + singletonCompletion(simpleRef()) } - val handleSingletonType: Tree => Tree = t => - if (in.token == TYPE) { + /** Singleton ::= SimpleRef + * | Singleton ‘.’ id + */ + def singletonCompletion(t: Tree): Tree = + if in.token == DOT then in.nextToken() - atSpan(startOffset(t)) { SingletonTypeTree(t) } - } - else t + if in.token == TYPE then + in.nextToken() + atSpan(startOffset(t)) { SingletonTypeTree(t) } + else + singletonCompletion(idSelector(t)) + else convertToTypeId(t) private def simpleTypeRest(t: Tree): Tree = in.token match { case HASH => simpleTypeRest(typeProjection(t)) @@ -2207,7 +2208,7 @@ object Parsers { * | SimpleExpr1 [`_`] * SimpleExpr1 ::= literal * | xmlLiteral - * | Path + * | SimpleRef * | `(` [ExprsInParens] `)` * | SimpleExpr `.` id * | SimpleExpr `.` MatchClause @@ -2223,9 +2224,9 @@ object Parsers { xmlLiteral() case IDENTIFIER => if (isSplice) splice(isType = false) - else path(thisOK = true) + else simpleRef() case BACKQUOTED_IDENT | THIS | SUPER => - path(thisOK = true) + simpleRef() case USCORE => val start = in.skipToken() val pname = WildcardParamName.fresh() @@ -2658,17 +2659,16 @@ object Parsers { * | XmlPattern * | `(' [Patterns] `)' * | SimplePattern1 [TypeArgs] [ArgumentPatterns] - * SimplePattern1 ::= Path + * SimplePattern1 ::= SimpleRef * | SimplePattern1 `.' id * PatVar ::= id * | `_' */ val simplePattern: () => Tree = () => in.token match { - case IDENTIFIER | BACKQUOTED_IDENT | THIS => - path(thisOK = true) match { + case IDENTIFIER | BACKQUOTED_IDENT | THIS | SUPER => + simpleRef() match case id @ Ident(nme.raw.MINUS) if isNumericLit => literal(startOffset(id)) case t => simplePatternRest(t) - } case USCORE => val wildIdent = wildcardIdent() @@ -2694,14 +2694,17 @@ object Parsers { } } - def simplePatternRest(t: Tree): Tree = { - var p = t - if (in.token == LBRACKET) - p = atSpan(startOffset(t), in.offset) { TypeApply(p, typeArgs(namedOK = false, wildOK = false)) } - if (in.token == LPAREN) - p = atSpan(startOffset(t), in.offset) { Apply(p, argumentPatterns()) } - p - } + def simplePatternRest(t: Tree): Tree = + if in.token == DOT then + in.nextToken() + simplePatternRest(idSelector(t)) + else + var p = t + if (in.token == LBRACKET) + p = atSpan(startOffset(t), in.offset) { TypeApply(p, typeArgs(namedOK = false, wildOK = false)) } + if (in.token == LPAREN) + p = atSpan(startOffset(t), in.offset) { Apply(p, argumentPatterns()) } + p /** Patterns ::= Pattern [`,' Pattern] */ @@ -3088,7 +3091,7 @@ object Parsers { ctx.compilationUnit.sourceVersion = Some(SourceVersion.valueOf(imported.toString)) Import(tree, selectors) - /** ImportExpr ::= StableId ‘.’ ImportSpec + /** ImportExpr ::= SimpleRef {‘.’ id} ‘.’ ImportSpec * ImportSpec ::= id * | ‘_’ * | ‘{’ ImportSelectors) ‘}’ @@ -3135,26 +3138,24 @@ object Parsers { Nil selector :: rest - val handleImport: Tree => Tree = tree => + def importSelection(qual: Tree): Tree = + accept(DOT) in.token match case USCORE => - mkTree(tree, ImportSelector(wildcardSelectorId()) :: Nil) + mkTree(qual, ImportSelector(wildcardSelectorId()) :: Nil) case LBRACE => - mkTree(tree, inBraces(importSelectors(idOK = true))) + mkTree(qual, inBraces(importSelectors(idOK = true))) case _ => - tree - - () => { - val p = path(thisOK = false, handleImport) - p match - case _: Import | _: Export => p - case sel @ Select(qual, name) => - val selector = ImportSelector(atSpan(pointOffset(sel)) { Ident(name) }) - mkTree(qual, selector :: Nil).withSpan(sel.span) - case t => - accept(DOT) - mkTree(t, ImportSelector(Ident(nme.WILDCARD)) :: Nil) - } + val start = in.offset + val name = ident() + if in.token == DOT then + importSelection(atSpan(startOffset(qual), start) { Select(qual, name) }) + else + atSpan(startOffset(qual)) { + mkTree(qual, ImportSelector(atSpan(start) { Ident(name) }) :: Nil) + } + + () => importSelection(simpleRef()) } def posMods(start: Int, mods: Modifiers): Modifiers = { diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index d818d43e7132..55d99b0bf0da 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -900,7 +900,10 @@ object Scanners { // Lookahead --------------------------------------------------------------- /** The next token after this one. + * The token is computed via fetchToken, so complex two word + * tokens such as CASECLASS are not recognized. * Newlines and indent/unindent tokens are skipped. + * */ def lookahead: TokenData = if next.token == EMPTY then diff --git a/compiler/test/dotty/tools/repl/TabcompleteTests.scala b/compiler/test/dotty/tools/repl/TabcompleteTests.scala index 373b625aca26..4e6bfbddf102 100644 --- a/compiler/test/dotty/tools/repl/TabcompleteTests.scala +++ b/compiler/test/dotty/tools/repl/TabcompleteTests.scala @@ -100,7 +100,7 @@ class TabcompleteTests extends ReplTest { @Test def importScala = fromInitialState { implicit s => val comp = tabComplete("import scala.") // check that there are no special symbols leaked: , , ... - assertEquals(comp.find(_.startsWith("<")), Some("<:<")) + assertEquals(Some("<:<"), comp.find(_.startsWith("<"))) assert(!comp.contains("package")) } From 5cbc8400913825207ca5cfd7e3e8d07e8950f128 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 1 May 2020 17:57:41 +0200 Subject: [PATCH 3/9] Refactor syntax for ConstrApps Split off the parts of SimpleType that - can be applied to type arguments, or selected with # - can be used in a constructor This gives a bit more precision for type and constructor parsing. It's also a necessary step to be able to add types that are applied to singletons. --- .../dotty/tools/dotc/parsing/Parsers.scala | 83 ++++++++++--------- docs/docs/internals/syntax.md | 37 +++++---- tests/neg/i4373.scala | 4 +- tests/neg/i4373c.scala | 2 +- 4 files changed, 67 insertions(+), 59 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 4df5ebe9404a..db617fff8292 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1572,61 +1572,62 @@ object Parsers { if (isType) TypSplice(expr) else Splice(expr) } - /** SimpleType ::= SimpleType TypeArgs - * | SimpleType `#' id - * | Singleton `.' id - * | Singleton `.' type - * | `(' ArgTypes `)' - * | `_' TypeBounds - * | Refinement - * | Literal - * | ‘$’ ‘{’ Block ‘}’ + /** SimpleType ::= SimpleLiteral + * | ‘?’ SubtypeBounds + * | SimpleType1 */ - def simpleType(): Tree = simpleTypeRest { - if (in.token == LPAREN) - atSpan(in.offset) { - makeTupleOrParens(inParens(argTypes(namedOK = false, wildOK = true))) - } - else if (in.token == LBRACE) - atSpan(in.offset) { RefinedTypeTree(EmptyTree, refinement()) } - else if (isSimpleLiteral) { SingletonTypeTree(literal(inType = true)) } + def simpleType(): Tree = + if isSimpleLiteral then + SingletonTypeTree(literal(inType = true)) else if isIdent(nme.raw.MINUS) && numericLitTokens.contains(in.lookahead.token) then val start = in.offset in.nextToken() SingletonTypeTree(literal(negOffset = start, inType = true)) - else if (in.token == USCORE) { + else if in.token == USCORE then if sourceVersion.isAtLeast(`3.1`) then deprecationWarning(em"`_` is deprecated for wildcard arguments of types: use `?` instead") patch(source, Span(in.offset, in.offset + 1), "?") val start = in.skipToken() typeBounds().withSpan(Span(start, in.lastOffset, start)) - } - else if (isIdent(nme.?)) { + else if isIdent(nme.?) then val start = in.skipToken() typeBounds().withSpan(Span(start, in.lastOffset, start)) - } - else if (isIdent(nme.*) && ctx.settings.YkindProjector.value) { + else if isIdent(nme.*) && ctx.settings.YkindProjector.value then typeIdent() - } + else + simpleType1() + + /** SimpleType1 ::= id + * | Singleton `.' id + * | Singleton `.' type + * | ‘(’ ArgTypes ‘)’ + * | Refinement + * | ‘$’ ‘{’ Block ‘}’ + * | SimpleType1 TypeArgs + * | SimpleType1 `#' id + */ + def simpleType1() = simpleTypeRest { + if in.token == LPAREN then + atSpan(in.offset) { + makeTupleOrParens(inParens(argTypes(namedOK = false, wildOK = true))) + } + else if in.token == LBRACE then + atSpan(in.offset) { RefinedTypeTree(EmptyTree, refinement()) } else if (isSplice) splice(isType = true) else + def singletonCompletion(t: Tree): Tree = + if in.token == DOT then + in.nextToken() + if in.token == TYPE then + in.nextToken() + atSpan(startOffset(t)) { SingletonTypeTree(t) } + else + singletonCompletion(idSelector(t)) + else convertToTypeId(t) singletonCompletion(simpleRef()) } - /** Singleton ::= SimpleRef - * | Singleton ‘.’ id - */ - def singletonCompletion(t: Tree): Tree = - if in.token == DOT then - in.nextToken() - if in.token == TYPE then - in.nextToken() - atSpan(startOffset(t)) { SingletonTypeTree(t) } - else - singletonCompletion(idSelector(t)) - else convertToTypeId(t) - private def simpleTypeRest(t: Tree): Tree = in.token match { case HASH => simpleTypeRest(typeProjection(t)) case LBRACKET => simpleTypeRest(atSpan(startOffset(t)) { @@ -3630,13 +3631,13 @@ object Parsers { /* -------- TEMPLATES ------------------------------------------- */ - /** SimpleConstrApp ::= AnnotType {ParArgumentExprs} + /** ConstrApp ::= SimpleType1 {Annotation} {ParArgumentExprs} */ - val constrApp: () => Tree = () => { - val t = rejectWildcardType(annotType(), fallbackTree = Ident(nme.ERROR)) - // Using Ident(nme.ERROR) to avoid causing cascade errors on non-user-written code + val constrApp: () => Tree = () => + val t = rejectWildcardType(annotTypeRest(simpleType1()), + fallbackTree = Ident(tpnme.ERROR)) + // Using Ident(tpnme.ERROR) to avoid causing cascade errors on non-user-written code if in.token == LPAREN then parArgumentExprss(wrapNew(t)) else t - } /** ConstrApps ::= ConstrApp {(‘,’ | ‘with’) ConstrApp} */ diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 88c49035696c..46770818ad4d 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -128,11 +128,10 @@ Literal ::= SimpleLiteral QualId ::= id {‘.’ id} ids ::= id {‘,’ id} -Path ::= StableId +SimpleRef ::= id | [id ‘.’] ‘this’ -StableId ::= id - | Path ‘.’ id | [id ‘.’] ‘super’ [ClassQualifier] ‘.’ id + ClassQualifier ::= ‘[’ id ‘]’ ``` @@ -153,17 +152,25 @@ InfixType ::= RefinedType {id [nl] RefinedType} RefinedType ::= WithType {[nl | colonEol] Refinement} RefinedTypeTree(t, ds) WithType ::= AnnotType {‘with’ AnnotType} (deprecated) AnnotType ::= SimpleType {Annotation} Annotated(t, annot) -SimpleType ::= SimpleType TypeArgs AppliedTypeTree(t, args) - | SimpleType ‘#’ id Select(t, name) - | StableId - | Path ‘.’ ‘type’ SingletonTypeTree(p) - | ‘(’ ArgTypes ‘)’ Tuple(ts) + +SimpleType ::= SimpleLiteral SingletonTypeTree(l) | ‘?’ SubtypeBounds + | SimpleType ‘(’ Singletons ‘)’ + | SimpleType1 +SimpleType1 ::= id Ident(name) + | Singleton ‘.’ id Select(t, name) + | Singleton ‘.’ ‘type’ SingletonTypeTree(p) + | ‘(’ ArgTypes ‘)’ Tuple(ts) | Refinement RefinedTypeTree(EmptyTree, refinement) - | SimpleLiteral SingletonTypeTree(l) | ‘$’ ‘{’ Block ‘}’ -ArgTypes ::= Type {‘,’ Type} - | NamedTypeArg {‘,’ NamedTypeArg} + | SimpleType1 TypeArgs AppliedTypeTree(t, args) + | SimpleType1 ‘#’ id Select(t, name) +Singleton ::= SimpleRef + | Singleton ‘.’ id +-- not yet | Singleton ‘(’ Singletons ‘)’ +-- not yet | Singleton ‘[’ ArgTypes ‘]’ +Singletons ::= Singleton { ‘,’ Singleton } +ArgTypes ::= Types FunArgType ::= Type | ‘=>’ Type PrefixOp(=>, t) ParamType ::= [‘=>’] ParamValueType @@ -209,7 +216,7 @@ InfixExpr ::= PrefixExpr | InfixExpr MatchClause MatchClause ::= ‘match’ ‘{’ CaseClauses ‘}’ Match(expr, cases) PrefixExpr ::= [‘-’ | ‘+’ | ‘~’ | ‘!’] SimpleExpr PrefixOp(expr, op) -SimpleExpr ::= Path +SimpleExpr ::= SimpleRef | Literal | ‘_’ | BlockExpr @@ -271,7 +278,7 @@ SimplePattern ::= PatVar | Quoted | XmlPattern | SimplePattern1 [TypeArgs] [ArgumentPatterns] -SimplePattern1 ::= Path +SimplePattern1 ::= SimpleRef | SimplePattern1 ‘.’ id PatVar ::= varid | ‘_’ @@ -335,7 +342,7 @@ AccessQualifier ::= ‘[’ id ‘]’ Annotation ::= ‘@’ SimpleType {ParArgumentExprs} Apply(tpe, args) Import ::= ‘import’ ImportExpr {‘,’ ImportExpr} -ImportExpr ::= StableId ‘.’ ImportSpec Import(expr, sels) +ImportExpr ::= SimpleRef {‘.’ id} ‘.’ ImportSpec Import(expr, sels) ImportSpec ::= id | ‘_’ | ‘{’ ImportSelectors) ‘}’ @@ -398,7 +405,7 @@ ExtParamClause ::= [DefTypeParamClause] ‘(’ DefParam ‘)’ Template ::= InheritClauses [TemplateBody] Template(constr, parents, self, stats) InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}] ConstrApps ::= ConstrApp {(‘,’ | ‘with’) ConstrApp} -ConstrApp ::= AnnotType {ParArgumentExprs} Apply(tp, args) +ConstrApp ::= SimpleType1 {Annotation} {ParArgumentExprs} Apply(tp, args) ConstrExpr ::= SelfInvocation | ‘{’ SelfInvocation {semi BlockStat} ‘}’ SelfInvocation ::= ‘this’ ArgumentExprs {ArgumentExprs} diff --git a/tests/neg/i4373.scala b/tests/neg/i4373.scala index 20a6c2595c9b..d3a7bce51436 100644 --- a/tests/neg/i4373.scala +++ b/tests/neg/i4373.scala @@ -9,13 +9,13 @@ class X5[A >: _ with X5[_]] // error class X6[A >: X6[_] with _] // error class A1 extends _ // error -class A2 extends _ with _ // error // error +class A2 extends _ with _ // error class A3 extends Base with _ // error class A4 extends _ with Base // error object Test { type T1 = _ // error - type T2 = _[Int] // error + type T2 = _[Int] // error // error type T3 = _ { type S } // error type T4 = [X] =>> _ // error diff --git a/tests/neg/i4373c.scala b/tests/neg/i4373c.scala index 44d744856f3f..c14d69da65d8 100644 --- a/tests/neg/i4373c.scala +++ b/tests/neg/i4373c.scala @@ -1,4 +1,4 @@ // ==> 18b253a4a89a84c5674165c6fc3efafad535eee3.scala <== object x0 { - def x1[x2 <:_[ // error // error + def x1[x2 <:_[ // error // error \ No newline at end of file From 6aa95a39f4dd01a6afa3df2b75d1c177e442aee4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 2 May 2020 14:01:10 +0200 Subject: [PATCH 4/9] Drop unused parsing methods --- .../dotty/tools/dotc/parsing/Parsers.scala | 38 +++++-------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index db617fff8292..62befc1e5a7a 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1040,37 +1040,17 @@ object Parsers { atSpan(accept(USCORE)) { Ident(nme.WILDCARD) } /** Accept identifier or match clause acting as a selector on given tree `t` */ - def selector(t: Tree): Tree = + def selectorOrMatch(t: Tree): Tree = atSpan(startOffset(t), in.offset) { if in.token == MATCH then matchClause(t) else Select(t, ident()) } - def idSelector(t: Tree): Tree = + def selector(t: Tree): Tree = atSpan(startOffset(t), in.offset) { Select(t, ident()) } - /** Selectors ::= id { `.' id } - * - * Accept `.' separated identifiers acting as a selectors on given tree `t`. - * @param finish An alternative parse in case the next token is not an identifier. - * If the alternative does not apply, its tree argument is returned unchanged. - */ - def selectors(t: Tree, finish: Tree => Tree): Tree = { - val t1 = finish(t) - if (t1 ne t) t1 else dotSelectors(selector(t), finish) - } - - /** DotSelectors ::= { `.' id } - * - * Accept `.' separated identifiers acting as a selectors on given tree `t`. - * @param finish An alternative parse in case the token following a `.' is not an identifier. - * If the alternative does not apply, its tree argument is returned unchanged. - */ - def dotSelectors(t: Tree, finish: Tree => Tree = id): Tree = - if (in.token == DOT) { in.nextToken(); selectors(t, finish) } - else t - - def dotSelections(t: Tree): Tree = - if (in.token == DOT) { in.nextToken(); dotSelections(t) } + /** DotSelectors ::= { `.' id } */ + def dotSelectors(t: Tree): Tree = + if (in.token == DOT) { in.nextToken(); dotSelectors(selector(t)) } else t private val id: Tree => Tree = x => x @@ -1091,7 +1071,7 @@ object Parsers { val mix = mixinQualifierOpt() val t = atSpan(start) { Super(This(qual), mix) } accept(DOT) - idSelector(t) + selector(t) if in.token == THIS then handleThis(EmptyTypeIdent) else if in.token == SUPER then handleSuper(EmptyTypeIdent) @@ -1623,7 +1603,7 @@ object Parsers { in.nextToken() atSpan(startOffset(t)) { SingletonTypeTree(t) } else - singletonCompletion(idSelector(t)) + singletonCompletion(selector(t)) else convertToTypeId(t) singletonCompletion(simpleRef()) } @@ -2282,7 +2262,7 @@ object Parsers { in.token match { case DOT => in.nextToken() - simpleExprRest(selector(t), canApply = true) + simpleExprRest(selectorOrMatch(t), canApply = true) case LBRACKET => val tapp = atSpan(startOffset(t), in.offset) { TypeApply(t, typeArgs(namedOK = true, wildOK = false)) } simpleExprRest(tapp, canApply = true) @@ -2698,7 +2678,7 @@ object Parsers { def simplePatternRest(t: Tree): Tree = if in.token == DOT then in.nextToken() - simplePatternRest(idSelector(t)) + simplePatternRest(selector(t)) else var p = t if (in.token == LBRACKET) From ddde226b0d12cb28816f881dd441ab477c2668d3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 2 May 2020 17:00:21 +0200 Subject: [PATCH 5/9] Parse type/singleton applications --- .../dotty/tools/dotc/parsing/Parsers.scala | 55 ++++++++++++++----- .../tools/dotc/printing/RefinedPrinter.scala | 7 ++- docs/docs/internals/syntax.md | 8 ++- 3 files changed, 51 insertions(+), 19 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 62befc1e5a7a..9dc44bcd702a 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -192,7 +192,9 @@ object Parsers { def isIdent = in.isIdent def isIdent(name: Name) = in.isIdent(name) - def isSimpleLiteral = simpleLiteralTokens contains in.token + def isSimpleLiteral = + simpleLiteralTokens.contains(in.token) + || isIdent(nme.raw.MINUS) && numericLitTokens.contains(in.lookahead.token) def isLiteral = literalTokens contains in.token def isNumericLit = numericLitTokens contains in.token def isTemplateIntro = templateIntroTokens contains in.token @@ -1100,18 +1102,42 @@ object Parsers { */ def qualId(): Tree = dotSelectors(termIdent()) - /** SimpleExpr ::= literal - * | 'id | 'this | 'true | 'false | 'null - * | null + /** Singleton ::= SimpleRef + * | SimpleLiteral + * | Singleton ‘.’ id + */ + def singleton(): Tree = + if isSimpleLiteral then simpleLiteral() + else dotSelectors(simpleRef()) + + /** SimpleLiteral ::= [‘-’] integerLiteral + * | [‘-’] floatingPointLiteral + * | booleanLiteral + * | characterLiteral + * | stringLiteral + */ + def simpleLiteral(): Tree = + if isIdent(nme.raw.MINUS) then + val start = in.offset + in.nextToken() + literal(negOffset = start, inTypeOrSingleton = true) + else + literal(inTypeOrSingleton = true) + + /** Literal ::= SimpleLiteral + * | processedStringLiteral + * | symbolLiteral + * | ‘null’ + * * @param negOffset The offset of a preceding `-' sign, if any. - * If the literal is not negated, negOffset = in.offset. + * If the literal is not negated, negOffset == in.offset. */ - def literal(negOffset: Int = in.offset, inPattern: Boolean = false, inType: Boolean = false, inStringInterpolation: Boolean = false): Tree = { + def literal(negOffset: Int = in.offset, inPattern: Boolean = false, inTypeOrSingleton: Boolean = false, inStringInterpolation: Boolean = false): Tree = { def literalOf(token: Token): Tree = { val isNegated = negOffset < in.offset def digits0 = in.removeNumberSeparators(in.strVal) def digits = if (isNegated) "-" + digits0 else digits0 - if (!inType) + if !inTypeOrSingleton then token match { case INTLIT => return Number(digits, NumberKind.Whole(in.base)) case DECILIT => return Number(digits, NumberKind.Decimal) @@ -1554,15 +1580,12 @@ object Parsers { /** SimpleType ::= SimpleLiteral * | ‘?’ SubtypeBounds - * | SimpleType1 + * | SimpleType1 { ‘(’ Singletons ‘)’ } + * Singletons ::= Singleton {‘,’ Singleton} */ def simpleType(): Tree = if isSimpleLiteral then - SingletonTypeTree(literal(inType = true)) - else if isIdent(nme.raw.MINUS) && numericLitTokens.contains(in.lookahead.token) then - val start = in.offset - in.nextToken() - SingletonTypeTree(literal(negOffset = start, inType = true)) + SingletonTypeTree(simpleLiteral()) else if in.token == USCORE then if sourceVersion.isAtLeast(`3.1`) then deprecationWarning(em"`_` is deprecated for wildcard arguments of types: use `?` instead") @@ -1575,7 +1598,11 @@ object Parsers { else if isIdent(nme.*) && ctx.settings.YkindProjector.value then typeIdent() else - simpleType1() + def singletonArgs(t: Tree): Tree = + if in.token == LPAREN + then singletonArgs(AppliedTypeTree(t, inParens(commaSeparated(singleton)))) + else t + singletonArgs(simpleType1()) /** SimpleType1 ::= id * | Singleton `.' id diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 98d231de643c..2fe40e998916 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -477,8 +477,11 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { changePrec(OrTypePrec) { toText(args(0)) ~ " | " ~ atPrec(OrTypePrec + 1) { toText(args(1)) } } else if (tpt.symbol == defn.andType && args.length == 2) changePrec(AndTypePrec) { toText(args(0)) ~ " & " ~ atPrec(AndTypePrec + 1) { toText(args(1)) } } - else - toTextLocal(tpt) ~ "[" ~ Text(args map argText, ", ") ~ "]" + else args match + case arg :: _ if arg.isTerm => + toTextLocal(tpt) ~ "(" ~ Text(args.map(argText), ", ") ~ ")" + case _ => + toTextLocal(tpt) ~ "[" ~ Text(args.map(argText), ", ") ~ "]" case LambdaTypeTree(tparams, body) => changePrec(GlobalPrec) { tparamsText(tparams) ~ " =>> " ~ toText(body) diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 46770818ad4d..b5290f9a5205 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -139,13 +139,15 @@ ClassQualifier ::= ‘[’ id ‘]’ ```ebnf Type ::= FunType | HkTypeParamClause ‘=>>’ Type TypeLambda(ps, t) + | ‘(’ TypedFunParams ‘)’ ‘=>>’ Type TypeLambda(ps, t) | MatchType | InfixType FunType ::= FunArgTypes (‘=>’ | ‘?=>’) Type Function(ts, t) | HKTypeParamClause '=>' Type PolyFunction(ps, t) FunArgTypes ::= InfixType | ‘(’ [ FunArgType {‘,’ FunArgType } ] ‘)’ - | ‘(’ TypedFunParam {‘,’ TypedFunParam } ‘)’ + | ‘(’ TypedFunParams ‘)’ +TypedFunParams ::= TypedFunParam {‘,’ TypedFunParam } TypedFunParam ::= id ‘:’ Type MatchType ::= InfixType `match` ‘{’ TypeCaseClauses ‘}’ InfixType ::= RefinedType {id [nl] RefinedType} InfixOp(t1, op, t2) @@ -155,8 +157,7 @@ AnnotType ::= SimpleType {Annotation} SimpleType ::= SimpleLiteral SingletonTypeTree(l) | ‘?’ SubtypeBounds - | SimpleType ‘(’ Singletons ‘)’ - | SimpleType1 + | SimpleType1 { ‘(’ Singletons ‘)’ } SimpleType1 ::= id Ident(name) | Singleton ‘.’ id Select(t, name) | Singleton ‘.’ ‘type’ SingletonTypeTree(p) @@ -166,6 +167,7 @@ SimpleType1 ::= id | SimpleType1 TypeArgs AppliedTypeTree(t, args) | SimpleType1 ‘#’ id Select(t, name) Singleton ::= SimpleRef + | SimpleLiteral | Singleton ‘.’ id -- not yet | Singleton ‘(’ Singletons ‘)’ -- not yet | Singleton ‘[’ ArgTypes ‘]’ From 54bc59ab5d00ecc088930a4a2397a1ad5918950e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 3 May 2020 10:24:01 +0200 Subject: [PATCH 6/9] Introduce language import for experimental.dependent --- compiler/src/dotty/tools/dotc/config/Feature.scala | 9 ++++++--- compiler/src/dotty/tools/dotc/core/Definitions.scala | 1 + compiler/src/dotty/tools/dotc/core/StdNames.scala | 1 + compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 3 ++- library/src/scalaShadowing/language.scala | 6 ++++++ 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/Feature.scala b/compiler/src/dotty/tools/dotc/config/Feature.scala index 3b9d03773f6a..933fd0f372c4 100644 --- a/compiler/src/dotty/tools/dotc/config/Feature.scala +++ b/compiler/src/dotty/tools/dotc/config/Feature.scala @@ -3,7 +3,7 @@ package dotc package config import core._ -import Contexts._, Symbols._, Names._ +import Contexts._, Symbols._, Names._, NameOps._ import StdNames.nme import Decorators.{given _} import util.SourcePosition @@ -22,7 +22,7 @@ object Feature: def enabledBySetting(feature: TermName, owner: Symbol = NoSymbol)(using Context): Boolean = def toPrefix(sym: Symbol): String = if !sym.exists || sym == defn.LanguageModule.moduleClass then "" - else toPrefix(sym.owner) + sym.name + "." + else toPrefix(sym.owner) + sym.name.stripModuleClassSuffix + "." val prefix = if owner.exists then toPrefix(owner) else "" ctx.base.settings.language.value.contains(prefix + feature) @@ -38,7 +38,7 @@ object Feature: def enabledByImport(feature: TermName, owner: Symbol = NoSymbol)(using Context): Boolean = ctx.atPhase(ctx.typerPhase) { ctx.importInfo != null - && ctx.importInfo.featureImported(feature.toTermName, + && ctx.importInfo.featureImported(feature, if owner.exists then owner else defn.LanguageModule.moduleClass) } @@ -57,6 +57,9 @@ object Feature: def dynamicsEnabled(using Context): Boolean = enabled(nme.dynamics) + def dependentEnabled(using Context) = + enabled(nme.dependent, defn.LanguageExperimentalModule.moduleClass) + def sourceVersionSetting(using Context): SourceVersion = SourceVersion.valueOf(ctx.settings.source.value) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 65a53c19dece..fa5354d4f0aa 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -664,6 +664,7 @@ class Definitions { @tu lazy val Mirror_SingletonProxyClass: ClassSymbol = ctx.requiredClass("scala.deriving.Mirror.SingletonProxy") @tu lazy val LanguageModule: Symbol = ctx.requiredModule("scala.language") + @tu lazy val LanguageExperimentalModule: Symbol = ctx.requiredModule("scala.language.experimental") @tu lazy val NonLocalReturnControlClass: ClassSymbol = ctx.requiredClass("scala.runtime.NonLocalReturnControl") @tu lazy val SelectableClass: ClassSymbol = ctx.requiredClass("scala.Selectable") diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 45e23e9d4512..799565dcbdcd 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -441,6 +441,7 @@ object StdNames { val definitions: N = "definitions" val delayedInit: N = "delayedInit" val delayedInitArg: N = "delayedInit$body" + val dependent: N = "dependent" val derived: N = "derived" val derives: N = "derives" val drop: N = "drop" diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 9dc44bcd702a..92b22d292ac7 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1580,7 +1580,8 @@ object Parsers { /** SimpleType ::= SimpleLiteral * | ‘?’ SubtypeBounds - * | SimpleType1 { ‘(’ Singletons ‘)’ } + * | SimpleType1 + * | SimpeType ‘(’ Singletons ‘)’ -- under language.experimental.dependent, checked in Typer * Singletons ::= Singleton {‘,’ Singleton} */ def simpleType(): Tree = diff --git a/library/src/scalaShadowing/language.scala b/library/src/scalaShadowing/language.scala index 0dc00b4029d0..3c2ab45c2b0d 100644 --- a/library/src/scalaShadowing/language.scala +++ b/library/src/scalaShadowing/language.scala @@ -210,6 +210,9 @@ object language { * to debug and understand. */ implicit lazy val macros: macros = languageFeature.experimental.macros + + /** Experimental support for richer dependent types */ + object dependent } /** Where imported, a backwards compatibility mode for Scala2 is enabled */ @@ -239,6 +242,9 @@ object language { */ object adhocExtensions + /** Experimental support for richer dependent types */ + object dependent + /** Source version */ object `3.0-migration` object `3.0` From 34b312b28deaac052cd021355748226361cd27d3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 3 May 2020 10:38:24 +0200 Subject: [PATCH 7/9] Fix parsing of annotations --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 4 ++-- docs/docs/internals/syntax.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 92b22d292ac7..6422797f1463 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2846,11 +2846,11 @@ object Parsers { else tree1 } - /** Annotation ::= `@' SimpleType {ParArgumentExprs} + /** Annotation ::= `@' SimpleType1 {ParArgumentExprs} */ def annot(): Tree = adjustStart(accept(AT)) { - ensureApplied(parArgumentExprss(wrapNew(simpleType()))) + ensureApplied(parArgumentExprss(wrapNew(simpleType1()))) } def annotations(skipNewLines: Boolean = false): List[Tree] = { diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index b5290f9a5205..1b055570bb3b 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -341,7 +341,7 @@ LocalModifier ::= ‘abstract’ AccessModifier ::= (‘private’ | ‘protected’) [AccessQualifier] AccessQualifier ::= ‘[’ id ‘]’ -Annotation ::= ‘@’ SimpleType {ParArgumentExprs} Apply(tpe, args) +Annotation ::= ‘@’ SimpleType1 {ParArgumentExprs} Apply(tpe, args) Import ::= ‘import’ ImportExpr {‘,’ ImportExpr} ImportExpr ::= SimpleRef {‘.’ id} ‘.’ ImportSpec Import(expr, sels) From 900ffc490c7c310af7c14b5659687bd9e3b1dcf9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 3 May 2020 10:40:02 +0200 Subject: [PATCH 8/9] Parsing of term lambdas --- compiler/src/dotty/tools/dotc/ast/Trees.scala | 18 ++++++++++++++++++ compiler/src/dotty/tools/dotc/ast/untpd.scala | 1 + .../src/dotty/tools/dotc/parsing/Parsers.scala | 11 +++++++++++ .../tools/dotc/printing/RefinedPrinter.scala | 4 ++++ .../tools/dotc/typer/ErrorReporting.scala | 4 ++++ .../src/dotty/tools/dotc/typer/Typer.scala | 15 +++++++++++++++ tests/neg/deptypes-badsyntax.scala | 2 ++ tests/neg/deptypes.scala | 5 +++++ 8 files changed, 60 insertions(+) create mode 100644 tests/neg/deptypes-badsyntax.scala create mode 100644 tests/neg/deptypes.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 59a439e4bb21..2835d3c06ddf 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -669,6 +669,11 @@ object Trees { type ThisTree[-T >: Untyped] = LambdaTypeTree[T] } + case class TermLambdaTypeTree[-T >: Untyped] private[ast] (params: List[ValDef[T]], body: Tree[T])(implicit @constructorOnly src: SourceFile) + extends TypTree[T] { + type ThisTree[-T >: Untyped] = TermLambdaTypeTree[T] + } + /** [bound] selector match { cases } */ case class MatchTypeTree[-T >: Untyped] private[ast] (bound: Tree[T], selector: Tree[T], cases: List[CaseDef[T]])(implicit @constructorOnly src: SourceFile) extends TypTree[T] { @@ -964,6 +969,7 @@ object Trees { type RefinedTypeTree = Trees.RefinedTypeTree[T] type AppliedTypeTree = Trees.AppliedTypeTree[T] type LambdaTypeTree = Trees.LambdaTypeTree[T] + type TermLambdaTypeTree = Trees.TermLambdaTypeTree[T] type MatchTypeTree = Trees.MatchTypeTree[T] type ByNameTypeTree = Trees.ByNameTypeTree[T] type TypeBoundsTree = Trees.TypeBoundsTree[T] @@ -1135,6 +1141,10 @@ object Trees { case tree: LambdaTypeTree if (tparams eq tree.tparams) && (body eq tree.body) => tree case _ => finalize(tree, untpd.LambdaTypeTree(tparams, body)(sourceFile(tree))) } + def TermLambdaTypeTree(tree: Tree)(params: List[ValDef], body: Tree)(implicit ctx: Context): TermLambdaTypeTree = tree match { + case tree: TermLambdaTypeTree if (params eq tree.params) && (body eq tree.body) => tree + case _ => finalize(tree, untpd.TermLambdaTypeTree(params, body)(sourceFile(tree))) + } def MatchTypeTree(tree: Tree)(bound: Tree, selector: Tree, cases: List[CaseDef])(implicit ctx: Context): MatchTypeTree = tree match { case tree: MatchTypeTree if (bound eq tree.bound) && (selector eq tree.selector) && (cases eq tree.cases) => tree case _ => finalize(tree, untpd.MatchTypeTree(bound, selector, cases)(sourceFile(tree))) @@ -1293,6 +1303,10 @@ object Trees { inContext(localCtx) { cpy.LambdaTypeTree(tree)(transformSub(tparams), transform(body)) } + case TermLambdaTypeTree(params, body) => + inContext(localCtx) { + cpy.TermLambdaTypeTree(tree)(transformSub(params), transform(body)) + } case MatchTypeTree(bound, selector, cases) => cpy.MatchTypeTree(tree)(transform(bound), transform(selector), transformSub(cases)) case ByNameTypeTree(result) => @@ -1422,6 +1436,10 @@ object Trees { inContext(localCtx) { this(this(x, tparams), body) } + case TermLambdaTypeTree(params, body) => + inContext(localCtx) { + this(this(x, params), body) + } case MatchTypeTree(bound, selector, cases) => this(this(this(x, bound), selector), cases) case ByNameTypeTree(result) => diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index bb8da0d59d72..385065cc9ffa 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -377,6 +377,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def RefinedTypeTree(tpt: Tree, refinements: List[Tree])(implicit src: SourceFile): RefinedTypeTree = new RefinedTypeTree(tpt, refinements) def AppliedTypeTree(tpt: Tree, args: List[Tree])(implicit src: SourceFile): AppliedTypeTree = new AppliedTypeTree(tpt, args) def LambdaTypeTree(tparams: List[TypeDef], body: Tree)(implicit src: SourceFile): LambdaTypeTree = new LambdaTypeTree(tparams, body) + def TermLambdaTypeTree(params: List[ValDef], body: Tree)(implicit src: SourceFile): TermLambdaTypeTree = new TermLambdaTypeTree(params, body) def MatchTypeTree(bound: Tree, selector: Tree, cases: List[CaseDef])(implicit src: SourceFile): MatchTypeTree = new MatchTypeTree(bound, selector, cases) def ByNameTypeTree(result: Tree)(implicit src: SourceFile): ByNameTypeTree = new ByNameTypeTree(result) def TypeBoundsTree(lo: Tree, hi: Tree, alias: Tree = EmptyTree)(implicit src: SourceFile): TypeBoundsTree = new TypeBoundsTree(lo, hi, alias) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 6422797f1463..604bac80c65a 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1360,6 +1360,7 @@ object Parsers { /** Type ::= FunType * | HkTypeParamClause ‘=>>’ Type + * | ‘(’ TypedFunParams ‘)’ ‘=>>’ Type * | MatchType * | InfixType * FunType ::= (MonoFunType | PolyFunType) @@ -1374,6 +1375,16 @@ object Parsers { var imods = Modifiers() def functionRest(params: List[Tree]): Tree = atSpan(start, in.offset) { + if in.token == TLARROW then + if !imods.flags.isEmpty || params.isEmpty then + syntaxError(em"illegal parameter list for type lambda", start) + in.token = ARROW + else + for case ValDef(_, tpt: ByNameTypeTree, _) <- params do + syntaxError(em"parameter of type lambda may not be call-by-name", tpt.span) + in.nextToken() + return TermLambdaTypeTree(params.asInstanceOf[List[ValDef]], typ()) + if in.token == CTXARROW then in.nextToken() imods |= Given diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 2fe40e998916..fbf7f24b3a0e 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -486,6 +486,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { changePrec(GlobalPrec) { tparamsText(tparams) ~ " =>> " ~ toText(body) } + case TermLambdaTypeTree(params, body) => + changePrec(GlobalPrec) { + paramsText(params) ~ " =>> " ~ toText(body) + } case MatchTypeTree(bound, sel, cases) => changePrec(GlobalPrec) { toText(sel) ~ keywordStr(" match ") ~ blockText(cases) ~ diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index d075e49247f8..55f5cb18baff 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -146,5 +146,9 @@ object ErrorReporting { else "" } + def dependentStr = + """Term-dependent types are experimental, + |they must be enabled with a `experimental.dependent` language import or setting""".stripMargin + def err(using Context): Errors = new Errors } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index fdd19deb1e1e..4892aba6034a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1578,6 +1578,14 @@ class Typer extends Namer } def typedAppliedTypeTree(tree: untpd.AppliedTypeTree)(using Context): Tree = { + tree.args match + case arg :: _ if arg.isTerm => + if dependentEnabled then + return errorTree(tree, i"Not yet implemented: T(...)") + else + return errorTree(tree, dependentStr) + case _ => + val tpt1 = typed(tree.tpt, AnyTypeConstructorProto)(using ctx.retractMode(Mode.Pattern)) val tparams = tpt1.tpe.typeParams if (tparams.isEmpty) { @@ -1654,6 +1662,12 @@ class Typer extends Namer assignType(cpy.LambdaTypeTree(tree)(tparams1, body1), tparams1, body1) } + def typedTermLambdaTypeTree(tree: untpd.TermLambdaTypeTree)(using Context): Tree = + if dependentEnabled then + errorTree(tree, i"Not yet implemented: (...) =>> ...") + else + errorTree(tree, dependentStr) + def typedMatchTypeTree(tree: untpd.MatchTypeTree, pt: Type)(using Context): Tree = { val bound1 = if (tree.bound.isEmpty && isFullyDefined(pt, ForceDegree.none)) TypeTree(pt) @@ -2369,6 +2383,7 @@ class Typer extends Namer case tree: untpd.RefinedTypeTree => typedRefinedTypeTree(tree) case tree: untpd.AppliedTypeTree => typedAppliedTypeTree(tree) case tree: untpd.LambdaTypeTree => typedLambdaTypeTree(tree)(using ctx.localContext(tree, NoSymbol).setNewScope) + case tree: untpd.TermLambdaTypeTree => typedTermLambdaTypeTree(tree)(using ctx.localContext(tree, NoSymbol).setNewScope) case tree: untpd.MatchTypeTree => typedMatchTypeTree(tree, pt) case tree: untpd.ByNameTypeTree => typedByNameTypeTree(tree) case tree: untpd.TypeBoundsTree => typedTypeBoundsTree(tree, pt) diff --git a/tests/neg/deptypes-badsyntax.scala b/tests/neg/deptypes-badsyntax.scala new file mode 100644 index 000000000000..88c49d6203cc --- /dev/null +++ b/tests/neg/deptypes-badsyntax.scala @@ -0,0 +1,2 @@ + +type Bad1[T] = () =>> Array[T] // error diff --git a/tests/neg/deptypes.scala b/tests/neg/deptypes.scala new file mode 100644 index 000000000000..b4345bcdff0b --- /dev/null +++ b/tests/neg/deptypes.scala @@ -0,0 +1,5 @@ + +type Vec[T] = (n: Int) =>> Array[T] // error: needs to be enabled +val x: Vec[Int](10) = ??? // error: needs to be enabled +val n = 10 +type T = Vec[String](n) // error: needs to be enabled \ No newline at end of file From 8385c17e89dd2500e6d055201d75d5d0a96278f8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 3 May 2020 11:42:04 +0200 Subject: [PATCH 9/9] Parsing of singleton parameters in type definitions --- compiler/src/dotty/tools/dotc/ast/untpd.scala | 10 ++++- .../dotty/tools/dotc/parsing/Parsers.scala | 44 ++++++++++++------- docs/docs/internals/syntax.md | 11 ++--- tests/neg/deptypes.scala | 5 +++ 4 files changed, 46 insertions(+), 24 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 385065cc9ffa..45a67971b36a 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -486,8 +486,14 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { ValDef(nme.syntheticParamName(n), if (tpt == null) TypeTree() else tpt, EmptyTree) .withFlags(flags) - def lambdaAbstract(tparams: List[TypeDef], tpt: Tree)(implicit ctx: Context): Tree = - if (tparams.isEmpty) tpt else LambdaTypeTree(tparams, tpt) + def lambdaAbstract(params: List[ValDef] | List[TypeDef], tpt: Tree)(using Context): Tree = + params match + case Nil => tpt + case (vd: ValDef) :: _ => TermLambdaTypeTree(params.asInstanceOf[List[ValDef]], tpt) + case _ => LambdaTypeTree(params.asInstanceOf[List[TypeDef]], tpt) + + def lambdaAbstractAll(paramss: List[List[ValDef] | List[TypeDef]], tpt: Tree)(using Context): Tree = + paramss.foldRight(tpt)(lambdaAbstract) /** A reference to given definition. If definition is a repeated * parameter, the reference will be a repeated argument. diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 604bac80c65a..a41912e212a2 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1358,17 +1358,17 @@ object Parsers { case _ => false } - /** Type ::= FunType - * | HkTypeParamClause ‘=>>’ Type - * | ‘(’ TypedFunParams ‘)’ ‘=>>’ Type - * | MatchType - * | InfixType - * FunType ::= (MonoFunType | PolyFunType) - * MonoFunType ::= FunArgTypes (‘=>’ | ‘?=>’) Type - * PolyFunType ::= HKTypeParamClause '=>' Type - * FunArgTypes ::= InfixType - * | `(' [ [ ‘[using]’ ‘['erased'] FunArgType {`,' FunArgType } ] `)' - * | '(' [ ‘[using]’ ‘['erased'] TypedFunParam {',' TypedFunParam } ')' + /** Type ::= FunType + * | HkTypeParamClause ‘=>>’ Type + * | FunParamClause ‘=>>’ Type + * | MatchType + * | InfixType + * FunType ::= (MonoFunType | PolyFunType) + * MonoFunType ::= FunArgTypes (‘=>’ | ‘?=>’) Type + * PolyFunType ::= HKTypeParamClause '=>' Type + * FunArgTypes ::= InfixType + * | `(' [ [ ‘[using]’ ‘['erased'] FunArgType {`,' FunArgType } ] `)' + * | '(' [ ‘[using]’ ‘['erased'] TypedFunParam {',' TypedFunParam } ')' */ def typ(): Tree = { val start = in.offset @@ -1511,10 +1511,19 @@ object Parsers { Span(start, start + nme.IMPLICITkw.asSimpleName.length) /** TypedFunParam ::= id ':' Type */ - def typedFunParam(start: Offset, name: TermName, mods: Modifiers = EmptyModifiers): Tree = atSpan(start) { - accept(COLON) - makeParameter(name, typ(), mods | Param) - } + def typedFunParam(start: Offset, name: TermName, mods: Modifiers = EmptyModifiers): ValDef = + atSpan(start) { + accept(COLON) + makeParameter(name, typ(), mods | Param) + } + + /** FunParamClause ::= ‘(’ TypedFunParam {‘,’ TypedFunParam } ‘)’ + */ + def funParamClause(): List[ValDef] = + inParens(commaSeparated(() => typedFunParam(in.offset, ident()))) + + def funParamClauses(): List[List[ValDef]] = + if in.token == LPAREN then funParamClause() :: funParamClauses() else Nil /** InfixType ::= RefinedType {id [nl] RefinedType} */ @@ -3394,15 +3403,16 @@ object Parsers { argumentExprss(mkApply(Ident(nme.CONSTRUCTOR), argumentExprs())) } - /** TypeDcl ::= id [TypeParamClause] TypeBounds [‘=’ Type] + /** TypeDcl ::= id [TypeParamClause] {FunParamClause} TypeBounds [‘=’ Type] */ def typeDefOrDcl(start: Offset, mods: Modifiers): Tree = { newLinesOpt() atSpan(start, nameStart) { val nameIdent = typeIdent() val tparams = typeParamClauseOpt(ParamOwner.Type) + val vparamss = funParamClauses() def makeTypeDef(rhs: Tree): Tree = { - val rhs1 = lambdaAbstract(tparams, rhs) + val rhs1 = lambdaAbstractAll(tparams :: vparamss, rhs) val tdef = TypeDef(nameIdent.name.toTypeName, rhs1) if (nameIdent.isBackquoted) tdef.pushAttachment(Backquoted, ()) diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 1b055570bb3b..acf02c91f040 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -138,16 +138,16 @@ ClassQualifier ::= ‘[’ id ‘]’ ### Types ```ebnf Type ::= FunType - | HkTypeParamClause ‘=>>’ Type TypeLambda(ps, t) - | ‘(’ TypedFunParams ‘)’ ‘=>>’ Type TypeLambda(ps, t) + | HkTypeParamClause ‘=>>’ Type LambdaTypeTree(ps, t) + | FunParamClause ‘=>>’ Type TermLambdaTypeTree(ps, t) | MatchType | InfixType FunType ::= FunArgTypes (‘=>’ | ‘?=>’) Type Function(ts, t) | HKTypeParamClause '=>' Type PolyFunction(ps, t) FunArgTypes ::= InfixType | ‘(’ [ FunArgType {‘,’ FunArgType } ] ‘)’ - | ‘(’ TypedFunParams ‘)’ -TypedFunParams ::= TypedFunParam {‘,’ TypedFunParam } + | FunParamClause +FunParamClause ::= ‘(’ TypedFunParam {‘,’ TypedFunParam } ‘)’ TypedFunParam ::= id ‘:’ Type MatchType ::= InfixType `match` ‘{’ TypeCaseClauses ‘}’ InfixType ::= RefinedType {id [nl] RefinedType} InfixOp(t1, op, t2) @@ -372,7 +372,8 @@ VarDcl ::= ids ‘:’ Type DefDcl ::= DefSig ‘:’ Type DefDef(_, name, tparams, vparamss, tpe, EmptyTree) DefSig ::= id [DefTypeParamClause] DefParamClauses | ExtParamClause {nl} [‘.’] id DefParamClauses -TypeDcl ::= id [TypeParamClause] SubtypeBounds [‘=’ Type] TypeDefTree(_, name, tparams, bound +TypeDcl ::= id [TypeParamClause] {FunParamClause} SubtypeBounds TypeDefTree(_, name, tparams, bound + [‘=’ Type] Def ::= ‘val’ PatDef | ‘var’ VarDef diff --git a/tests/neg/deptypes.scala b/tests/neg/deptypes.scala index b4345bcdff0b..ee6d1c95c0f2 100644 --- a/tests/neg/deptypes.scala +++ b/tests/neg/deptypes.scala @@ -1,5 +1,10 @@ type Vec[T] = (n: Int) =>> Array[T] // error: needs to be enabled + +type Matrix[T](m: Int, n: Int) = Vec[Vec[T](n)](m) // error: needs to be enabled + +type Tensor2[T](m: Int)(n: Int) = Matrix[T](m, n) // error: needs to be enabled + val x: Vec[Int](10) = ??? // error: needs to be enabled val n = 10 type T = Vec[String](n) // error: needs to be enabled \ No newline at end of file