Skip to content

Backport "Update syntax.md" to LTS #20917

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 2, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 89 additions & 91 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
@@ -1521,13 +1521,15 @@ object Parsers {
* PolyFunType ::= HKTypeParamClause '=>' Type
* | HKTypeParamClause ‘->’ [CaptureSet] Type -- under pureFunctions
* FunTypeArgs ::= InfixType
* | `(' [ [ ‘['erased'] FunArgType {`,' FunArgType } ] `)'
* | '(' [ ‘['erased'] TypedFunParam {',' TypedFunParam } ')'
* | `(' [ FunArgType {`,' FunArgType } ] `)'
* | '(' [ TypedFunParam {',' TypedFunParam } ')'
* MatchType ::= InfixType `match` <<< TypeCaseClauses >>>
*/
def typ(): Tree =
val start = in.offset
var imods = Modifiers()
var erasedArgs: ListBuffer[Boolean] = ListBuffer()
val erasedArgs: ListBuffer[Boolean] = ListBuffer()

def functionRest(params: List[Tree]): Tree =
val paramSpan = Span(start, in.lastOffset)
atSpan(start, in.offset) {
@@ -1556,7 +1558,8 @@ object Parsers {
else
accept(ARROW)

val resultType = if isPure then capturesAndResult(typ) else typ()
val resultType =
if isPure then capturesAndResult(typ) else typ()
if token == TLARROW then
for case ValDef(_, tpt, _) <- params do
if isByNameType(tpt) then
@@ -1573,99 +1576,94 @@ object Parsers {
Function(params, resultType)
}

var isValParamList = false
def typeRest(t: Tree) = in.token match
case ARROW | CTXARROW =>
erasedArgs.addOne(false)
functionRest(t :: Nil)
case MATCH =>
matchType(t)
case FORSOME =>
syntaxError(ExistentialTypesNoLongerSupported())
t
case _ if isPureArrow =>
erasedArgs.addOne(false)
functionRest(t :: Nil)
case _ =>
if erasedArgs.contains(true) && !t.isInstanceOf[FunctionWithMods] then
syntaxError(ErasedTypesCanOnlyBeFunctionTypes(), implicitKwPos(start))
t

val t =
if (in.token == LPAREN) {
var isValParamList = false
if in.token == LPAREN then
in.nextToken()
if in.token == RPAREN then
in.nextToken()
if (in.token == RPAREN) {
in.nextToken()
functionRest(Nil)
}
else {
val paramStart = in.offset
def addErased() =
erasedArgs.addOne(isErasedKw)
if isErasedKw then { in.skipToken(); }
addErased()
val ts = in.currentRegion.withCommasExpected {
functionRest(Nil)
else
val paramStart = in.offset
def addErased() =
erasedArgs.addOne(isErasedKw)
if isErasedKw then in.skipToken()
addErased()
val args =
in.currentRegion.withCommasExpected:
funArgType() match
case Ident(name) if name != tpnme.WILDCARD && in.isColon =>
isValParamList = true
def funParam(start: Offset, mods: Modifiers) = {
atSpan(start) {
def funParam(start: Offset, mods: Modifiers) =
atSpan(start):
addErased()
typedFunParam(in.offset, ident(), imods)
}
}
commaSeparatedRest(
typedFunParam(paramStart, name.toTermName, imods),
() => funParam(in.offset, imods))
case t =>
def funParam() = {
addErased()
funArgType()
}
commaSeparatedRest(t, funParam)
}
accept(RPAREN)
if isValParamList || in.isArrow || isPureArrow then
functionRest(ts)
else {
val ts1 = ts.mapConserve { t =>
if isByNameType(t) then
syntaxError(ByNameParameterNotSupported(t), t.span)
stripByNameType(t)
else
t
}
val tuple = atSpan(start) { makeTupleOrParens(ts1) }
infixTypeRest(
refinedTypeRest(
withTypeRest(
annotTypeRest(
simpleTypeRest(tuple)))))
}
}
}
else if (in.token == LBRACKET) {
val start = in.offset
val tparams = typeParamClause(ParamOwner.TypeParam)
if (in.token == TLARROW)
atSpan(start, in.skipToken())(LambdaTypeTree(tparams, toplevelTyp()))
else if (in.token == ARROW || isPureArrow(nme.PUREARROW)) {
val arrowOffset = in.skipToken()
val body = toplevelTyp()
atSpan(start, arrowOffset) {
getFunction(body) match {
case Some(f) =>
checkFunctionNotErased(f, "poly function")
PolyFunction(tparams, body)
case None =>
syntaxError(em"Implementation restriction: polymorphic function types must have a value parameter", arrowOffset)
Ident(nme.ERROR.toTypeName)
}
}
}
else { accept(TLARROW); typ() }
}
else if (in.token == INDENT) enclosed(INDENT, typ())
else infixType()

in.token match
case ARROW | CTXARROW =>
erasedArgs.addOne(false)
functionRest(t :: Nil)
case MATCH => matchType(t)
case FORSOME => syntaxError(ExistentialTypesNoLongerSupported()); t
case _ =>
if isPureArrow then
erasedArgs.addOne(false)
functionRest(t :: Nil)
def funArg() =
erasedArgs.addOne(false)
funArgType()
commaSeparatedRest(t, funArg)
accept(RPAREN)
if isValParamList || in.isArrow || isPureArrow then
functionRest(args)
else
if (erasedArgs.contains(true) && !t.isInstanceOf[FunctionWithMods])
syntaxError(ErasedTypesCanOnlyBeFunctionTypes(), implicitKwPos(start))
t
val args1 = args.mapConserve: t =>
if isByNameType(t) then
syntaxError(ByNameParameterNotSupported(t), t.span)
stripByNameType(t)
else
t
val tuple = atSpan(start):
makeTupleOrParens(args1)
typeRest:
infixTypeRest:
refinedTypeRest:
withTypeRest:
annotTypeRest:
simpleTypeRest(tuple)
else if in.token == LBRACKET then
val start = in.offset
val tparams = typeParamClause(ParamOwner.TypeParam)
if in.token == TLARROW then
atSpan(start, in.skipToken()):
LambdaTypeTree(tparams, toplevelTyp())
else if in.token == ARROW || isPureArrow(nme.PUREARROW) then
val arrowOffset = in.skipToken()
val body = toplevelTyp()
atSpan(start, arrowOffset):
getFunction(body) match
case Some(f) =>
checkFunctionNotErased(f, "poly function")
PolyFunction(tparams, body)
case None =>
syntaxError(em"Implementation restriction: polymorphic function types must have a value parameter", arrowOffset)
Ident(nme.ERROR.toTypeName)
else
accept(TLARROW)
typ()
else if in.token == INDENT then
enclosed(INDENT, typ())
else
typeRest(infixType())
end typ

private def makeKindProjectorTypeDef(name: TypeName): TypeDef = {
@@ -1702,7 +1700,7 @@ object Parsers {
private def implicitKwPos(start: Int): Span =
Span(start, start + nme.IMPLICITkw.asSimpleName.length)

/** TypedFunParam ::= id ':' Type */
/** TypedFunParam ::= [`erased`] id ':' Type */
def typedFunParam(start: Offset, name: TermName, mods: Modifiers = EmptyModifiers): ValDef =
atSpan(start) {
acceptColon()
@@ -2016,7 +2014,7 @@ object Parsers {
*/
def paramType(): Tree = paramTypeOf(paramValueType)

/** ParamValueType ::= [`into`] Type [`*']
/** ParamValueType ::= Type [`*']
*/
def paramValueType(): Tree = {
val t = maybeInto(toplevelTyp)
@@ -2374,7 +2372,7 @@ object Parsers {
Match(t, inBracesOrIndented(caseClauses(() => caseClause())))
}

/** `match' `{' TypeCaseClauses `}'
/** `match' <<< TypeCaseClauses >>>
*/
def matchType(t: Tree): MatchTypeTree =
atSpan(startOffset(t), accept(MATCH)) {
@@ -2384,7 +2382,7 @@ object Parsers {
/** FunParams ::= Bindings
* | id
* | `_'
* Bindings ::= `(' [[‘erased’] Binding {`,' Binding}] `)'
* Bindings ::= `(' [Binding {`,' Binding}] `)'
*/
def funParams(mods: Modifiers, location: Location): List[Tree] =
if in.token == LPAREN then
@@ -3126,7 +3124,7 @@ object Parsers {
* | AccessModifier
* | override
* | opaque
* LocalModifier ::= abstract | final | sealed | open | implicit | lazy | erased | inline | transparent
* LocalModifier ::= abstract | final | sealed | open | implicit | lazy | inline | transparent | infix | erased
*/
def modifiers(allowed: BitSet = modifierTokens, start: Modifiers = Modifiers()): Modifiers = {
@tailrec
@@ -3283,7 +3281,7 @@ object Parsers {
/** ClsTermParamClause ::= ‘(’ ClsParams ‘)’ | UsingClsTermParamClause
* UsingClsTermParamClause::= ‘(’ ‘using’ [‘erased’] (ClsParams | ContextTypes) ‘)’
* ClsParams ::= ClsParam {‘,’ ClsParam}
* ClsParam ::= {Annotation}
* ClsParam ::= {Annotation} [{Modifier} (‘val’ | ‘var’)] Param
*
* TypelessClause ::= DefTermParamClause
* | UsingParamClause
3 changes: 1 addition & 2 deletions compiler/src/dotty/tools/dotc/transform/Recheck.scala
Original file line number Diff line number Diff line change
@@ -9,13 +9,12 @@ import ast.*
import Names.Name
import Phases.Phase
import DenotTransformers.{DenotTransformer, IdentityDenotTransformer, SymTransformer}
import NamerOps.{methodType, linkConstructorParams}
import NamerOps.linkConstructorParams
import NullOpsDecorator.stripNull
import typer.ErrorReporting.err
import typer.ProtoTypes.*
import typer.TypeAssigner.seqLitType
import typer.ConstFold
import NamerOps.methodType
import config.Printers.recheckr
import util.Property
import StdNames.nme
39 changes: 25 additions & 14 deletions docs/_docs/internals/syntax.md
Original file line number Diff line number Diff line change
@@ -20,6 +20,8 @@ productions map to AST nodes.
The following description of Scala tokens uses literal characters `‘c’` when
referring to the ASCII fragment `\u0000``\u007F`.

Informal descriptions are typeset as `“some comment”`.

## Lexical Syntax

The lexical syntax of Scala is given by the following grammar in EBNF form:
@@ -99,7 +101,10 @@ semi ::= ‘;’ | nl {nl}

## Optional Braces

The lexical analyzer also inserts `indent` and `outdent` tokens that represent regions of indented code [at certain points](../reference/other-new-features/indentation.md)
The principle of optional braces is that any keyword that can be followed by `{` can also be followed by an indented block, without needing an intervening `:`.
(Allowing an optional `:` would be counterproductive since it would introduce several ways to do the same thing.)

The lexical analyzer inserts `indent` and `outdent` tokens that represent regions of indented code [at certain points](../reference/other-new-features/indentation.md).

In the context-free productions below we use the notation `<<< ts >>>`
to indicate a token sequence `ts` that is either enclosed in a pair of braces `{ ts }` or that constitutes an indented region `indent ts outdent`. Analogously, the
@@ -201,14 +206,13 @@ SimpleType1 ::= id
Singleton ::= SimpleRef
| SimpleLiteral
| Singleton ‘.’ id
FunArgType ::= [`erased`] Type
| [`erased`] ‘=>’ Type PrefixOp(=>, t)
FunArgType ::= Type
| ‘=>’ Type PrefixOp(=>, t)
FunArgTypes ::= FunArgType { ‘,’ FunArgType }
ParamType ::= [‘=>’] ParamValueType
ParamValueType ::= [‘into’] ExactParamType Into(t)
ExactParamType ::= ParamValueType [‘*’] PostfixOp(t, "*")
ParamValueType ::= Type [‘*’] PostfixOp(t, "*")
TypeArgs ::= ‘[’ Types ‘]’ ts
Refinement ::= :<<< [RefineDef] {semi [RefineDef]} >>> ds
Refinement ::= :<<< [RefineDcl] {semi [RefineDcl]} >>> ds
TypeBounds ::= [‘>:’ Type] [‘<:’ Type] TypeBoundsTree(lo, hi)
TypeParamBounds ::= TypeBounds {‘:’ Type} ContextBounds(typeBounds, tps)
Types ::= Type {‘,’ Type}
@@ -223,7 +227,7 @@ BlockResult ::= FunParams (‘=>’ | ‘?=>’) Block
| HkTypeParamClause ‘=>’ Block
| Expr1
FunParams ::= Bindings
| [`erased`] id
| id
| ‘_’
Expr1 ::= [‘inline’] ‘if’ ‘(’ Expr ‘)’ {nl} Expr [[semi] ‘else’ Expr] If(Parens(cond), thenp, elsep?)
| [‘inline’] ‘if’ Expr ‘then’ Expr [[semi] ‘else’ Expr] If(cond, thenp, elsep?)
@@ -272,7 +276,7 @@ ColonArgument ::= colon [LambdaStart]
LambdaStart ::= FunParams (‘=>’ | ‘?=>’)
| HkTypeParamClause ‘=>’
Quoted ::= ‘'’ ‘{’ Block ‘}’
| ‘'’ ‘[’ Type ‘]’
| ‘'’ ‘[’ TypeBlock ‘]’
ExprSplice ::= spliceId -- if inside quoted block
| ‘$’ ‘{’ Block ‘}’ -- unless inside quoted pattern
| ‘$’ ‘{’ Pattern ‘}’ -- when inside quoted pattern
@@ -294,6 +298,8 @@ BlockStat ::= Import
| Extension
| Expr1
| EndMarker
TypeBlock ::= {TypeBlockStat semi} Type
TypeBlockStat ::= ‘type’ {nl} TypeDef
ForExpr ::= ‘for’ ‘(’ Enumerators0 ‘)’ {nl} [‘do‘ | ‘yield’] Expr ForYield(enums, expr) / ForDo(enums, expr)
| ‘for’ ‘{’ Enumerators0 ‘}’ {nl} [‘do‘ | ‘yield’] Expr
@@ -353,7 +359,7 @@ ClsParamClause ::= [nl] ‘(’ ClsParams ‘)’
| [nl] ‘(’ ‘using’ (ClsParams | FunArgTypes) ‘)’
ClsParams ::= ClsParam {‘,’ ClsParam}
ClsParam ::= {Annotation} ValDef(mods, id, tpe, expr) -- point of mods on val/var
[{Modifier} (‘val’ | ‘var’) | ‘inline’] Param
[{Modifier} (‘val’ | ‘var’)] Param
DefParamClauses ::= DefParamClause { DefParamClause } -- and two DefTypeParamClause cannot be adjacent
DefParamClause ::= DefTypeParamClause
@@ -376,8 +382,8 @@ Param ::= id ‘:’ ParamType [‘=’ Expr]

### Bindings and Imports
```ebnf
Bindings ::= ‘(’[`erased`] [Binding {‘,’ [`erased`] Binding}] ‘)’
Binding ::= (id | ‘_’) [‘:’ Type] ValDef(_, id, tpe, EmptyTree)
Bindings ::= ‘(’ [Binding {‘,’ Binding}] ‘)’
Binding ::= [`erased`] (id | ‘_’) [‘:’ Type] ValDef(_, id, tpe, EmptyTree)
Modifier ::= LocalModifier
| AccessModifier
@@ -390,6 +396,10 @@ LocalModifier ::= ‘abstract’
| ‘implicit’
| ‘lazy’
| ‘inline’
| ‘transparent’
| ‘infix’
| ‘erased’
AccessModifier ::= (‘private’ | ‘protected’) [AccessQualifier]
AccessQualifier ::= ‘[’ id ‘]’
@@ -414,9 +424,11 @@ EndMarkerTag ::= id | ‘if’ | ‘while’ | ‘for’ | ‘match’ |

### Definitions
```ebnf
RefineDef ::= ‘val’ ValDef
| ‘def’ DefDef
RefineDcl ::= ‘val’ ValDcl
| ‘def’ DefDcl
| ‘type’ {nl} TypeDef
ValDcl ::= ids ‘:’ Type
DefDcl ::= DefSig ‘:’ Type
Def ::= ‘val’ PatDef
| ‘var’ PatDef
@@ -461,7 +473,6 @@ TemplateBody ::= :<<< [SelfType] TemplateStat {semi TemplateStat} >>>
TemplateStat ::= Import
| Export
| {Annotation [nl]} {Modifier} Def
| {Annotation [nl]} {Modifier} Dcl
| Extension
| Expr1
| EndMarker
29 changes: 16 additions & 13 deletions docs/_docs/reference/syntax.md
Original file line number Diff line number Diff line change
@@ -348,8 +348,12 @@ ClsParamClauses ::= {ClsParamClause} [[nl] ‘(’ [‘implicit’] ClsParams
ClsParamClause ::= [nl] ‘(’ ClsParams ‘)’
| [nl] ‘(’ ‘using’ (ClsParams | FunArgTypes) ‘)’
ClsParams ::= ClsParam {‘,’ ClsParam}
ClsParam ::= {Annotation} [{Modifier} (‘val’ | ‘var’) | ‘inline’] Param
ClsParam ::= {Annotation} [{Modifier} (‘val’ | ‘var’)] Param
DefParamClauses ::= DefParamClause { DefParamClause } -- and two DefTypeParamClause cannot be adjacent
DefParamClause ::= DefTypeParamClause
| DefTermParamClause
| UsingParamClause
TypelessClauses ::= TypelessClause {TypelessClause}
TypelessClause ::= DefTermParamClause
| UsingParamClause
@@ -381,6 +385,8 @@ LocalModifier ::= ‘abstract’
| ‘implicit’
| ‘lazy’
| ‘inline’
| ‘transparent’
| ‘infix’
AccessModifier ::= (‘private’ | ‘protected’) [AccessQualifier]
AccessQualifier ::= ‘[’ id ‘]’
@@ -407,24 +413,22 @@ EndMarkerTag ::= id | ‘if’ | ‘while’ | ‘for’ | ‘match’ |
```
RefineDcl ::= ‘val’ ValDcl
| ‘def’ DefDcl
| ‘type’ {nl} TypeDcl
Dcl ::= RefineDcl
| ‘var’ VarDcl
| ‘type’ {nl} TypeDef
ValDcl ::= ids ‘:’ Type
VarDcl ::= ids ‘:’ Type
DefDcl ::= DefSig ‘:’ Type
DefSig ::= id [DefTypeParamClause] [TypelessClauses] [DefImplicitClause]
TypeDcl ::= id [TypeParamClause] {FunParamClause} TypeBounds
Def ::= ‘val’ PatDef
| ‘var’ PatDef
| ‘def’ DefDef
| ‘type’ {nl} TypeDcl
| ‘type’ {nl} TypeDef
| TmplDef
PatDef ::= ids [‘:’ Type] ‘=’ Expr
| Pattern2 [‘:’ Type] ‘=’ Expr
DefDef ::= DefSig [‘:’ Type] ‘=’ Expr
| ‘this’ TypelessClauses [DefImplicitClause] ‘=’ ConstrExpr
PatDef ::= ids [‘:’ Type] [‘=’ Expr]
| Pattern2 [‘:’ Type] [‘=’ Expr] PatDef(_, pats, tpe?, expr)
DefDef ::= DefSig [‘:’ Type] [‘=’ Expr] DefDef(_, name, paramss, tpe, expr)
| ‘this’ TypelessClauses [DefImplicitClause] ‘=’ ConstrExpr DefDef(_, <init>, vparamss, EmptyTree, expr | Block)
DefSig ::= id [DefParamClauses] [DefImplicitClause]
TypeDef ::= id [TypeParamClause] {FunParamClause} TypeBounds TypeDefTree(_, name, tparams, bound
[‘=’ Type]
TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef
| [‘case’] ‘object’ ObjectDef
@@ -456,7 +460,6 @@ TemplateBody ::= :<<< [SelfType] TemplateStat {semi TemplateStat} >>>
TemplateStat ::= Import
| Export
| {Annotation [nl]} {Modifier} Def
| {Annotation [nl]} {Modifier} Dcl
| Extension
| Expr1
| EndMarker
4 changes: 2 additions & 2 deletions tests/neg/lambda-infer.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//> using options -language:experimental.erasedDefinitions

type F = (Int, erased Int) => Int
type F = (x: Int, erased y: Int) => Int

erased class A

@@ -14,7 +14,7 @@ erased class A

use { (x, y) => x } // error: Expected F got (Int, Int) => Int

def singleParam(f: (erased Int) => Int) = f(5)
def singleParam(f: (erased x: Int) => Int) = f(5)

singleParam(x => 5) // error: Expected (erased Int) => Int got Int => Int
singleParam((erased x) => 5) // ok
8 changes: 4 additions & 4 deletions tests/run/erased-lambdas.scala
Original file line number Diff line number Diff line change
@@ -3,8 +3,8 @@

// lambdas should parse and work

type F = (erased Int, String) => String
type S = (Int, erased String) => Int
type F = (erased x: Int, y: String) => String
type S = (x: Int, erased y: String) => Int

def useF(f: F) = f(5, "a")
def useS(f: S) = f(5, "a")
@@ -16,7 +16,7 @@ val fsExpl = (x: Int, erased y: String) => x

// contextual lambdas should work

type FC = (Int, erased String) ?=> Int
type FC = (x: Int, erased y: String) ?=> Int

def useCtx(f: FC) = f(using 5, "a")

@@ -25,7 +25,7 @@ val fCvExpl = (x: Int, erased y: String) ?=> x

// nested lambdas should work

val nested: Int => (String, erased Int) => FC = a => (_, erased _) => (c, erased d) ?=> a + c
val nested: Int => (x: String, erased y: Int) => FC = a => (_, erased _) => (c, erased d) ?=> a + c

@main def Test() =
assert("a" == useF(ff))