diff --git a/community-build/community-projects/squants b/community-build/community-projects/squants index c30f46abc841..711503eeaba2 160000 --- a/community-build/community-projects/squants +++ b/community-build/community-projects/squants @@ -1 +1 @@ -Subproject commit c30f46abc841b9b26f96850a21cfc009545cd22b +Subproject commit 711503eeaba2d48b1bb20f286a0060b9bc4d02d1 diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 739694b709b8..30f49dddc327 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -49,9 +49,6 @@ object desugar { case None, Exhaustive, IrrefutablePatDef, IrrefutableGenFrom } - /** Info of a variable in a pattern: The named tree and its type */ - private type VarInfo = (NameTree, Tree) - /** Is `name` the name of a method that can be invalidated as a compiler-generated * case class method if it clashes with a user-defined method? */ @@ -1685,16 +1682,6 @@ object desugar { TypeDef(tpnme.REFINE_CLASS, impl).withFlags(Trait) } - /** If tree is of the form `id` or `id: T`, return its name and type, otherwise return None. - */ - private object IdPattern { - def unapply(tree: Tree)(implicit ctx: Context): Option[VarInfo] = tree match { - case id: Ident if id.name != nme.WILDCARD => Some(id, TypeTree()) - case Typed(id: Ident, tpt) => Some((id, tpt)) - case _ => None - } - } - /** Returns list of all pattern variables, possibly with their types, * without duplicates */ diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index fcb619cfca13..9d9f45dccf8b 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -345,6 +345,18 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped] def bodyKind(body: List[Tree])(implicit ctx: Context): FlagSet = body.foldLeft(NoInitsInterface)((fs, stat) => fs & defKind(stat)) + /** Info of a variable in a pattern: The named tree and its type */ + type VarInfo = (NameTree, Tree) + + /** An extractor for trees of the form `id` or `id: T` */ + object IdPattern { + def unapply(tree: Tree)(implicit ctx: Context): Option[VarInfo] = tree match { + case id: Ident if id.name != nme.WILDCARD => Some(id, TypeTree()) + case Typed(id: Ident, tpt) => Some((id, tpt)) + case _ => None + } + } + // todo: fill with other methods from TreeInfo that only apply to untpd.Tree's } diff --git a/compiler/src/dotty/tools/dotc/config/Config.scala b/compiler/src/dotty/tools/dotc/config/Config.scala index 9e0c0a997511..437db786e3a3 100644 --- a/compiler/src/dotty/tools/dotc/config/Config.scala +++ b/compiler/src/dotty/tools/dotc/config/Config.scala @@ -160,6 +160,9 @@ object Config { /** Assume -indent by default */ final val defaultIndent = true + /** Assume indentation is significant after a class, object, ... signature */ + final val silentTemplateIndent = true + /** If set, prints a trace of all symbol completions */ final val showCompletions = false diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index be885758046e..0065573e0f78 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -832,11 +832,12 @@ object SymDenotations { | Access to protected $this not permitted because enclosing ${ctx.owner.enclosingClass.showLocated} | is not a subclass of ${owner.showLocated} where target is defined""") else if - (!( isType // allow accesses to types from arbitrary subclasses fixes #4737 + !( isType // allow accesses to types from arbitrary subclasses fixes #4737 || pre.derivesFrom(cls) || isConstructor || owner.is(ModuleClass) // don't perform this check for static members - )) + ) + then fail( i""" | Access to protected ${symbol.show} not permitted because prefix type ${pre.widen.show} diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 50b917574bc4..4de9b5230f8d 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -26,6 +26,7 @@ import Decorators._ import scala.internal.Chars import scala.annotation.{tailrec, switch} import rewrites.Rewrites.{patch, overlapsPatch} +import config.Config.silentTemplateIndent object Parsers { @@ -124,13 +125,13 @@ object Parsers { /* ------------- ERROR HANDLING ------------------------------------------- */ /** The offset where the last syntax error was reported, or if a skip to a - * safepoint occurred afterwards, the offset of the safe point. - */ + * safepoint occurred afterwards, the offset of the safe point. + */ protected var lastErrorOffset : Int = -1 /** Issue an error at given offset if beyond last error offset - * and update lastErrorOffset. - */ + * and update lastErrorOffset. + */ def syntaxError(msg: => Message, offset: Int = in.offset): Unit = if (offset > lastErrorOffset) { val length = if (offset == in.offset && in.name != null) in.name.show.length else 0 @@ -337,6 +338,9 @@ object Parsers { offset } + def reportMissing(expected: Token): Unit = + syntaxError(ExpectedTokenButFound(expected, in.token)) + /** semi = nl {nl} | `;' * nl = `\n' // where allowed */ @@ -356,6 +360,17 @@ object Parsers { accept(SEMI) } + /** Under -language:Scala2 or -old-syntax, flag + * + * extends p1 with new p1 with t1 with + * p2 p2 t2 + * + * as a migration warning or error since that means something else under significant indentation. + */ + def checkNotWithAtEOL(): Unit = + if (in.isScala2Mode || in.oldSyntax) && in.isAfterLineEnd then + in.errorOrMigrationWarning("`with` cannot be followed by new line, place at beginning of next line instead") + def rewriteNotice(additionalOption: String = "") = { val optionStr = if (additionalOption.isEmpty) "" else " " ++ additionalOption i"\nThis construct can be rewritten automatically under$optionStr -rewrite." @@ -616,6 +631,7 @@ object Parsers { /** If indentation is not significant, check that this is not the start of a * statement that's indented relative to the current region. + * TODO: Drop if `with` is required before indented template definitions. */ def checkNextNotIndented(): Unit = in.currentRegion match case r: IndentSignificantRegion if in.isNewLine => @@ -1249,10 +1265,14 @@ object Parsers { newLineOptWhenFollowedBy(LBRACE) } - def possibleTemplateStart(): Unit = { - in.observeIndented() - newLineOptWhenFollowedBy(LBRACE) - } + def possibleTemplateStart(isNew: Boolean = false): Unit = + if in.token == WITH then + in.nextToken() + if in.token != LBRACE && in.token != INDENT then + syntaxError(i"indented definitions or `{' expected") + else + if silentTemplateIndent && !isNew then in.observeIndented() + newLineOptWhenFollowedBy(LBRACE) def indentRegion[T](tag: EndMarkerTag)(op: => T): T = { val iw = in.currentRegion.indentWidth @@ -1396,7 +1416,7 @@ object Parsers { makeParameter(name, typ(), mods | Param) } - /** InfixType ::= RefinedType {id [nl] refinedType} + /** InfixType ::= RefinedType {id [nl] RefinedType} */ def infixType(): Tree = infixTypeRest(refinedType()) @@ -1407,7 +1427,7 @@ object Parsers { def infixTypeRest(t: Tree): Tree = infixOps(t, canStartTypeTokens, refinedType, isType = true, isOperator = !isPostfixStar) - /** RefinedType ::= WithType {Annotation | [nl] Refinement} + /** RefinedType ::= WithType {[nl | `with'] Refinement} */ val refinedType: () => Tree = () => refinedTypeRest(withType()) @@ -1423,12 +1443,16 @@ object Parsers { def withType(): Tree = withTypeRest(annotType()) def withTypeRest(t: Tree): Tree = - if (in.token == WITH) { - if (ctx.settings.strict.value) - deprecationWarning(DeprecatedWithOperator()) + if in.token == WITH then + val withOffset = in.offset in.nextToken() - makeAndType(t, withType()) - } + if in.token == LBRACE || in.token == INDENT then + t + else + checkNotWithAtEOL() + if (ctx.settings.strict.value) + deprecationWarning(DeprecatedWithOperator(), withOffset) + makeAndType(t, withType()) else t /** AnnotType ::= SimpleType {Annotation} @@ -1676,7 +1700,7 @@ object Parsers { else if (altToken == THEN || enclosedInParens) && in.isNewLine then in.observeIndented() - if !enclosedInParens && in.token != INDENT then accept(altToken) + if !enclosedInParens && in.token != INDENT then reportMissing(altToken) if (rewriteToNewSyntax(t.span)) dropParensOrBraces(t.span.start, s"${tokenString(altToken)}") t @@ -2125,7 +2149,8 @@ object Parsers { } } - /** SimpleExpr ::= ‘new’ (ConstrApp {`with` ConstrApp} [TemplateBody] | TemplateBody) + /** SimpleExpr ::= ‘new’ ConstrApp {`with` ConstrApp} [TemplateBody] + * | ‘new’ TemplateBody */ def newExpr(): Tree = indentRegion(NEW) { @@ -2133,18 +2158,10 @@ object Parsers { def reposition(t: Tree) = t.withSpan(Span(start, in.lastOffset)) possibleBracesStart() val parents = - if (in.isNestedStart) Nil - else constrApp() :: { - if (in.token == WITH) { - // Enable this for 3.1, when we drop `with` for inheritance: - // in.errorUnlessInScala2Mode( - // "anonymous class with multiple parents is no longer supported; use a named class instead") - in.nextToken() - tokenSeparated(WITH, constrApp) - } - else Nil - } - possibleBracesStart() + if in.token == LBRACE || in.token == WITH then Nil + else constrApps(commaOK = false, templateCanFollow = true) + colonAtEOLOpt() + possibleTemplateStart(isNew = true) parents match { case parent :: Nil if !in.isNestedStart => reposition(if (parent.isType) ensureApplied(wrapNew(parent)) else parent) @@ -3052,11 +3069,17 @@ object Parsers { } else EmptyTree lhs match { - case (id @ Ident(name: TermName)) :: Nil if name != nme.WILDCARD => - val vdef = ValDef(name, tpt, rhs) + case IdPattern(id, t) :: Nil if t.isEmpty => + val vdef = ValDef(id.name.asTermName, tpt, rhs) if (isBackquoted(id)) vdef.pushAttachment(Backquoted, ()) finalizeDef(vdef, mods, start) case _ => + def isAllIds = lhs.forall { + case IdPattern(id, t) => t.isEmpty + case _ => false + } + if rhs.isEmpty && !isAllIds then + syntaxError(ExpectedTokenButFound(EQUALS, in.token), Span(in.lastOffset)) PatDef(mods, lhs, tpt, rhs) } } @@ -3325,7 +3348,7 @@ object Parsers { val parents = if (in.token == EXTENDS) { in.nextToken() - tokenSeparated(WITH, constrApp) + constrApps(commaOK = true, templateCanFollow = false) } else Nil Template(constr, parents, Nil, EmptyValDef, Nil) @@ -3341,12 +3364,20 @@ object Parsers { case _ => syntaxError(em"extension clause must start with a single regular parameter", start) + def checkExtensionMethod(stat: Tree): Unit = stat match { + case stat: DefDef => + if stat.mods.is(Extension) then + syntaxError(i"no extension method allowed here since leading parameter was already given", stat.span) + case _ => + syntaxError(i"extension clause can only define methods", stat.span) + } /** GivenDef ::= [GivenSig (‘:’ | <:)] Type ‘=’ Expr * | [GivenSig ‘:’] [ConstrApp {‘,’ ConstrApp }] [TemplateBody] - * | [id ‘:’] [ExtParamClause] TemplateBody + * | [id ‘:’] ExtParamClause ExtMethods * GivenSig ::= [id] [DefTypeParamClause] {GivenParamClause} * ExtParamClause ::= [DefTypeParamClause] DefParamClause {GivenParamClause} + * ExtMethods ::= [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ */ def givenDef(start: Offset, mods: Modifiers, instanceMod: Mod) = atSpan(start, nameStart) { var mods1 = addMod(mods, instanceMod) @@ -3375,6 +3406,7 @@ object Parsers { if in.token == COLON then in.nextToken() if in.token == LBRACE + || in.token == WITH || in.token == LBRACKET || in.token == LPAREN && followingIsParamOrGivenType() then @@ -3387,8 +3419,10 @@ object Parsers { syntaxError("`<:' is only allowed for given with `inline' modifier") in.nextToken() TypeBoundsTree(EmptyTree, toplevelTyp()) :: Nil - else if name.isEmpty && in.token != LBRACE then - tokenSeparated(COMMA, constrApp) + else if name.isEmpty + && in.token != LBRACE && in.token != WITH + && !hasExtensionParams + then tokenSeparated(COMMA, constrApp) else Nil val gdef = @@ -3401,12 +3435,17 @@ object Parsers { case TypeBoundsTree(_, _) :: _ => syntaxError("`=' expected") case _ => possibleTemplateStart() - if !hasExtensionParams then + if hasExtensionParams then + in.observeIndented() + else tparams = tparams.map(tparam => tparam.withMods(tparam.mods | PrivateLocal)) vparamss = vparamss.map(_.map(vparam => vparam.withMods(vparam.mods &~ Param | ParamAccessor | PrivateLocal))) val templ = templateBodyOpt(makeConstructor(tparams, vparamss), parents, Nil) - if tparams.isEmpty && vparamss.isEmpty || hasExtensionParams then ModuleDef(name, templ) + if hasExtensionParams then + templ.body.foreach(checkExtensionMethod) + ModuleDef(name, templ) + else if tparams.isEmpty && vparamss.isEmpty then ModuleDef(name, templ) else TypeDef(name.toTypeName, templ) finalizeDef(gdef, mods1, start) @@ -3423,51 +3462,47 @@ object Parsers { if in.token == LPAREN then parArgumentExprss(wrapNew(t)) else t } - /** ConstrApps ::= ConstrApp {‘with’ ConstrApp} (to be deprecated in 3.1) - * | ConstrApp {‘,’ ConstrApp} + /** ConstrApps ::= ConstrApp {(‘,’ | ‘with’) ConstrApp} */ - def constrApps(): List[Tree] = { + def constrApps(commaOK: Boolean, templateCanFollow: Boolean): List[Tree] = val t = constrApp() val ts = - if (in.token == WITH) { - in.nextToken() - tokenSeparated(WITH, constrApp) - } - else if (in.token == COMMA) { + if in.token == WITH then + val lookahead = in.LookaheadScanner(indent = true) + lookahead.nextToken() + if templateCanFollow && (lookahead.token == LBRACE || lookahead.token == INDENT) then + Nil + else + in.nextToken() + checkNotWithAtEOL() + constrApps(commaOK, templateCanFollow) + else if commaOK && in.token == COMMA then in.nextToken() - tokenSeparated(COMMA, constrApp) - } + constrApps(commaOK, templateCanFollow) else Nil t :: ts - } - /** InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}] + /** Template ::= InheritClauses [TemplateBody] + * InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}] */ - def inheritClauses(): (List[Tree], List[Tree]) = { - val extended = + def template(constr: DefDef, isEnum: Boolean = false): Template = { + val parents = if (in.token == EXTENDS) { in.nextToken() if (in.token == LBRACE || in.token == COLONEOL) { in.errorOrMigrationWarning("`extends' must be followed by at least one parent") Nil } - else constrApps() + else constrApps(commaOK = true, templateCanFollow = true) } else Nil + newLinesOptWhenFollowedBy(nme.derives) val derived = if (isIdent(nme.derives)) { in.nextToken() tokenSeparated(COMMA, () => convertToTypeId(qualId())) } else Nil - (extended, derived) - } - - /** Template ::= InheritClauses [TemplateBody] - */ - def template(constr: DefDef, isEnum: Boolean = false): Template = { - newLinesOptWhenFollowedBy(nme.derives) - val (parents, derived) = inheritClauses() possibleTemplateStart() if (isEnum) { val (self, stats) = withinEnum(templateBody()) @@ -3490,7 +3525,8 @@ object Parsers { checkNextNotIndented() Template(constr, Nil, Nil, EmptyValDef, Nil) - /** TemplateBody ::= [nl] `{' TemplateStatSeq `}' + /** TemplateBody ::= [nl | `with'] `{' TemplateStatSeq `}' + * EnumBody ::= [nl | ‘with’] ‘{’ [SelfType] EnumStat {semi EnumStat} ‘}’ */ def templateBodyOpt(constr: DefDef, parents: List[Tree], derived: List[Tree]): Template = val (self, stats) = @@ -3518,7 +3554,7 @@ object Parsers { case x: RefTree => atSpan(start, pointOffset(pkg))(PackageDef(x, stats)) } - /** Packaging ::= package QualId [nl] `{' TopStatSeq `}' + /** Packaging ::= package QualId [nl | `with'] `{' TopStatSeq `}' */ def packaging(start: Int): Tree = { val pkg = qualId() @@ -3707,23 +3743,23 @@ object Parsers { ts ++= topStatSeq() } } - else { + else val pkg = qualId() + var continue = false indentRegion(pkg) { possibleTemplateStart() - if (in.token == EOF) + if in.token == EOF then ts += makePackaging(start, pkg, List()) - else if (in.isNestedStart) { + else if in.isNestedStart then ts += inDefScopeBraces(makePackaging(start, pkg, topStatSeq())) - acceptStatSepUnlessAtEnd() - ts ++= topStatSeq() - } - else { + continue = true + else acceptStatSep() ts += makePackaging(start, pkg, topstats()) - } } - } + if continue then + acceptStatSepUnlessAtEnd() + ts ++= topStatSeq() } else ts ++= topStatSeq() diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index eedaacd5cc6d..a7513b8a722b 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -869,8 +869,8 @@ object Scanners { // Lookahead --------------------------------------------------------------- - class LookaheadScanner extends Scanner(source, offset) { - override val indentSyntax = false + class LookaheadScanner(indent: Boolean = false) extends Scanner(source, offset) { + override val indentSyntax = indent override protected def printState() = { print("la:") super.printState() diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index 72bde459335a..edec54fbb206 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -270,7 +270,7 @@ object Tokens extends TokensCommon { final val closingRegionTokens = BitSet(RBRACE, CASE) | statCtdTokens final val canStartIndentTokens: BitSet = - statCtdTokens | BitSet(COLONEOL, EQUALS, ARROW, LARROW, WHILE, TRY, FOR) + statCtdTokens | BitSet(COLONEOL, EQUALS, ARROW, LARROW, WHILE, TRY, FOR, IF, WITH) // `if` is excluded because it often comes after `else` which makes for awkward indentation rules TODO: try to do without the exception /** Faced with the choice between a type and a formal parameter, the following diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 3019c37d925b..6820a10829e0 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -84,7 +84,6 @@ comment ::= ‘/*’ “any sequence of characters; nested comments ar | ‘//’ “any sequence of characters up to end of line” nl ::= “new line character” -cnl ::= nl | "colon at eol" semi ::= ‘;’ | nl {nl} ``` @@ -151,8 +150,8 @@ FunArgTypes ::= InfixType | ‘(’ ‘[given]’ TypedFunParam {‘,’ TypedFunParam } ‘)’ TypedFunParam ::= id ‘:’ Type MatchType ::= InfixType `match` TypeCaseClauses -InfixType ::= RefinedType {id [cnl] RefinedType} InfixOp(t1, op, t2) -RefinedType ::= WithType {[cnl] Refinement} RefinedTypeTree(t, ds) +InfixType ::= RefinedType {id [nl] RefinedType} InfixOp(t1, op, t2) +RefinedType ::= WithType {[nl | ‘with’] Refinement} RefinedTypeTree(t, ds) WithType ::= AnnotType {‘with’ AnnotType} (deprecated) AnnotType ::= SimpleType {Annotation} Annotated(t, annot) SimpleType ::= SimpleType TypeArgs AppliedTypeTree(t, args) @@ -209,22 +208,23 @@ Ascription ::= ‘:’ InfixType Catches ::= ‘catch’ Expr PostfixExpr ::= InfixExpr [id] PostfixOp(expr, op) InfixExpr ::= PrefixExpr - | InfixExpr id [cnl] InfixExpr InfixOp(expr, op, expr) + | InfixExpr id [nl] InfixExpr InfixOp(expr, op, expr) | InfixExpr ‘given’ (InfixExpr | ParArgumentExprs) PrefixExpr ::= [‘-’ | ‘+’ | ‘~’ | ‘!’] SimpleExpr PrefixOp(expr, op) -SimpleExpr ::= ‘new’ (ConstrApp [TemplateBody] | TemplateBody) New(constr | templ) +SimpleExpr ::= Path + | Literal + | ‘_’ | BlockExpr | ‘$’ ‘{’ Block ‘}’ | Quoted | quoteId // only inside splices - | SimpleExpr1 [‘_’] PostfixOp(expr, _) -SimpleExpr1 ::= Literal - | Path - | ‘_’ + | ‘new’ ConstrApp {`with` ConstrApp} [TemplateBody] New(constr | templ) + | ‘new’ TemplateBody | ‘(’ ExprsInParens ‘)’ Parens(exprs) | SimpleExpr ‘.’ id Select(expr, id) - | SimpleExpr (TypeArgs | NamedTypeArgs) TypeApply(expr, args) - | SimpleExpr1 ArgumentExprs Apply(expr, args) + | SimpleExpr TypeArgs TypeApply(expr, args) + | SimpleExpr ArgumentExprs Apply(expr, args) + | SimpleExpr ‘_’ PostfixOp(expr, _) | XmlExpr Quoted ::= ‘'’ ‘{’ Block ‘}’ | ‘'’ ‘[’ Type ‘]’ @@ -234,8 +234,8 @@ ExprInParens ::= PostfixExpr ‘:’ Type ParArgumentExprs ::= ‘(’ [‘given’] ExprsInParens ‘)’ exprs | ‘(’ [ExprsInParens ‘,’] PostfixExpr ‘:’ ‘_’ ‘*’ ‘)’ exprs :+ Typed(expr, Ident(wildcardStar)) ArgumentExprs ::= ParArgumentExprs - | [cnl] BlockExpr -BlockExpr ::= ‘{’ CaseClauses | Block ‘}’ + | [nl] BlockExpr +BlockExpr ::= ‘{’ (CaseClauses | Block) ‘}’ Block ::= {BlockStat semi} [BlockResult] Block(stats, expr?) BlockStat ::= Import | {Annotation [nl]} [‘implicit’ | ‘lazy’] Def @@ -263,7 +263,7 @@ Pattern ::= Pattern1 { ‘|’ Pattern1 } Pattern1 ::= Pattern2 [‘:’ RefinedType] Bind(name, Typed(Ident(wildcard), tpe)) | ‘given’ PatVar ‘:’ RefinedType Pattern2 ::= [id ‘@’] InfixPattern Bind(name, pat) -InfixPattern ::= SimplePattern { id [cnl] SimplePattern } InfixOp(pat, op, pat) +InfixPattern ::= SimplePattern { id [nl] SimplePattern } InfixOp(pat, op, pat) SimplePattern ::= PatVar Ident(wildcard) | Literal Bind(name, Ident(wildcard)) | ‘(’ [Patterns] ‘)’ Parens(pats) Tuple(pats) @@ -388,19 +388,20 @@ ObjectDef ::= id [Template] EnumDef ::= id ClassConstr InheritClauses EnumBody EnumDef(mods, name, tparams, template) GivenDef ::= [GivenSig (‘:’ | <:)] Type ‘=’ Expr | [GivenSig ‘:’] [ConstrApp {‘,’ ConstrApp }] [TemplateBody] - | [GivenSig ‘:’] [ExtParamClause] TemplateBody + | [GivenSig ‘:’] ExtParamClause ExtMethods GivenSig ::= [id] [DefTypeParamClause] {GivenParamClause} ExtParamClause ::= [DefTypeParamClause] ‘(’ DefParam ‘)’ {GivenParamClause} +ExtMethods ::= [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ Template ::= InheritClauses [TemplateBody] Template(constr, parents, self, stats) InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}] -ConstrApps ::= ConstrApp {‘with’ ConstrApp} - | ConstrApp {‘,’ ConstrApp} +ConstrApps ::= ConstrApp {(‘,’ | ‘with’) ConstrApp} ConstrApp ::= AnnotType {ParArgumentExprs} Apply(tp, args) ConstrExpr ::= SelfInvocation | ‘{’ SelfInvocation {semi BlockStat} ‘}’ SelfInvocation ::= ‘this’ ArgumentExprs {ArgumentExprs} -TemplateBody ::= [cnl] ‘{’ [SelfType] TemplateStat {semi TemplateStat} ‘}’ (self, stats) +TemplateBody ::= [nl | ‘with’] + ‘{’ [SelfType] TemplateStat {semi TemplateStat} ‘}’ (self, stats) TemplateStat ::= Import | Export | {Annotation [nl]} {Modifier} Def @@ -410,7 +411,7 @@ TemplateStat ::= Import SelfType ::= id [‘:’ InfixType] ‘=>’ ValDef(_, name, tpt, _) | ‘this’ ‘:’ InfixType ‘=>’ -EnumBody ::= [cnl] ‘{’ [SelfType] EnumStat {semi EnumStat} ‘}’ +EnumBody ::= [nl | ‘with’] ‘{’ [SelfType] EnumStat {semi EnumStat} ‘}’ EnumStat ::= TemplateStat | {Annotation [nl]} {Modifier} EnumCase EnumCase ::= ‘case’ (id ClassConstr [‘extends’ ConstrApps]] | ids) @@ -422,8 +423,8 @@ TopStat ::= Import | Packaging | PackageObject | -Packaging ::= ‘package’ QualId [cnl] ‘{’ TopStatSeq ‘}’ Package(qid, stats) +Packaging ::= ‘package’ QualId [nl | ‘with’] ‘{’ TopStatSeq ‘}’ Package(qid, stats) PackageObject ::= ‘package’ ‘object’ ObjectDef object with package in mods. -CompilationUnit ::= {‘package’ QualId (semi | cnl)} TopStatSeq Package(qid, stats) +CompilationUnit ::= {‘package’ QualId semi} TopStatSeq Package(qid, stats) ``` diff --git a/docs/docs/reference/contextual/extension-methods-new.md b/docs/docs/reference/contextual/extension-methods-new.md new file mode 100644 index 000000000000..fbba4973aace --- /dev/null +++ b/docs/docs/reference/contextual/extension-methods-new.md @@ -0,0 +1,155 @@ +--- +layout: doc-page +title: "Extension Methods" +--- + +Extension methods allow one to add methods to a type after the type is defined. Example: + +```scala +case class Circle(x: Double, y: Double, radius: Double) + +def (c: Circle) circumference: Double = c.radius * math.Pi * 2 +``` + +Like regular methods, extension methods can be invoked with infix `.`: + +```scala +val circle = Circle(0, 0, 1) +circle.circumference +``` + +### Translation of Extension Methods + +Extension methods are methods that have a parameter clause in front of the defined +identifier. They translate to methods where the leading parameter section is moved +to after the defined identifier. So, the definition of `circumference` above translates +to the plain method, and can also be invoked as such: +```scala +def circumference(c: Circle): Double = c.radius * math.Pi * 2 + +assert(circle.circumference == circumference(circle)) +``` + +### Translation of Calls to Extension Methods + +When is an extension method applicable? There are two possibilities. + + - An extension method is applicable if it is visible under a simple name, by being defined + or inherited or imported in a scope enclosing the application. + - An extension method is applicable if it is a member of some given instance at the point of the application. + +As an example, consider an extension method `longestStrings` on `Seq[String]` defined in a trait `StringSeqOps`. + +```scala +trait StringSeqOps { + def (xs: Seq[String]) longestStrings = { + val maxLength = xs.map(_.length).max + xs.filter(_.length == maxLength) + } +} +``` +We can make the extension method available by defining a given `StringSeqOps` instance, like this: +```scala +given ops1: StringSeqOps +``` +Then +```scala +List("here", "is", "a", "list").longestStrings +``` +is legal everywhere `ops1` is available. Alternatively, we can define `longestStrings` as a member of a normal object. But then the method has to be brought into scope to be usable as an extension method. + +```scala +object ops2 extends StringSeqOps +import ops2.longestStrings +List("here", "is", "a", "list").longestStrings +``` +The precise rules for resolving a selection to an extension method are as follows. + +Assume a selection `e.m[Ts]` where `m` is not a member of `e`, where the type arguments `[Ts]` are optional, +and where `T` is the expected type. The following two rewritings are tried in order: + + 1. The selection is rewritten to `m[Ts](e)`. + 2. If the first rewriting does not typecheck with expected type `T`, and there is a given instance `i` + in either the current scope or in the implicit scope of `T`, and `i` defines an extension + method named `m`, then selection is expanded to `i.m[Ts](e)`. + This second rewriting is attempted at the time where the compiler also tries an implicit conversion + from `T` to a type containing `m`. If there is more than one way of rewriting, an ambiguity error results. + +So `circle.circumference` translates to `CircleOps.circumference(circle)`, provided +`circle` has type `Circle` and `CircleOps` is given (i.e. it is visible at the point of call or it is defined in the companion object of `Circle`). + +### Given Instances for Extension Methods + +A special syntax allows to define a given instance for one or more extension methods without listing a parent type. +Examples: + +```scala +given stringOps: (xs: Seq[String]) { + def longestStrings: Seq[String] = { + val maxLength = xs.map(_.length).max + xs.filter(_.length == maxLength) + } +} + +given [T](xs: List[T]) { + def second = xs.tail.head + def third[T]: T = xs.tail.tail.head +} +``` +These given clauses define extension methods `longestStrings`, `second`, and `third`. All extension methods defined in such a given clause +share the same leading parameters, which follow the `given`. The remainder of the extension methods is written as regular defs inside braces. + +If such given instances are anonymous (as in the second clause), their name is synthesized from the name of the first defined extension method. + + +### Operators + +The extension method syntax also applies to the definition of operators. +In each case the definition syntax mirrors the way the operator is applied. +Examples: +```scala +def (x: String) < (y: String) = ... +def (x: Elem) +: (xs: Seq[Elem]) = ... + +"ab" + "c" +1 +: List(2, 3) +``` +The two definitions above translate to +```scala +def < (x: String)(y: String) = ... +def +: (xs: Seq[Elem])(x: Elem) = ... +``` +Note that swap of the two parameters `x` and `xs` when translating +the right-binding operator `+:` to an extension method. This is analogous +to the implementation of right binding operators as normal methods. + +### Generic Extensions + +The `StringSeqOps` examples extended a specific instance of a generic type. It is also possible to extend a generic type by adding type parameters to an extension method. Examples: + +```scala +def (xs: List[T]) second [T] = + xs.tail.head + +def (xs: List[List[T]]) flattened [T] = + xs.foldLeft[List[T]](Nil)(_ ++ _) + +def (x: T) + [T : Numeric](y: T): T = + summon[Numeric[T]].plus(x, y) +``` + +As usual, type parameters of the extension method follow the defined method name. Nevertheless, such type parameters can already be used in the preceding parameter clause. + + +### Syntax + +The required syntax extension just adds one clause for extension methods relative +to the [current syntax](../../internals/syntax.md). +``` +DefSig ::= ... + | ‘(’ DefParam ‘)’ [nl] id [DefTypeParamClause] DefParamClauses +GivenDef ::= ... + [GivenSig ‘:’] [ExtParamClause] ExtMethods +ExtParamClause ::= [DefTypeParamClause] ‘(’ DefParam ‘)’ {GivenParamClause} +ExtMethods ::= [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ +``` diff --git a/docs/docs/reference/contextual/extension-methods.md b/docs/docs/reference/contextual/extension-methods.md index 8388844eb77a..646914287a01 100644 --- a/docs/docs/reference/contextual/extension-methods.md +++ b/docs/docs/reference/contextual/extension-methods.md @@ -3,6 +3,9 @@ layout: doc-page title: "Extension Methods" --- +**Note** The syntax described in this section is currently under revision. +[Here is the new version which will be implemented in Dotty 0.20](./extension-methods-new.html). + Extension methods allow one to add methods to a type after the type is defined. Example: ```scala diff --git a/docs/docs/reference/other-new-features/control-syntax-new.md b/docs/docs/reference/other-new-features/control-syntax-new.md index 01987c1e0652..6e980a982ec9 100644 --- a/docs/docs/reference/other-new-features/control-syntax-new.md +++ b/docs/docs/reference/other-new-features/control-syntax-new.md @@ -36,7 +36,6 @@ The rules in detail are: - The enumerators of a `for`-expression can be written without enclosing parentheses or braces if they are followed by a `yield` or `do`. - A `do` in a `for`-expression expresses a `for`-loop. - ### Rewrites The Dotty compiler can rewrite source code from old syntax and new syntax and back. diff --git a/docs/docs/reference/other-new-features/indentation-new.md b/docs/docs/reference/other-new-features/indentation-new.md index 0b9c710431a4..4d30850fa6a8 100644 --- a/docs/docs/reference/other-new-features/indentation-new.md +++ b/docs/docs/reference/other-new-features/indentation-new.md @@ -3,7 +3,7 @@ layout: doc-page title: Optional Braces --- -As an experimental feature, Scala 3 treats indentation as significant and allows +As an experimental feature, Scala 3 enforces some rules on indentation and allows some occurrences of braces `{...}` to be optional. - First, some badly indented programs are ruled out, which means they are flagged with warnings. @@ -12,7 +12,7 @@ some occurrences of braces `{...}` to be optional. ### Indentation Rules -The compiler enforces three rules for well-indented programs, flagging violations as warnings. +The compiler enforces two rules for well-indented programs, flagging violations as warnings. 1. In a brace-delimited region, no statement is allowed to start to the left of the first statement after the opening brace that starts a new line. @@ -35,14 +35,6 @@ The compiler enforces three rules for well-indented programs, flagging violation println(2) // error: missing `{` ``` - 3. If significant indentation is turned off, code that follows a class or object definition (or similar) lacking a `{...}` body may not be indented more than that definition. This prevents misleading situations like: - - ```scala - trait A - case class B() extends A // error: indented too far to the right - ``` - It requires that the case class `C` to be written instead at the same level of indentation as the trait `A`. - These rules still leave a lot of leeway how programs should be indented. For instance, they do not impose any restrictions on indentation within expressions, nor do they require that all statements of an indentation block line up exactly. @@ -66,11 +58,10 @@ There are two rules: An indentation region can start - after the condition of an `if-else`, or - - at points where a set of definitions enclosed in braces is expected in a - class, object, given, or enum definition, in an enum case, or after a package clause, or + - after the leading parameter(s) of a given extension method clause, or - after one of the following tokens: ``` - = => <- if then else while do try catch finally for yield match + = => <- if then else while do try catch finally for yield match return with ``` If an `` is inserted, the indentation width of the token on the next line is pushed onto `IW`, which makes it the new current indentation width. @@ -97,6 +88,66 @@ if x < 0 Indentation tokens are only inserted in regions where newline statement separators are also inferred: at the toplevel, inside braces `{...}`, but not inside parentheses `(...)`, patterns or types. +### New Role of With + +To make bracews optional for constructs like class bodies, the syntax of the language is changed so that a class body or similar construct may optionally be prefixed with `with`. Since `with` can start an indentation region, this means that all of the following syntaxes are allowed and are equivalent: +```scala +trait A { + def f: Any +} +class C(x: Int) extends A { + def f = x +} +type T = A { + def f: Int +} +``` +--- +```scala +trait A with { + def f: Int +} +class C(x: Int) extends A with { + def f = x +} +type T = A with { + def f: Int +} +``` +--- +```scala +trait A with + def f: Int + +class C(x: Int) extends A with + def f = x + +type T = A with + def f: Int +``` + +The syntax changes allowing this are as follows: +``` +TemplateBody ::= [‘with’] ‘{’ [SelfType] TemplateStat {semi TemplateStat} ‘}’ +EnumBody ::= [‘with’] ‘{’ [SelfType] EnumStat {semi EnumStat} ‘}’ +Packaging ::= ‘package’ QualId [‘with’] ‘{’ TopStatSeq ‘}’ +RefinedType ::= AnnotType {[‘with’] Refinement} +``` +It is assumed here that braces following a `with` can be transparently replaced by an +indentation region. + +With the new indentation rules, the previously allowed syntax +``` +class A extends B with + C +``` +becomes illegal since `C` above would be terated as a nested statement inside `A`. More generally, a `with` that separates parent constructors cannot be at the end of a line. One has to write +``` +class A extends B + with C +``` +instead (or replace the "`with`" by a "`,`"). When compiling in Scala-2 mode, a migration warning is issued for the illegal syntax and a (manual) rewrite is suggested. + ### Spaces vs Tabs Indentation prefixes can consist of spaces and/or tabs. Indentation widths are the indentation prefixes themselves, ordered by the string prefix relation. So, so for instance "2 tabs, followed by 4 spaces" is strictly less than "2 tabs, followed by 5 spaces", but "2 tabs, followed by 4 spaces" is incomparable to "6 tabs" or to "4 spaces, followed by 2 tabs". It is an error if the indentation width of some line is incomparable with the indentation width of the region that's current at that point. To avoid such errors, it is a good idea not to mix spaces and tabs in the same source file. @@ -165,46 +216,44 @@ It is recommended that `end` markers are used for code where the extent of an in Here is a (somewhat meta-circular) example of code using indentation. It provides a concrete representation of indentation widths as defined above together with efficient operations for constructing and comparing indentation widths. ```scala -enum IndentWidth +enum IndentWidth with case Run(ch: Char, n: Int) case Conc(l: IndentWidth, r: Run) - def <= (that: IndentWidth): Boolean = - this match - case Run(ch1, n1) => - that match - case Run(ch2, n2) => n1 <= n2 && (ch1 == ch2 || n1 == 0) - case Conc(l, r) => this <= l - case Conc(l1, r1) => - that match - case Conc(l2, r2) => l1 == l2 && r1 <= r2 - case _ => false + def <= (that: IndentWidth): Boolean = this match + case Run(ch1, n1) => + that match + case Run(ch2, n2) => n1 <= n2 && (ch1 == ch2 || n1 == 0) + case Conc(l, r) => this <= l + case Conc(l1, r1) => + that match + case Conc(l2, r2) => l1 == l2 && r1 <= r2 + case _ => false def < (that: IndentWidth): Boolean = this <= that && !(that <= this) - override def toString: String = - this match - case Run(ch, n) => - val kind = ch match - case ' ' => "space" - case '\t' => "tab" - case _ => s"'$ch'-character" - val suffix = if n == 1 then "" else "s" - s"$n $kind$suffix" - case Conc(l, r) => - s"$l, $r" - -object IndentWidth + override def toString: String = this match + case Run(ch, n) => + val kind = ch match + case ' ' => "space" + case '\t' => "tab" + case _ => s"'$ch'-character" + val suffix = if n == 1 then "" else "s" + s"$n $kind$suffix" + case Conc(l, r) => + s"$l, $r" + +object IndentWidth with private inline val MaxCached = 40 private val spaces = IArray.tabulate(MaxCached + 1)(new Run(' ', _)) private val tabs = IArray.tabulate(MaxCached + 1)(new Run('\t', _)) def Run(ch: Char, n: Int): Run = - if n <= MaxCached && ch == ' ' + if n <= MaxCached && ch == ' ' then spaces(n) - else if n <= MaxCached && ch == '\t' + else if n <= MaxCached && ch == '\t' then tabs(n) else new Run(ch, n) diff --git a/tests/neg/deferred-patdef.scala b/tests/neg/deferred-patdef.scala new file mode 100644 index 000000000000..11ec8c2218d5 --- /dev/null +++ b/tests/neg/deferred-patdef.scala @@ -0,0 +1,4 @@ +object M { + val (x, y): (Int, Int) // error: `=` expected + val 1: Int // error: `=` expected +} diff --git a/tests/pos/packagings.scala b/tests/pos/packagings.scala new file mode 100644 index 000000000000..e78ed31b4b5d --- /dev/null +++ b/tests/pos/packagings.scala @@ -0,0 +1,10 @@ +package foo with + package bar with + object A with + def foo = 1 + end bar +end foo +package baz with + object B with + def f = foo.bar.A.foo +end baz diff --git a/tests/pos/reference/adts.scala b/tests/pos/reference/adts.scala index 716211f8b10f..5dc81823d9eb 100644 --- a/tests/pos/reference/adts.scala +++ b/tests/pos/reference/adts.scala @@ -1,44 +1,32 @@ package adts -object t1 { +object t1 with -enum Option[+T] { - case Some(x: T) - case None -} + enum Option[+T] with + case Some(x: T) + case None -} +object t2 with -object t2 { + enum Option[+T] with + case Some(x: T) extends Option[T] + case None extends Option[Nothing] -enum Option[+T] { - case Some(x: T) extends Option[T] - case None extends Option[Nothing] -} - - -} - -enum Color(val rgb: Int) { +enum Color(val rgb: Int) with case Red extends Color(0xFF0000) case Green extends Color(0x00FF00) case Blue extends Color(0x0000FF) case Mix(mix: Int) extends Color(mix) -} -object t3 { +object t3 with -enum Option[+T] { - case Some(x: T) extends Option[T] - case None + enum Option[+T] with + case Some(x: T) extends Option[T] + case None - def isDefined: Boolean = this match { - case None => false - case some => true - } -} -object Option { - def apply[T >: Null](x: T): Option[T] = - if (x == null) None else Some(x) -} + def isDefined: Boolean = this match + case None => false + case some => true -} + object Option with + def apply[T >: Null](x: T): Option[T] = + if (x == null) None else Some(x) diff --git a/tests/pos/reference/auto-param-tupling.scala b/tests/pos/reference/auto-param-tupling.scala index e700b019eb41..c2dd9889fa98 100644 --- a/tests/pos/reference/auto-param-tupling.scala +++ b/tests/pos/reference/auto-param-tupling.scala @@ -1,6 +1,6 @@ -package autoParamTupling { +package autoParamTupling -object t1 { +object t1 with val xs: List[(Int, Int)] = ??? xs.map { @@ -13,5 +13,3 @@ object t1 { xs.map(_ + _) -} -} diff --git a/tests/pos/reference/compile-time.scala b/tests/pos/reference/compile-time.scala index 87b75a424c34..cc4eb57688f5 100644 --- a/tests/pos/reference/compile-time.scala +++ b/tests/pos/reference/compile-time.scala @@ -1,6 +1,6 @@ package compiletime -class Test { +class Test with import scala.compiletime.{constValue, erasedValue, S} trait Nat @@ -8,14 +8,13 @@ class Test { case class Succ[N <: Nat](n: N) extends Nat inline def toIntC[N] <: Int = - inline constValue[N] match { + inline constValue[N] match case 0 => 0 case _: S[n1] => 1 + toIntC[n1] - } final val ctwo = toIntC[2] - inline def defaultValue[T] <: Option[Any] = inline erasedValue[T] match { + inline def defaultValue[T] <: Option[Any] = inline erasedValue[T] match case _: Byte => Some(0: Byte) case _: Char => Some(0: Char) case _: Short => Some(0: Short) @@ -26,20 +25,16 @@ class Test { case _: Boolean => Some(false) case _: Unit => Some(()) case _ => None - } val dInt: Some[Int] = defaultValue[Int] val dDouble: Some[Double] = defaultValue[Double] val dBoolean: Some[Boolean] = defaultValue[Boolean] val dAny: None.type = defaultValue[Any] - inline def toIntT[N <: Nat] <: Int = inline scala.compiletime.erasedValue[N] match { + inline def toIntT[N <: Nat] <: Int = inline scala.compiletime.erasedValue[N] match case _: Zero.type => 0 case _: Succ[n] => toIntT[n] + 1 - } inline def summonFrom(f: Nothing => Any) <: Any = ??? final val two = toIntT[Succ[Succ[Zero.type]]] - -} \ No newline at end of file diff --git a/tests/pos/reference/delegate-match.scala b/tests/pos/reference/delegate-match.scala index 0fd2e3b014a5..acc2f1fdcf60 100644 --- a/tests/pos/reference/delegate-match.scala +++ b/tests/pos/reference/delegate-match.scala @@ -1,6 +1,6 @@ package implicitmatch -class Test extends App { +class Test extends App with import scala.collection.immutable.{TreeSet, HashSet} import scala.compiletime.summonFrom @@ -22,4 +22,4 @@ class Test extends App { inline def f: Any = summonFrom { case ev: A => println(ev.x) // error: ambiguous implicits } -} + diff --git a/tests/pos/reference/delegates.scala b/tests/pos/reference/delegates.scala index 00f5dfcb322f..fbdb1e88be5d 100644 --- a/tests/pos/reference/delegates.scala +++ b/tests/pos/reference/delegates.scala @@ -1,95 +1,80 @@ -class Common { +class Common with - trait Ord[T] { + trait Ord[T] with def (x: T) compareTo (y: T): Int def (x: T) < (y: T) = x.compareTo(y) < 0 def (x: T) > (y: T) = x.compareTo(y) > 0 - } - trait Convertible[From, To] { + trait Convertible[From, To] with def (x: From) convert: To - } - trait SemiGroup[T] { + trait SemiGroup[T] with def (x: T) combine (y: T): T - } - trait Monoid[T] extends SemiGroup[T] { + trait Monoid[T] extends SemiGroup[T] with def unit: T - } - trait Functor[F[_]] { + trait Functor[F[_]] with def (x: F[A]) map[A, B] (f: A => B): F[B] - } - trait Monad[F[_]] extends Functor[F] { + trait Monad[F[_]] extends Functor[F] with def (x: F[A]) flatMap[A, B] (f: A => F[B]): F[B] def (x: F[A]) map[A, B] (f: A => B) = x.flatMap(f `andThen` pure) def pure[A](x: A): F[A] - } -} -object Instances extends Common { +object Instances extends Common with - given intOrd: Ord[Int] { + given intOrd: Ord[Int] with def (x: Int) compareTo (y: Int) = if (x < y) -1 else if (x > y) +1 else 0 - } - given listOrd[T](given Ord[T]): Ord[List[T]] { - def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match { + given listOrd[T](given Ord[T]): Ord[List[T]] with + def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match case (Nil, Nil) => 0 case (Nil, _) => -1 case (_, Nil) => +1 case (x :: xs1, y :: ys1) => val fst = x.compareTo(y) if (fst != 0) fst else xs1.compareTo(ys1) - } - } + end listOrd - given stringOps: { - def (xs: Seq[String]) longestStrings: Seq[String] = { + given stringOps: (xs: Seq[String]) + def longestStrings: Seq[String] = val maxLength = xs.map(_.length).max xs.filter(_.length == maxLength) - } - } - given { - def (xs: List[T]) second[T] = xs.tail.head - } + given [T](xs: List[T]) + def second = xs.tail.head + def third = xs.tail.tail.head - given listMonad: Monad[List] { + given listMonad: Monad[List] with def (xs: List[A]) flatMap[A, B] (f: A => List[B]): List[B] = xs.flatMap(f) def pure[A](x: A): List[A] = List(x) - } - given readerMonad[Ctx]: Monad[[X] =>> Ctx => X] { + given readerMonad[Ctx]: Monad[[X] =>> Ctx => X] with def (r: Ctx => A) flatMap[A, B] (f: A => Ctx => B): Ctx => B = ctx => f(r(ctx))(ctx) def pure[A](x: A): Ctx => A = ctx => x - } def maximum[T](xs: List[T])(given Ord[T]): T = xs.reduceLeft((x, y) => if (x < y) y else x) - def descending[T](given asc: Ord[T]): Ord[T] = new Ord[T] { + def descending[T](given asc: Ord[T]): Ord[T] = new Ord[T] with def (x: T) compareTo (y: T) = asc.compareTo(y)(x) - } def minimum[T](xs: List[T])(given Ord[T]) = maximum(xs)(given descending) - def test(): Unit = { + def test(): Unit = val xs = List(1, 2, 3) println(maximum(xs)) println(maximum(xs)(given descending)) println(maximum(xs)(given descending(given intOrd))) println(minimum(xs)) - } case class Context(value: String) val c0: (given Context) => String = (given ctx) => ctx.value @@ -99,25 +84,22 @@ object Instances extends Common { class B val ab: (given x: A, y: B) => Int = (given a: A, b: B) => 22 - trait TastyAPI { + trait TastyAPI with type Symbol - trait SymDeco { + trait SymDeco with def (sym: Symbol) name: String - } def symDeco: SymDeco given SymDeco = symDeco - } - object TastyImpl extends TastyAPI { + + object TastyImpl extends TastyAPI with type Symbol = String - val symDeco = new SymDeco { + val symDeco = new SymDeco with def (sym: Symbol) name = sym - } - } class D[T] - class C(given ctx: Context) { - def f() = { + class C(given ctx: Context) with + def f() = locally { given Context = this.ctx println(summon[Context].value) @@ -135,101 +117,79 @@ object Instances extends Common { given (given Context): D[Int] println(summon[D[Int]]) } - } - } + end C class Token(str: String) - object Token { - given StringToToken : Conversion[String, Token] { + object Token with + given StringToToken : Conversion[String, Token] with def apply(str: String): Token = new Token(str) - } - } val x: Token = "if" -} +end Instances -object PostConditions { +object PostConditions with opaque type WrappedResult[T] = T - private given WrappedResult: { - def apply[T](x: T): WrappedResult[T] = x - def (x: WrappedResult[T]) unwrap[T]: T = x - } - - def result[T](given wrapped: WrappedResult[T]): T = wrapped.unwrap + def result[T](given x: WrappedResult[T]): T = x - given { - def (x: T) ensuring[T] (condition: (given WrappedResult[T]) => Boolean): T = { - assert(condition(given WrappedResult(x))) + given [T](x: T) + def ensuring(condition: (given WrappedResult[T]) => Boolean): T = + assert(condition(given x)) x - } - } -} +end PostConditions -object AnonymousInstances extends Common { - given Ord[Int] { +object AnonymousInstances extends Common with + given Ord[Int] with def (x: Int) compareTo (y: Int) = if (x < y) -1 else if (x > y) +1 else 0 - } - given [T: Ord] : Ord[List[T]] { - def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match { + given [T: Ord] : Ord[List[T]] with + def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match case (Nil, Nil) => 0 case (Nil, _) => -1 case (_, Nil) => +1 case (x :: xs1, y :: ys1) => val fst = x.compareTo(y) if (fst != 0) fst else xs1.compareTo(ys1) - } - } - given { - def (xs: Seq[String]) longestStrings: Seq[String] = { + given with + def (xs: Seq[String]) longestStrings: Seq[String] = val maxLength = xs.map(_.length).max xs.filter(_.length == maxLength) - } - } - given { + given with def (xs: List[T]) second[T] = xs.tail.head - } - given [From, To](given c: Convertible[From, To]) : Convertible[List[From], List[To]] { + given [From, To](given c: Convertible[From, To]) : Convertible[List[From], List[To]] with def (x: List[From]) convert: List[To] = x.map(c.convert) - } - given Monoid[String] { + given Monoid[String] with def (x: String) combine (y: String): String = x.concat(y) def unit: String = "" - } def sum[T: Monoid](xs: List[T]): T = xs.foldLeft(summon[Monoid[T]].unit)(_.combine(_)) -} +end AnonymousInstances -object Implicits extends Common { - implicit object IntOrd extends Ord[Int] { +object Implicits extends Common with + implicit object IntOrd extends Ord[Int] with def (x: Int) compareTo (y: Int) = if (x < y) -1 else if (x > y) +1 else 0 - } - class ListOrd[T: Ord] extends Ord[List[T]] { - def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match { + class ListOrd[T: Ord] extends Ord[List[T]] with + def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match case (Nil, Nil) => 0 case (Nil, _) => -1 case (_, Nil) => +1 case (x :: xs1, y :: ys1) => val fst = x.compareTo(y) if (fst != 0) fst else xs1.compareTo(ys1) - } - } implicit def ListOrd[T: Ord]: Ord[List[T]] = new ListOrd[T] class given_Convertible_List_List[From, To](implicit c: Convertible[From, To]) - extends Convertible[List[From], List[To]] { + extends Convertible[List[From], List[To]] with def (x: List[From]) convert: List[To] = x.map(c.convert) - } implicit def given_Convertible_List_List[From, To](implicit c: Convertible[From, To]) : Convertible[List[From], List[To]] = new given_Convertible_List_List[From, To] @@ -238,35 +198,32 @@ object Implicits extends Common { (implicit cmp: Ord[T]): T = xs.reduceLeft((x, y) => if (x < y) y else x) - def descending[T](implicit asc: Ord[T]): Ord[T] = new Ord[T] { + def descending[T](implicit asc: Ord[T]): Ord[T] = new Ord[T] with def (x: T) compareTo (y: T) = asc.compareTo(y)(x) - } def minimum[T](xs: List[T])(implicit cmp: Ord[T]) = maximum(xs)(descending) -} -object Test extends App { +object Test extends App with Instances.test() import PostConditions.result import PostConditions.given val s = List(1, 2, 3).sum s.ensuring(result == 6) -} +end Test -object Completions { +object Completions with class Future[T] class HttpResponse class StatusCode // The argument "magnet" type - enum CompletionArg { + enum CompletionArg with case Error(s: String) case Response(f: Future[HttpResponse]) case Status(code: Future[StatusCode]) - } - object CompletionArg { + object CompletionArg with // conversions defining the possible arguments to pass to `complete` // these always come with CompletionArg @@ -277,12 +234,10 @@ object Completions { given fromString : Conversion[String, CompletionArg] = Error(_) given fromFuture : Conversion[Future[HttpResponse], CompletionArg] = Response(_) given fromStatusCode : Conversion[Future[StatusCode], CompletionArg] = Status(_) - } import CompletionArg._ - def complete[T](arg: CompletionArg) = arg match { + def complete[T](arg: CompletionArg) = arg match case Error(s) => ??? case Response(f) => ??? case Status(code) => ??? - } -} \ No newline at end of file +end Completions