diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 695795c878db..4b4e1784a85d 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1086,8 +1086,8 @@ object desugar { } def makeContextualFunction(formals: List[Type], body: Tree)(implicit ctx: Context): Tree = { - val params = makeImplicitParameters(formals.map(TypeTree), Contextual) - new FunctionWithMods(params, body, Modifiers(Implicit | Contextual)) + val params = makeImplicitParameters(formals.map(TypeTree), Given) + new FunctionWithMods(params, body, Modifiers(Implicit | Given)) } /** Add annotation to tree: diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index c10adfbc5cbf..e1b5a5c8997a 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -331,13 +331,13 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped] /** Is `tree` an implicit function or closure, possibly nested in a block? */ def isContextualClosure(tree: Tree)(implicit ctx: Context): Boolean = unsplice(tree) match { - case tree: FunctionWithMods => tree.mods.is(Contextual) - case Function((param: untpd.ValDef) :: _, _) => param.mods.is(Contextual) + case tree: FunctionWithMods => tree.mods.is(Given) + case Function((param: untpd.ValDef) :: _, _) => param.mods.is(Given) case Closure(_, meth, _) => true case Block(Nil, expr) => isContextualClosure(expr) case Block(DefDef(nme.ANON_FUN, _, params :: _, _, _) :: Nil, cl: Closure) => params match { - case param :: _ => param.mods.is(Contextual) + case param :: _ => param.mods.is(Given) case Nil => cl.tpt.eq(untpd.ContextualEmptyTree) || defn.isImplicitFunctionType(cl.tpt.typeOpt) } case _ => false diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index c180f514822e..e3d1127a08db 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -234,7 +234,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def valueParam(name: TermName, origInfo: Type): TermSymbol = { val maybeImplicit = - if (tp.isContextual) Implicit | Contextual + if (tp.isContextual) Implicit | Given else if (tp.isImplicitMethod) Implicit else EmptyFlags val maybeErased = if (tp.isErasedMethod) Erased else EmptyFlags diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 428c74c85960..a074281674c2 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -132,6 +132,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class Implicit()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.ImplicitCommon) + case class Given()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.ImplicitCommon | Flags.Given) + case class Erased()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Erased) case class Final()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Final) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 6f63373f064c..a031bb98a068 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -100,7 +100,7 @@ class Definitions { * ImplicitFunctionN traits follow this template: * * trait ImplicitFunctionN[T0,...,T{N-1}, R] extends Object { - * def apply with ($x0: T0, ..., $x{N_1}: T{N-1}): R + * def apply given ($x0: T0, ..., $x{N_1}: T{N-1}): R * } * * ErasedFunctionN traits follow this template: @@ -112,7 +112,7 @@ class Definitions { * ErasedImplicitFunctionN traits follow this template: * * trait ErasedImplicitFunctionN[T0,...,T{N-1}, R] extends Object { - * def apply with (erased $x0: T0, ..., $x{N_1}: T{N-1}): R + * def apply given (erased $x0: T0, ..., $x{N_1}: T{N-1}): R * } * * ErasedFunctionN and ErasedImplicitFunctionN erase to Function0. diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index f7bad7265047..0d6e42b3318a 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -347,8 +347,8 @@ object Flags { /** An extension method */ final val Extension = termFlag(28, "") - /** A contextual (with) parameter */ - final val Contextual = commonFlag(29, "") + /** An inferable (`given`) parameter */ + final val Given = commonFlag(29, "given") /** Symbol is defined by a Java class */ final val JavaDefined: FlagSet = commonFlag(30, "") diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 339d811f26d0..b0f3db0429aa 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -483,7 +483,6 @@ object StdNames { val notifyAll_ : N = "notifyAll" val notify_ : N = "notify" val null_ : N = "null" - val of: N = "of" val ofDim: N = "ofDim" val opaque: N = "opaque" val ordinal: N = "ordinal" diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index e246bc74f358..b6dab96c1583 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3879,11 +3879,11 @@ object Types { def selfType(implicit ctx: Context): Type = { if (selfTypeCache == null) selfTypeCache = { - val given = cls.givenSelfType - if (!given.isValueType) appliedRef - else if (cls is Module) given + val givenSelf = cls.givenSelfType + if (!givenSelf.isValueType) appliedRef + else if (cls is Module) givenSelf else if (ctx.erasedTypes) appliedRef - else AndType(given, appliedRef) + else AndType(givenSelf, appliedRef) } selfTypeCache } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index 9961d3b688ee..b794555fed5f 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -203,7 +203,7 @@ Standard-Section: "ASTs" TopLevelStat* DEFAULTparameterized // Method with default parameters STABLE // Method that is assumed to be stable EXTENSION // An extension method - CONTEXTUAL // new style implicit parameters, introduced with `with` + GIVEN // new style implicit parameters, introduced with `given` PARAMsetter // A setter without a body named `x_=` where `x` is pickled as a PARAM Annotation @@ -323,7 +323,7 @@ object TastyFormat { final val ERASED = 34 final val OPAQUE = 35 final val EXTENSION = 36 - final val CONTEXTUAL = 37 + final val GIVEN = 37 final val PARAMsetter = 38 // Cat. 2: tag Nat @@ -497,7 +497,7 @@ object TastyFormat { | DEFAULTparameterized | STABLE | EXTENSION - | CONTEXTUAL + | GIVEN | PARAMsetter | ANNOTATION | PRIVATEqualified @@ -558,7 +558,7 @@ object TastyFormat { case DEFAULTparameterized => "DEFAULTparameterized" case STABLE => "STABLE" case EXTENSION => "EXTENSION" - case CONTEXTUAL => "CONTEXTUAL" + case GIVEN => "GIVEN" case PARAMsetter => "PARAMsetter" case SHAREDterm => "SHAREDterm" diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index e0eaa47609f9..414646749b96 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -655,7 +655,7 @@ class TreePickler(pickler: TastyPickler) { if (flags is DefaultParameterized) writeByte(DEFAULTparameterized) if (flags is StableRealizable) writeByte(STABLE) if (flags is Extension) writeByte(EXTENSION) - if (flags is Contextual) writeByte(CONTEXTUAL) + if (flags is Given) writeByte(GIVEN) if (flags is ParamAccessor) writeByte(PARAMsetter) assert(!(flags is Label)) } else { diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index e442f9b00738..bdea7dcbe584 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -632,7 +632,7 @@ class TreeUnpickler(reader: TastyReader, case DEFAULTparameterized => addFlag(DefaultParameterized) case STABLE => addFlag(StableRealizable) case EXTENSION => addFlag(Extension) - case CONTEXTUAL => addFlag(Contextual) + case GIVEN => addFlag(Given) case PARAMsetter => addFlag(ParamAccessor) case PRIVATEqualified => diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 468e09204434..3ba8d0ebeed6 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -364,22 +364,22 @@ object Parsers { /** Convert tree to formal parameter list */ - def convertToParams(tree: Tree, mods: Modifiers): List[ValDef] = tree match { - case Parens(t) => convertToParam(t, mods) :: Nil - case Tuple(ts) => ts map (convertToParam(_, mods)) - case t => convertToParam(t, mods) :: Nil + def convertToParams(tree: Tree): List[ValDef] = tree match { + case Parens(t) => convertToParam(t) :: Nil + case Tuple(ts) => ts map (convertToParam(_)) + case t => convertToParam(t) :: Nil } /** Convert tree to formal parameter */ - def convertToParam(tree: Tree, mods: Modifiers, expected: String = "formal parameter"): ValDef = tree match { + def convertToParam(tree: Tree, expected: String = "formal parameter"): ValDef = tree match { case Ident(name) => - makeParameter(name.asTermName, TypeTree(), mods).withSpan(tree.span) + makeParameter(name.asTermName, TypeTree()).withSpan(tree.span) case Typed(Ident(name), tpt) => - makeParameter(name.asTermName, tpt, mods).withSpan(tree.span) + makeParameter(name.asTermName, tpt).withSpan(tree.span) case _ => syntaxError(s"not a legal $expected", tree.span) - makeParameter(nme.ERROR, tree, mods) + makeParameter(nme.ERROR, tree) } /** Convert (qual)ident to type identifier @@ -535,7 +535,7 @@ object Parsers { } else recur(operand()) } - else if (in.token == WITH) { + else if (in.token == GIVEN) { val top1 = reduceStack(base, top, minInfixPrec, leftAssoc = true, nme.WITHkw, isType) assert(opStack `eq` base) val app = atSpan(startOffset(top1), in.offset) { @@ -770,7 +770,7 @@ object Parsers { */ def toplevelTyp(): Tree = checkWildcard(typ()) - /** Type ::= [‘erased’] FunArgTypes (‘=>’ | ‘|=>’) Type + /** Type ::= FunTypeMods FunArgTypes `=>' Type * | HkTypeParamClause `->' Type * | InfixType * FunArgTypes ::= InfixType @@ -779,20 +779,11 @@ object Parsers { */ def typ(): Tree = { val start = in.offset - val imods = modifiers(BitSet(ERASED)) + val imods = modifiers(funTypeMods) def functionRest(params: List[Tree]): Tree = - atSpan(start, in.offset) { - val pmods = - if (in.token == CARROW) { - in.nextToken() - imods | (Contextual | Implicit) - } - else { - accept(ARROW) - imods - } + atSpan(start, accept(ARROW)) { val t = typ() - if (pmods.flags.is(Implicit | Contextual | Erased)) new FunctionWithMods(params, t, pmods) + if (imods.is(Implicit | Given | Erased)) new FunctionWithMods(params, t, imods) else Function(params, t) } def funArgTypesRest(first: Tree, following: () => Tree) = { @@ -826,7 +817,7 @@ object Parsers { } openParens.change(LPAREN, -1) accept(RPAREN) - if (imods.is(Implicit) || isValParamList || in.token == ARROW || in.token == CARROW) + if (imods.is(Implicit) || isValParamList || in.token == ARROW) functionRest(ts) else { val ts1 = @@ -858,7 +849,7 @@ object Parsers { else infixType() in.token match { - case ARROW | CARROW => functionRest(t :: Nil) + case ARROW => functionRest(t :: Nil) case MATCH => matchType(EmptyTree, t) case FORSOME => syntaxError(ExistentialTypesNoLongerSupported()); t case _ => @@ -1134,15 +1125,14 @@ object Parsers { } } - /** Expr ::= [FunArgMods] FunParams =>' Expr - * | [‘erased’] FunParams ‘|=>’ Expr + /** Expr ::= [ClosureMods] FunParams =>' Expr * | Expr1 * FunParams ::= Bindings * | id * | `_' * ExprInParens ::= PostfixExpr `:' Type * | Expr - * BlockResult ::= [FunArgMods] FunParams =>' Block + * BlockResult ::= [ClosureMods] FunParams =>' Block * | Expr1 * Expr1 ::= [‘inline’] `if' `(' Expr `)' {nl} Expr [[semi] else Expr] * | [‘inline’] `if' Expr `then' Expr [[semi] else Expr] @@ -1171,8 +1161,8 @@ object Parsers { def expr(location: Location.Value): Tree = { val start = in.offset - if (in.token == IMPLICIT || in.token == ERASED) { - val imods = modifiers(funArgMods) + if (in.token == IMPLICIT || in.token == ERASED || in.token == GIVEN) { + val imods = modifiers(closureMods) if (in.token == MATCH) implicitMatch(start, imods) else implicitClosure(start, location, imods) } else { @@ -1185,11 +1175,9 @@ object Parsers { finally placeholderParams = saved val t = expr1(location) - if (in.token == ARROW || in.token == CARROW) { + if (in.token == ARROW) { placeholderParams = Nil // don't interpret `_' to the left of `=>` as placeholder - val impliedMods = - if (in.token == CARROW) Modifiers(Implicit | Contextual) else EmptyModifiers - wrapPlaceholders(closureRest(start, location, convertToParams(t, impliedMods))) + wrapPlaceholders(closureRest(start, location, convertToParams(t))) } else if (isWildcard(t)) { placeholderParams = placeholderParams ::: saved @@ -1407,8 +1395,7 @@ object Parsers { } else ident() - /** Expr ::= FunArgMods FunParams `=>' Expr - * | [‘erased’] FunParams ‘|=>’ Expr + /** Expr ::= ClosureMods FunParams `=>' Expr * BlockResult ::= implicit id [`:' InfixType] `=>' Block // Scala2 only */ def implicitClosure(start: Int, location: Location.Value, implicitMods: Modifiers): Tree = @@ -1416,19 +1403,8 @@ object Parsers { def closureRest(start: Int, location: Location.Value, params: List[Tree]): Tree = atSpan(start, in.offset) { - val params1 = - if (in.token == CARROW) { - in.nextToken() - params.map { - case param: ValDef => param.withMods(param.mods | (Implicit | Contextual)) - case param => param - } - } - else { - accept(ARROW) - params - } - Function(params1, if (location == Location.InBlock) block() else expr()) + accept(ARROW) + Function(params, if (location == Location.InBlock) block() else expr()) } /** PostfixExpr ::= InfixExpr [id [nl]] @@ -1853,6 +1829,7 @@ object Parsers { case ABSTRACT => Mod.Abstract() case FINAL => Mod.Final() case IMPLICIT => Mod.Implicit() + case GIVEN => Mod.Given() case ERASED => Mod.Erased() case LAZY => Mod.Lazy() case OVERRIDE => Mod.Override() @@ -1949,9 +1926,13 @@ object Parsers { normalize(loop(start)) } - /** FunArgMods ::= { `implicit` | `erased` } + /** FunArgMods ::= { `implicit` | `erased` } + * ClosureMods ::= { ‘implicit’ | ‘erased’ | ‘given’} + * FunTypeMods ::= { ‘erased’ | ‘given’} */ - def funArgMods: BitSet = BitSet(IMPLICIT, ERASED) + val funArgMods: BitSet = BitSet(IMPLICIT, ERASED) + val closureMods: BitSet = BitSet(GIVEN, IMPLICIT, ERASED) + val funTypeMods: BitSet = BitSet(GIVEN, ERASED) /** Wrap annotation or constructor in New(...). */ def wrapNew(tpt: Tree): Select = Select(New(tpt), nme.CONSTRUCTOR) @@ -2037,8 +2018,8 @@ object Parsers { /** ClsParamClause ::= [nl | ‘with’] `(' [FunArgMods] [ClsParams] ')' * ClsParams ::= ClsParam {`' ClsParam} * ClsParam ::= {Annotation} [{Modifier} (`val' | `var') | `inline'] Param - * DefParamClause ::= [nl] `(' [FunArgMods] [DefParams] ')' | InstParamClause - * InstParamClause ::= ‘with’ (‘(’ [DefParams] ‘)’ | ContextTypes) + * DefParamClause ::= [nl] `(' [FunArgMods] [DefParams] ')' | InferParamClause + * InferParamClause ::= ‘given’ (‘(’ DefParams ‘)’ | ContextTypes) * ContextTypes ::= RefinedType {`,' RefinedType} * DefParams ::= DefParam {`,' DefParam} * DefParam ::= {Annotation} [`inline'] Param @@ -2109,10 +2090,11 @@ object Parsers { // begin paramClause inParens { - if (in.token == RPAREN && !prefix) Nil + val isContextual = impliedMods.is(Given) + if (in.token == RPAREN && !prefix && !isContextual) Nil else { def funArgMods(mods: Modifiers): Modifiers = - if (in.token == IMPLICIT && !mods.is(Contextual)) + if (in.token == IMPLICIT && !isContextual) funArgMods(addMod(mods, atSpan(accept(IMPLICIT)) { Mod.Implicit() })) else if (in.token == ERASED) funArgMods(addMod(mods, atSpan(accept(ERASED)) { Mod.Erased() })) @@ -2130,7 +2112,7 @@ object Parsers { /** ClsParamClauses ::= {ClsParamClause} * DefParamClauses ::= {DefParamClause} - * InstParamClauses ::= {InstParamClause} + * InferParamClauses ::= {InferParamClause} * * @return The parameter definitions */ @@ -2139,30 +2121,30 @@ object Parsers { ofInstance: Boolean = false): List[List[ValDef]] = { def recur(firstClause: Boolean, nparams: Int): List[List[ValDef]] = { val initialMods = - if (in.token == WITH) { + if (in.token == GIVEN) { in.nextToken() - Modifiers(Contextual | Implicit) + Modifiers(Given | Implicit) } else EmptyModifiers - val isContextual = initialMods.is(Contextual) + val isContextual = initialMods.is(Given) newLineOptWhenFollowedBy(LPAREN) if (in.token == LPAREN) { if (ofInstance && !isContextual) - syntaxError(em"parameters of instance definitions must come after `with'") + syntaxError(em"parameters of instance definitions must come after `given'") val params = paramClause( ofClass = ofClass, ofCaseClass = ofCaseClass, firstClause = firstClause, initialMods = initialMods) val lastClause = - params.nonEmpty && params.head.mods.flags.is(Implicit, butNot = Contextual) + params.nonEmpty && params.head.mods.flags.is(Implicit, butNot = Given) params :: (if (lastClause) Nil else recur(firstClause = false, nparams + params.length)) } else if (isContextual) { val tps = commaSeparated(refinedType) var counter = nparams def nextIdx = { counter += 1; counter } - val params = tps.map(makeSyntheticParameter(nextIdx, _, Contextual | Implicit)) + val params = tps.map(makeSyntheticParameter(nextIdx, _, Given | Implicit)) params :: recur(firstClause = false, nparams + params.length) } else Nil @@ -2456,7 +2438,7 @@ object Parsers { objectDef(start, posMods(start, mods | Case | Module)) case ENUM => enumDef(start, mods, atSpan(in.skipToken()) { Mod.Enum() }) - case INSTANCE => + case IMPLIED => instanceDef(start, mods, atSpan(in.skipToken()) { Mod.Instance() }) case _ => syntaxErrorOrIncomplete(ExpectedStartOfTopLevelDefinition()) @@ -2554,17 +2536,17 @@ object Parsers { } /** InstanceDef ::= [id] InstanceParams InstanceBody - * InstanceParams ::= [DefTypeParamClause] {InstParamClause} + * InstanceParams ::= [DefTypeParamClause] {InferParamClause} * InstanceBody ::= [‘of’ ConstrApp {‘,’ ConstrApp }] [TemplateBody] * | ‘of’ Type ‘=’ Expr */ def instanceDef(start: Offset, mods: Modifiers, instanceMod: Mod) = atSpan(start, nameStart) { var mods1 = addMod(mods, instanceMod) - val name = if (isIdent && !isIdent(nme.of)) ident() else EmptyTermName + val name = if (isIdent) ident() else EmptyTermName val tparams = typeParamClauseOpt(ParamOwner.Def) val vparamss = paramClauses(ofInstance = true) val parents = - if (isIdent(nme.of)) { + if (in.token == FOR) { in.nextToken() tokenSeparated(COMMA, constrApp) } @@ -2747,7 +2729,7 @@ object Parsers { case Typed(tree @ This(EmptyTypeIdent), tpt) => self = makeSelfDef(nme.WILDCARD, tpt).withSpan(first.span) case _ => - val ValDef(name, tpt, _) = convertToParam(first, EmptyModifiers, "self type clause") + val ValDef(name, tpt, _) = convertToParam(first, "self type clause") if (name != nme.ERROR) self = makeSelfDef(name, tpt).withSpan(first.span) } @@ -2834,12 +2816,14 @@ object Parsers { setLastStatOffset() if (in.token == IMPORT) stats ++= importClause() + else if (in.token == GIVEN) + stats += implicitClosure(in.offset, Location.InBlock, modifiers(closureMods)) else if (isExprIntro) stats += expr(Location.InBlock) else if (isDefIntro(localModifierTokens)) - if (in.token == IMPLICIT || in.token == ERASED) { + if (in.token == IMPLICIT || in.token == ERASED || in.token == GIVEN) { val start = in.offset - var imods = modifiers(funArgMods) + var imods = modifiers(closureMods) if (isBindingIntro) stats += implicitClosure(start, Location.InBlock, imods) else if (in.token == MATCH) diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index ec5f0bcf4181..2ea56b46cff3 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -205,11 +205,9 @@ object Scanners { private def handleMigration(keyword: Token): Token = if (!isScala2Mode) keyword - else if ( keyword == ENUM - || keyword == ERASED) treatAsIdent() + else if (scala3keywords.contains(keyword)) treatAsIdent() else keyword - private def treatAsIdent() = { testScala2Mode(i"$name is now a keyword, write `$name` instead of $name to keep it as an identifier") patch(source, Span(offset), "`") @@ -407,6 +405,7 @@ object Scanners { */ protected final def fetchToken(): Unit = { offset = charOffset - 1 + name = null (ch: @switch) match { case ' ' | '\t' | CR | LF | FF => nextChar() diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index 06e0118daf59..20dcf1b7e152 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -112,8 +112,7 @@ abstract class TokensCommon { //final val SUPERTYPE = 81; enter(SUPERTYPE, ">:") //final val HASH = 82; enter(HASH, "#") final val AT = 83; enter(AT, "@") - //final val CARROW = 84; - //final val VIEWBOUND = 85; enter(VIEWBOUND, "<%") // TODO: deprecate + //final val VIEWBOUND = 84; enter(VIEWBOUND, "<%") // TODO: deprecate val keywords: TokenSet @@ -179,7 +178,8 @@ object Tokens extends TokensCommon { final val FORSOME = 61; enter(FORSOME, "forSome") // TODO: deprecate final val ENUM = 62; enter(ENUM, "enum") final val ERASED = 63; enter(ERASED, "erased") - final val INSTANCE = 64; enter(INSTANCE, "instance") + final val IMPLIED = 64; enter(IMPLIED, "implied") + final val GIVEN = 65; enter(GIVEN, "given") /** special symbols */ final val NEWLINE = 78; enter(NEWLINE, "end of statement", "new line") @@ -192,16 +192,15 @@ object Tokens extends TokensCommon { final val SUBTYPE = 80; enter(SUBTYPE, "<:") final val SUPERTYPE = 81; enter(SUPERTYPE, ">:") final val HASH = 82; enter(HASH, "#") - final val CARROW = 84; enter(CARROW, "|=>") - final val VIEWBOUND = 85; enter(VIEWBOUND, "<%") // TODO: deprecate - final val QPAREN = 86; enter(QPAREN, "'(") - final val QBRACE = 87; enter(QBRACE, "'{") - final val QBRACKET = 88; enter(QBRACKET, "'[") + final val VIEWBOUND = 84; enter(VIEWBOUND, "<%") // TODO: deprecate + final val QPAREN = 85; enter(QPAREN, "'(") + final val QBRACE = 86; enter(QBRACE, "'{") + final val QBRACKET = 87; enter(QBRACKET, "'[") /** XML mode */ final val XMLSTART = 96; enter(XMLSTART, "$XMLSTART$<") // TODO: deprecate - final val alphaKeywords: TokenSet = tokenRange(IF, INSTANCE) + final val alphaKeywords: TokenSet = tokenRange(IF, GIVEN) final val symbolicKeywords: TokenSet = tokenRange(USCORE, VIEWBOUND) final val symbolicTokens: TokenSet = tokenRange(COMMA, VIEWBOUND) final val keywords: TokenSet = alphaKeywords | symbolicKeywords @@ -224,7 +223,7 @@ object Tokens extends TokensCommon { final val templateIntroTokens: TokenSet = BitSet(CLASS, TRAIT, OBJECT, ENUM, CASECLASS, CASEOBJECT) - final val dclIntroTokens: TokenSet = BitSet(DEF, VAL, VAR, TYPE, INSTANCE) + final val dclIntroTokens: TokenSet = BitSet(DEF, VAL, VAR, TYPE, IMPLIED) final val defIntroTokens: TokenSet = templateIntroTokens | dclIntroTokens @@ -252,5 +251,7 @@ object Tokens extends TokensCommon { final val numericLitTokens: TokenSet = BitSet(INTLIT, LONGLIT, FLOATLIT, DOUBLELIT) + final val scala3keywords = BitSet(ENUM, ERASED, GIVEN, IMPLIED) + final val softModifierNames = Set(nme.inline, nme.opaque) } diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index c5d338cd1553..6ad13a74f973 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -125,13 +125,11 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { else simpleNameString(tsym) } - protected def arrowText(contextual: Boolean): Text = if (contextual) " |=> " else " => " - override def toText(tp: Type): Text = controlled { def toTextTuple(args: List[Type]): Text = "(" ~ argsText(args) ~ ")" - def toTextFunction(args: List[Type], contextual: Boolean, isErased: Boolean): Text = + def toTextFunction(args: List[Type], isContextual: Boolean, isErased: Boolean): Text = changePrec(GlobalPrec) { val argStr: Text = if (args.length == 2 && !defn.isTupleType(args.head)) @@ -139,11 +137,14 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { else toTextTuple(args.init) (keywordText("erased ") provided isErased) ~ - argStr ~ arrowText(contextual) ~ argText(args.last) + (keywordText("given ") provided isContextual) ~ + argStr ~ " => " ~ argText(args.last) } def toTextDependentFunction(appType: MethodType): Text = - "(" ~ paramsText(appType) ~ ")" ~ arrowText(appType.isContextual) ~ toText(appType.resultType) + (keywordText("erased ") provided appType.isErasedMethod) ~ + (keywordText("given ") provided appType.isImplicitMethod) ~ + "(" ~ paramsText(appType) ~ ") => " ~ toText(appType.resultType) def isInfixType(tp: Type): Boolean = tp match { case AppliedType(tycon, args) => @@ -519,7 +520,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { def argToText(arg: Tree) = arg match { case arg @ ValDef(name, tpt, _) => val implicitText = - if ((arg.mods is Contextual)) { contextual = true; "" } + if ((arg.mods is Given)) { contextual = true; "" } else if ((arg.mods is Implicit) && !implicitSeen) { implicitSeen = true; keywordStr("implicit ") } else "" implicitText ~ toText(name) ~ optAscription(tpt) @@ -530,7 +531,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case (arg @ ValDef(_, tpt, _)) :: Nil if tpt.isEmpty => argToText(arg) case _ => "(" ~ Text(args map argToText, ", ") ~ ")" } - changePrec(GlobalPrec) { argsText ~ arrowText(contextual) ~ toText(body) } + changePrec(GlobalPrec) { + (keywordText("given ") provided contextual) ~ + argsText ~ " => " ~ toText(body) + } case InfixOp(l, op, r) => val opPrec = parsing.precedence(op.name) changePrec(opPrec) { toText(l) ~ " " ~ toText(op) ~ " " ~ toText(r) } diff --git a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala index 54c3a8ada151..f217388697f2 100644 --- a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala +++ b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala @@ -178,10 +178,10 @@ object EtaExpansion extends LiftImpure { * { val xs = es; (x1: T1, ..., xn: Tn) => expr(x1, ..., xn) _ } * * where `T1, ..., Tn` are the paremeter types of the expanded method. - * If `expr` has a contectual function type, the arguments are passed with `with`. + * If `expr` has implicit function type, the arguments are passed with `given`. * E.g. for (1): * - * { val xs = es; (x1, ..., xn) => expr with (x1, ..., xn) } + * { val xs = es; (x1, ..., xn) => expr given (x1, ..., xn) } * * Case (3) applies if the method is curried, i.e. its result type is again a method * type. Case (2) applies if the expected arity of the function type `xarity` differs @@ -210,7 +210,7 @@ object EtaExpansion extends LiftImpure { if (isLastApplication && mt.paramInfos.length == xarity) mt.paramInfos map (_ => TypeTree()) else mt.paramInfos map TypeTree var paramFlag = Synthetic | Param - if (mt.isContextual) paramFlag |= Contextual + if (mt.isContextual) paramFlag |= Given if (mt.isImplicitMethod) paramFlag |= Implicit val params = (mt.paramNames, paramTypes).zipped.map((name, tpe) => ValDef(name, tpe, EmptyTree).withFlags(paramFlag).withSpan(tree.span.startPos)) @@ -221,7 +221,7 @@ object EtaExpansion extends LiftImpure { if (mt.isContextual) body.pushAttachment(WithApply, ()) if (!isLastApplication) body = PostfixOp(body, Ident(nme.WILDCARD)) val fn = - if (mt.isContextual) new untpd.FunctionWithMods(params, body, Modifiers(Implicit | Contextual)) + if (mt.isContextual) new untpd.FunctionWithMods(params, body, Modifiers(Implicit | Given)) else if (mt.isImplicitMethod) new untpd.FunctionWithMods(params, body, Modifiers(Implicit)) else untpd.Function(params, body) if (defs.nonEmpty) untpd.Block(defs.toList map (untpd.TypedSplice(_)), fn) else fn diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index aaf0f2147559..2b22f6d3c7db 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -118,9 +118,9 @@ trait NamerContextOps { this: Context => /** The given type, unless `sym` is a constructor, in which case the * type of the constructed instance is returned */ - def effectiveResultType(sym: Symbol, typeParams: List[Symbol], given: Type): Type = + def effectiveResultType(sym: Symbol, typeParams: List[Symbol], givenTp: Type): Type = if (sym.name == nme.CONSTRUCTOR) sym.owner.typeRef.appliedTo(typeParams.map(_.typeRef)) - else given + else givenTp /** if isConstructor, make sure it has one non-implicit parameter list */ def normalizeIfConstructor(termParamss: List[List[Symbol]], isConstructor: Boolean): List[List[Symbol]] = @@ -131,13 +131,12 @@ trait NamerContextOps { this: Context => termParamss /** The method type corresponding to given parameters and result type */ - def methodType(typeParams: List[Symbol], valueParamss: List[List[Symbol]], resultType: Type, - isJava: Boolean = false, isInstance: Boolean = false)(implicit ctx: Context): Type = { + def methodType(typeParams: List[Symbol], valueParamss: List[List[Symbol]], resultType: Type, isJava: Boolean = false)(implicit ctx: Context): Type = { val monotpe = (valueParamss :\ resultType) { (params, resultType) => val (isImplicit, isErased, isContextual) = - if (params.isEmpty) (isInstance, false, false) - else (params.head is Implicit, params.head is Erased, params.head.is(Contextual)) + if (params.isEmpty) (false, false, false) + else (params.head is Implicit, params.head is Erased, params.head.is(Given)) val make = MethodType.maker(isJava = isJava, isImplicit = isImplicit, isErased = isErased, isContextual = isContextual) if (isJava) for (param <- params) @@ -1301,8 +1300,7 @@ class Namer { typer: Typer => val termParamss = ctx.normalizeIfConstructor(vparamss.nestedMap(symbolOfTree), isConstructor) def wrapMethType(restpe: Type): Type = { instantiateDependent(restpe, typeParams, termParamss) - ctx.methodType(tparams map symbolOfTree, termParamss, restpe, - isJava = ddef.mods is JavaDefined, isInstance = ddef.mods.hasMod(classOf[Mod.Instance])) + ctx.methodType(tparams map symbolOfTree, termParamss, restpe, isJava = ddef.mods is JavaDefined) } if (isConstructor) { // set result type tree to unit, but take the current class as result type of the symbol diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index c792f939aaf4..e72d8dcc471c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -791,7 +791,7 @@ class Typer extends Namer } val funCls = defn.FunctionClass(args.length, - isContextual = funFlags.is(Contextual), isErased = funFlags.is(Erased)) + isContextual = funFlags.is(Given), isErased = funFlags.is(Erased)) /** Typechecks dependent function type with given parameters `params` */ def typedDependent(params: List[ValDef])(implicit ctx: Context): Tree = { @@ -801,7 +801,7 @@ class Typer extends Namer params1.foreach(_.symbol.setFlag(funFlags)) val resultTpt = typed(body) val companion = MethodType.maker( - isContextual = funFlags.is(Contextual), isErased = funFlags.is(Erased)) + isContextual = funFlags.is(Given), isErased = funFlags.is(Erased)) val mt = companion.fromSymbols(params1.map(_.symbol), resultTpt.tpe) if (mt.isParamDependent) ctx.error(i"$mt is an illegal function type because it has inter-parameter dependencies", tree.sourcePos) @@ -829,7 +829,7 @@ class Typer extends Namer val untpd.Function(params: List[untpd.ValDef] @unchecked, body) = tree val isContextual = tree match { - case tree: untpd.FunctionWithMods => tree.mods.is(Contextual) + case tree: untpd.FunctionWithMods => tree.mods.is(Given) case _ => false } @@ -2536,8 +2536,9 @@ class Typer extends Namer ctx.warning(ex"${tree.symbol} is eta-expanded even though $pt does not have the @FunctionalInterface annotation.", tree.sourcePos) case _ => } - simplify(typed(etaExpand(tree, wtp, arity), pt), pt, locked) - } else if (wtp.paramInfos.isEmpty && isAutoApplied(tree.symbol)) + simplify(typed(etaExpand(tree, wtp, arity), pt), pt, locked) + } + else if (wtp.paramInfos.isEmpty && isAutoApplied(tree.symbol)) readaptSimplified(tpd.Apply(tree, Nil)) else if (wtp.isImplicitMethod) err.typeMismatch(tree, pt) @@ -2718,11 +2719,11 @@ class Typer extends Namer else err.typeMismatch(tree, pt, failure) if (ctx.mode.is(Mode.ImplicitsEnabled) && tree.typeOpt.isValueType) inferView(tree, pt) match { - case SearchSuccess(inferred: ExtMethodApply, _, _) => - inferred // nothing to check or adapt for extension method applications - case SearchSuccess(inferred, _, _) => - checkImplicitConversionUseOK(inferred.symbol, tree.posd) - readapt(inferred)(ctx.retractMode(Mode.ImplicitsEnabled)) + case SearchSuccess(found: ExtMethodApply, _, _) => + found // nothing to check or adapt for extension method applications + case SearchSuccess(found, _, _) => + checkImplicitConversionUseOK(found.symbol, tree.posd) + readapt(found)(ctx.retractMode(Mode.ImplicitsEnabled)) case failure: SearchFailure => if (pt.isInstanceOf[ProtoType] && !failure.isAmbiguous) // don't report the failure but return the tree unchanged. This diff --git a/compiler/test-resources/repl/3932 b/compiler/test-resources/repl/3932 index 4a3e16000756..2751fa1fee7f 100644 --- a/compiler/test-resources/repl/3932 +++ b/compiler/test-resources/repl/3932 @@ -1,2 +1,2 @@ -scala> def fun[T](x: T): List[T] |=> Int = ??? -def fun[T](x: T): List[T] |=> Int +scala> def fun[T](x: T): given List[T] => Int = ??? +def fun[T](x: T): given List[T] => Int diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index b399d22cc476..389b1d85922c 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -92,10 +92,11 @@ semi ::= ‘;’ | nl {nl} ``` abstract case catch class def do else enum -erased extends false final finally for if implicit -import lazy match new null object package private -protected override return super sealed then throw trait -true try type val var while with yield +erased extends false final finally for given if +implicit import instance lazy match new null object +package private protected override return super sealed then +throw trait true try type val var while +with yield : = <- => <: :> # @ ``` @@ -136,11 +137,10 @@ ClassQualifier ::= ‘[’ id ‘]’ ### Types ```ebnf -Type ::= [‘erased’] FunArgTypes (‘=>’ | ‘|=>’) Type Function(ts, t) +Type ::= { ‘erased’ | ‘given’} FunArgTypes ‘=>’ Type Function(ts, t) | HkTypeParamClause ‘=>’ Type TypeLambda(ps, t) | MatchType | InfixType -FunArgMods ::= { ‘implicit’ | ‘erased’ } FunArgTypes ::= InfixType | ‘(’ [ FunArgType {‘,’ FunArgType } ] ‘)’ | ‘(’ TypedFunParam {‘,’ TypedFunParam } ‘)’ @@ -156,7 +156,7 @@ SimpleType ::= SimpleType TypeArgs | [‘-’ | ‘+’ | ‘~’ | ‘!’] StableId PrefixOp(expr, op) | Path ‘.’ ‘type’ SingletonTypeTree(p) | ‘(’ ArgTypes ‘)’ Tuple(ts) - | ‘_’ TypeBounds + | ‘_’ SubtypeBounds | Refinement RefinedTypeTree(EmptyTree, refinement) | SimpleLiteral SingletonTypeTree(l) ArgTypes ::= Type {‘,’ Type} @@ -169,16 +169,15 @@ TypeArgs ::= ‘[’ ArgTypes ‘]’ NamedTypeArg ::= id ‘=’ Type NamedArg(id, t) NamedTypeArgs ::= ‘[’ NamedTypeArg {‘,’ NamedTypeArg} ‘]’ nts Refinement ::= ‘{’ [RefineDcl] {semi [RefineDcl]} ‘}’ ds -TypeBounds ::= [‘>:’ Type] [‘<:’ Type] | INT TypeBoundsTree(lo, hi) -TypeParamBounds ::= TypeBounds {‘<%’ Type} {‘:’ Type} ContextBounds(typeBounds, tps) +SubtypeBounds ::= [‘>:’ Type] [‘<:’ Type] | INT TypeBoundsTree(lo, hi) +TypeParamBounds ::= SubtypeBounds {‘<%’ Type} {‘:’ Type} ContextBounds(typeBounds, tps) ``` ### Expressions ```ebnf -Expr ::= [FunArgMods] FunParams ‘=>’ Expr Function(args, expr), Function(ValDef([implicit], id, TypeTree(), EmptyTree), expr) - | [‘erased’] FunParams ‘|=>’ Expr +Expr ::= [ClosureMods] FunParams ‘=>’ Expr Function(args, expr), Function(ValDef([implicit], id, TypeTree(), EmptyTree), expr) | Expr1 -BlockResult ::= [FunArgMods] FunParams ‘=>’ Block +BlockResult ::= [ClosureMods] FunParams ‘=>’ Block | Expr1 FunParams ::= Bindings | id @@ -205,7 +204,7 @@ Catches ::= ‘catch’ Expr PostfixExpr ::= InfixExpr [id] PostfixOp(expr, op) InfixExpr ::= PrefixExpr | InfixExpr id [nl] InfixExpr InfixOp(expr, op, expr) - | InfixExpr ‘with’ (InfixExpr | ParArgumentExprs) + | InfixExpr ‘given’ (InfixExpr | ParArgumentExprs) PrefixExpr ::= [‘-’ | ‘+’ | ‘~’ | ‘!’] SimpleExpr PrefixOp(expr, op) SimpleExpr ::= ‘new’ (ConstrApp [TemplateBody] | TemplateBody) New(constr | templ) | BlockExpr @@ -282,15 +281,15 @@ DefTypeParamClause::= ‘[’ DefTypeParam {‘,’ DefTypeParam} ‘]’ DefTypeParam ::= {Annotation} id [HkTypeParamClause] TypeParamBounds TypTypeParamClause::= ‘[’ TypTypeParam {‘,’ TypTypeParam} ‘]’ -TypTypeParam ::= {Annotation} id [HkTypeParamClause] TypeBounds +TypTypeParam ::= {Annotation} id [HkTypeParamClause] SubtypeBounds HkTypeParamClause ::= ‘[’ HkTypeParam {‘,’ HkTypeParam} ‘]’ HkTypeParam ::= {Annotation} [‘+’ | ‘-’] (Id[HkTypeParamClause] | ‘_’) - TypeBounds + SubtypeBounds ClsParamClauses ::= {ClsParamClause} ClsParamClause ::= [nl] ‘(’ [[FunArgMods] ClsParams] ‘)’ - | ‘with’ (‘(’ ([[FunArgMods] ClsParams] ‘)’ | ContextTypes) + | ‘given’ (‘(’ ([[FunArgMods] ClsParams] ‘)’ | ContextTypes) ClsParams ::= ClsParam {‘,’ ClsParam} ClsParam ::= {Annotation} ValDef(mods, id, tpe, expr) -- point of mods on val/var [{Modifier} (‘val’ | ‘var’) | ‘inline’] Param @@ -298,11 +297,13 @@ Param ::= id ‘:’ ParamType [‘=’ Expr] | INT DefParamClauses ::= {DefParamClause} [[nl] ‘(’ [FunArgMods] DefParams ‘)’] -DefParamClause ::= [nl] ‘(’ [DefParams] ‘)’ | InstParamClause -InstParamClause ::= ‘with’ (‘(’ [DefParams] ‘)’ | ContextTypes) +DefParamClause ::= [nl] ‘(’ [DefParams] ‘)’ | InferParamClause +InferParamClause ::= ‘given’ (‘(’ [DefParams] ‘)’ | ContextTypes) DefParams ::= DefParam {‘,’ DefParam} DefParam ::= {Annotation} [‘inline’] Param ValDef(mods, id, tpe, expr) -- point of mods at id. ContextTypes ::= RefinedType {‘,’ RefinedType} +FunArgMods ::= { ‘implicit’ | ‘erased’ } +ClosureMods ::= { ‘implicit’ | ‘erased’ | ‘given’} ``` ### Bindings and Imports @@ -345,7 +346,7 @@ VarDcl ::= ids ‘:’ Type DefDcl ::= DefSig [‘:’ Type] DefDef(_, name, tparams, vparamss, tpe, EmptyTree) DefSig ::= ‘(’ DefParam ‘)’ [nl] id [DefTypeParamClause] DefParamClauses -TypeDcl ::= id [TypeParamClause] (TypeBounds | ‘=’ Type) TypeDefTree(_, name, tparams, bounds) +TypeDcl ::= id [TypeParamClause] (SubtypeBounds | ‘=’ Type) TypeDefTree(_, name, tparams, bounds) | id [TypeParamClause] <: Type = MatchType Def ::= ‘val’ PatDef @@ -373,7 +374,7 @@ ConstrMods ::= {Annotation} [AccessModifier] ObjectDef ::= id [Template] ModuleDef(mods, name, template) // no constructor EnumDef ::= id ClassConstr InheritClauses EnumBody EnumDef(mods, name, tparams, template) InstanceDef ::= [id] InstanceParams InstanceBody -InstanceParams ::= [DefTypeParamClause] {InstParamClause} +InstanceParams ::= [DefTypeParamClause] {InferParamClause} InstanceBody ::= [‘of’ ConstrApp {‘,’ ConstrApp }] [TemplateBody] | ‘of’ Type ‘=’ Expr Template ::= InheritClauses [TemplateBody] Template(constr, parents, self, stats) diff --git a/docs/docs/reference/changed-features/eta-expansion-spec.md b/docs/docs/reference/changed-features/eta-expansion-spec.md index c3d1a8001150..f5c83e140d9e 100644 --- a/docs/docs/reference/changed-features/eta-expansion-spec.md +++ b/docs/docs/reference/changed-features/eta-expansion-spec.md @@ -48,14 +48,13 @@ implicit val bla: Double = 1.0 val bar = foo // val bar: Int => Float = ... ``` -## Automatic Eta-Expansion and implicit function types +## Automatic Eta-Expansion and query types -Methods with implicit parameter lists can be assigned to a value with an implicit function type -only by using the expected type explicitly. +A method with inferable parameters can be expanded to a value of query type by writing the expected query type explicitly. ```scala -def foo(x: Int)(implicit p: Double): Float = ??? -val bar: implicit Double => Float = foo(3) // val bar: implicit Double => Float = ... +def foo(x: Int) given (p: Double): Float = ??? +val bar: given Double => Float = foo(3) // val bar: given Double => Float = ... ``` ## Rules diff --git a/docs/docs/reference/contextual/context-bounds.md b/docs/docs/reference/contextual/context-bounds.md new file mode 100644 index 000000000000..3458c5cf6cd1 --- /dev/null +++ b/docs/docs/reference/contextual/context-bounds.md @@ -0,0 +1,30 @@ +--- +layout: doc-page +title: "Context Bounds" +--- + +## Context Bounds + +A context bound is a shorthand for expressing a common pattern of an inferable parameter that depends on a type parameter. Using a context bound, the `maximum` function of the last section can be written like this: +```scala +def maximum[T: Ord](xs: List[T]): T = xs.reduceLeft(max) +``` +A bound like `: Ord` on a type parameter `T` of a method or class indicates an inferable parameter `given Ord[T]`. The inferable parameter(s) generated from context bounds come last in the definition of the containing method or class. E.g., +```scala +def f[T: C1 : C2, U: C3](x: T) given (y: U, z: V): R +``` +would expand to +```scala +def f[T, U](x: T) given (y: U, z: V) given C1[T], C2[T], C3[U]: R +``` +Context bounds can be combined with subtype bounds. If both are present, subtype bounds come first, e.g. +```scala +def g[T <: B : C](x: T): R = ... +``` + +## Syntax + +``` +TypeParamBounds ::= [SubtypeBounds] {ContextBound} +ContextBound ::= ‘:’ Type +``` diff --git a/docs/docs/reference/contextual/conversions.md b/docs/docs/reference/contextual/conversions.md new file mode 100644 index 000000000000..e838964a5144 --- /dev/null +++ b/docs/docs/reference/contextual/conversions.md @@ -0,0 +1,78 @@ +--- +layout: doc-page +title: "Implicit Conversions" +--- + +Implicit conversions are defined by implied instances of the `scala.Conversion` class. +This class is defined in package `scala` as follows: +```scala +abstract class Conversion[-T, +U] extends (T => U) +``` +For example, here is an implicit conversion from `String` to `Token`: +```scala +implied for Conversion[String, Token] { + def apply(str: String): Token = new KeyWord(str) +} +``` +An implicit conversion is applied automatically by the compiler in three situations: + +1. If an expression `e` has type `T`, and `T` does not conform to the expression's expected type `S`. +2. In a selection `e.m` with `e` of type `T`, but `T` defines no member `m`. +3. In an application `e.m(args)` with `e` of type `T`, if `T` does define + some member(s) named `m`, but none of these members can be applied to the arguments `args`. + +In the first case, the compiler looks in the implied scope for a an instance of +`scala.Conversion` that maps an argument of type `T` to type `S`. In the second and third +case, it looks for an instance of `scala.Conversion` that maps an argument of type `T` +to a type that defines a member `m` which can be applied to `args` if present. +If such an instance `C` is found, the expression `e` is replaced by `C.apply(e)`. + +## Examples + +1. The `Predef` package contains "auto-boxing" conversions that map +primitive number types to subclasses of `java.lang.Number`. For instance, the +conversion from `Int` to `java.lang.Integer` can be defined as follows: +```scala +implied int2Integer for Conversion[Int, java.lang.Integer] { + def apply(x: Int) = new java.lang.Integer(x) +} +``` + +2. The "magnet" pattern is sometimes used to express many variants of a method. Instead of defining overloaded versions of the method, one can also let the method take one or more arguments of specially defined "magnet" types, into which various argument types can be converted. E.g. +```scala +object Completions { + + // The argument "magnet" type + enum CompletionArg { + case Error(s: String) + case Response(f: Future[HttpResponse]) + case Status(code: Future[StatusCode]) + } + object CompletionArg { + + // conversions defining the possible arguments to pass to `complete` + // these always come with CompletionArg + // They can be invoked explicitly, e.g. + // + // CompletionArg.from(statusCode) + + implied from for Conversion[String, CompletionArg] { + def apply(s: String) = CompletionArg.Error(s) + } + implied from for Conversion[Future[HttpResponse], CompletionArg] { + def apply(f: Future[HttpResponse]) = CompletionArg.Response(f) + } + implied from for Conversion[Future[StatusCode], CompletionArg] { + def apply(code: Future[StatusCode]) = CompletionArg.Status(code) + } + } + import CompletionArg._ + + def complete[T](arg: CompletionArg) = arg match { + case Error(s) => ... + case Response(f) => ... + case Status(code) => ... + } +} +``` +This setup is more complicated than simple overloading of `complete`, but it can still be useful if normal overloading is not available (as in the case above, since we cannot have two overloaded methods that take `Future[...]` arguments), or if normal overloading would lead to a combinatorial explosion of variants. diff --git a/docs/docs/reference/derivation.md b/docs/docs/reference/contextual/derivation.md similarity index 86% rename from docs/docs/reference/derivation.md rename to docs/docs/reference/contextual/derivation.md index 7439be868cc4..2c07e8502f21 100644 --- a/docs/docs/reference/derivation.md +++ b/docs/docs/reference/contextual/derivation.md @@ -10,11 +10,11 @@ enum Tree[T] derives Eq, Ordering, Pickling { case Leaf(elem: T) } ``` -The `derives` clause generates implicit instances of the `Eq`, `Ordering`, and `Pickling` traits in the companion object `Tree`: +The `derives` clause generates implied instances of the `Eq`, `Ordering`, and `Pickling` traits in the companion object `Tree`: ```scala -implicit def derived$Eq [T: Eq]: Eq[Tree[T]] = Eq.derived -implicit def derived$Ordering [T: Ordering]: Ordering[Tree[T]] = Ordering.derived -implicit def derived$Pickling [T: Pickling]: Pickling[Tree[T]] = Pickling.derived +implied [T: Eq] for Eq[Tree[T]] = Eq.derived +implied [T: Ordering] for Ordering[Tree[T]] = Ordering.derived +implied [T: Pickling] for Pickling[Tree[T]] = Pickling.derived ``` ### Deriving Types @@ -45,9 +45,10 @@ A trait or class can appear in a `derives` clause if These two conditions ensure that the synthesized derived instances for the trait are well-formed. The type and implementation of a `derived` method are arbitrary, but typically it has a definition like this: ```scala - def derived[T](implicit ev: Generic[T]) = ... + def derived[T] with Generic[T] = ... ``` -That is, the `derived` method takes an implicit parameter of type `Generic` that determines the _shape_ of the deriving type `T` and it computes the typeclass implementation according to that shape. Implicit `Generic` instances are generated automatically for all types that have a `derives` clause. One can also derive `Generic` alone, which means a `Generic` instance is generated without any other type class instances. E.g.: +That is, the `derived` method takes an inferable parameter of type `Generic` that determines the _shape_ of the deriving type `T` and it computes the typeclass implementation according to that shape. An implied `Generic` instance is generated automatically for any type that derives a typeclass with a `derived` +method that refers to `Generic`. One can also derive `Generic` alone, which means a `Generic` instance is generated without any other type class instances. E.g.: ```scala sealed trait ParseResult[T] derives Generic ``` @@ -55,7 +56,7 @@ This is all a user of typeclass derivation has to know. The rest of this page co ### The Shape Type -For every class with a `derives` clause, the compiler computes the shape of that class as a type. For instance, here is the shape type for the `Tree[T]` enum: +For every class with a `derives` clause, the compiler computes the shape of that class as a type. For example, here is the shape type for the `Tree[T]` enum: ```scala Cases[( Case[Branch[T], (Tree[T], Tree[T])], @@ -96,17 +97,15 @@ Cases[( Note that an empty element tuple is represented as type `Unit`. A single-element tuple is represented as `T *: Unit` since there is no direct syntax for such tuples: `(T)` is just `T` in parentheses, not a tuple. -### The Generic TypeClass +### The Generic Typeclass -For every class `C[T_1,...,T_n]` with a `derives` clause, the compiler generates in the companion object of `C` an implicit instance of `Generic[C[T_1,...,T_n]]` that follows +For every class `C[T_1,...,T_n]` with a `derives` clause, the compiler generates in the companion object of `C` an implied instance of `Generic[C[T_1,...,T_n]]` that follows the outline below: ```scala -class derived$Generic[T_1, ..., T_n] extends Generic[C[T_1,...,T_n]] { +implied [T_1, ..., T_n] for Generic[C[T_1,...,T_n]] { type Shape = ... ... } -implicit def derived$Generic[T_1,...,T_n]]: derived$Generic[T_1,...,T_n]] = - new derived$Generic[T_1,...,T_n]] ``` where the right hand side of `Shape` is the shape type of `C[T_1,...,T_n]`. For instance, the definition @@ -121,15 +120,13 @@ would produce: object Result { import scala.compiletime.Shape._ - class derived$Generic[T, E] extends Generic[Result[T, E]] { + implied [T, E] for Generic[Result[T, E]] { type Shape = Cases[( Case[Ok[T], T *: Unit], Case[Err[E], E *: Unit] )] ... } - implicit def derived$Generic[T, E]: derived$Generic[T, E] = - new derived$Generic[T, E] } ``` The `Generic` class is defined in package `scala.reflect`. @@ -226,7 +223,7 @@ trait Eq[T] { We need to implement a method `Eq.derived` that produces an instance of `Eq[T]` provided there exists evidence of type `Generic[T]`. Here's a possible solution: ```scala - inline def derived[T](implicit ev: Generic[T]): Eq[T] = new Eq[T] { + inline def derived[T] with (ev: Generic[T]): Eq[T] = new Eq[T] { def eql(x: T, y: T): Boolean = { val mx = ev.reflect(x) // (1) val my = ev.reflect(y) // (2) @@ -314,7 +311,7 @@ The last, and in a sense most interesting part of the derivation is the comparis error("No `Eq` instance was found for $T") } ``` -`tryEql` is an inline method that takes an element type `T` and two element values of that type as arguments. It is defined using an `inline match` that tries to find an implicit instance of `Eq[T]`. If an instance `ev` is found, it proceeds by comparing the arguments using `ev.eql`. On the other hand, if no instance is found +`tryEql` is an inline method that takes an element type `T` and two element values of that type as arguments. It is defined using an `implicit match` that tries to find an implied instance of `Eq[T]`. If an instance `ev` is found, it proceeds by comparing the arguments using `ev.eql`. On the other hand, if no instance is found this signals a compilation error: the user tried a generic derivation of `Eq` for a class with an element type that does not support an `Eq` instance itself. The error is signaled by calling the `error` method defined in `scala.compiletime`. @@ -323,15 +320,15 @@ calling the `error` method defined in `scala.compiletime`. **Example:** Here is a slightly polished and compacted version of the code that's generated by inline expansion for the derived `Eq` instance of class `Tree`. ```scala -implicit def derived$Eq[T](implicit elemEq: Eq[T]): Eq[Tree[T]] = new Eq[Tree[T]] { +implied [T] with (elemEq: Eq[T]) for Eq[Tree[T]] { def eql(x: Tree[T], y: Tree[T]): Boolean = { - val ev = implicitly[Generic[Tree[T]]] + val ev = infer[Generic[Tree[T]]] val mx = ev.reflect(x) val my = ev.reflect(y) mx.ordinal == my.ordinal && { if (mx.ordinal == 0) { - derived$Eq.eql(mx(0).asInstanceOf[Tree[T]], my(0).asInstanceOf[Tree[T]]) && - derived$Eq.eql(mx(1).asInstanceOf[Tree[T]], my(1).asInstanceOf[Tree[T]]) + this.eql(mx(0).asInstanceOf[Tree[T]], my(0).asInstanceOf[Tree[T]]) && + this.eql(mx(1).asInstanceOf[Tree[T]], my(1).asInstanceOf[Tree[T]]) } else if (mx.ordinal == 1) { elemEq.eql(mx(0).asInstanceOf[T], my(0).asInstanceOf[T]) @@ -342,21 +339,21 @@ implicit def derived$Eq[T](implicit elemEq: Eq[T]): Eq[Tree[T]] = new Eq[Tree[T] } ``` -One important difference between this approach and Scala-2 typeclass derivation frameworks such as Shapeless or Magnolia is that no automatic attempt is made to generate typeclass instances of elements recursively using the generic derivation framework. There must be an implicit instance of `Eq[T]` (which can of course be produced in turn using `Eq.derived`), or the compilation will fail. The advantage of this more restrictive approach to typeclass derivation is that it avoids uncontrolled transitive typeclass derivation by design. This keeps code sizes smaller, compile times lower, and is generally more predictable. +One important difference between this approach and Scala-2 typeclass derivation frameworks such as Shapeless or Magnolia is that no automatic attempt is made to generate typeclass instances of elements recursively using the generic derivation framework. There must be an implied instance of `Eq[T]` (which can of course be produced in turn using `Eq.derived`), or the compilation will fail. The advantage of this more restrictive approach to typeclass derivation is that it avoids uncontrolled transitive typeclass derivation by design. This keeps code sizes smaller, compile times lower, and is generally more predictable. ### Derived Instances Elsewhere Sometimes one would like to derive a typeclass instance for an ADT after the ADT is defined, without being able to change the code of the ADT itself. To do this, simply define an instance with the `derived` method of the typeclass as right-hand side. E.g, to implement `Ordering` for `Option`, define: ```scala -implicit def myOptionOrdering[T: Ordering]: Ordering[Option[T]] = Ordering.derived +implied [T: Ordering]: Ordering[Option[T]] = Ordering.derived ``` -Usually, the `Ordering.derived` clause has an implicit parameter of type +Usually, the `Ordering.derived` clause has an inferable parameter of type `Generic[Option[T]]`. Since the `Option` trait has a `derives` clause, -the necessary implicit instance is already present in the companion object of `Option`. -If the ADT in question does not have a `derives` clause, an implicit `Generic` instance +the necessary implied instance is already present in the companion object of `Option`. +If the ADT in question does not have a `derives` clause, an implied `Generic` instance would still be synthesized by the compiler at the point where `derived` is called. -This is similar to the situation with type tags or class tags: If no implicit instance +This is similar to the situation with type tags or class tags: If no implied instance is found, the compiler will synthesize one. ### Syntax diff --git a/docs/docs/reference/contextual/extension-methods.md b/docs/docs/reference/contextual/extension-methods.md new file mode 100644 index 000000000000..c38c0c1ba52f --- /dev/null +++ b/docs/docs/reference/contextual/extension-methods.md @@ -0,0 +1,150 @@ +--- +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 implied instance at the point of the application. + +As an example, consider an extension method `longestStrings` on `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 an implied instance of `StringSeqOps`, like this: +```scala +implied ops1 for StringSeqOps +``` +Then +```scala +List("here", "is", "a", "list").longestStrings +``` +is legal everywhere `ops1` is available as an implied instance. 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 an implied instance `i` + in either the current scope or in the implied 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 an eligible implied instance (i.e. it is visible at the point of call or it is defined in the companion object of `Circle`). + +### Implied Instances for Extension Methods + +Implied instances that define extension methods can also be defined without an `of` clause. E.g., + +```scala +implied StringOps { + def (xs: Seq[String]) longestStrings: Seq[String] = { + val maxLength = xs.map(_.length).max + xs.filter(_.length == maxLength) + } +} + +implied ListOps { + def (xs: List[T]) second[T] = xs.tail.head +} +``` +If such implied instances are anonymous (as in the examples above), 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 = + implicitly[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](https://github.com/lampepfl/dotty/blob/master/docs/docs/internals/syntax.md). +``` +DefSig ::= ... + | ‘(’ DefParam ‘)’ [nl] id [DefTypeParamClause] DefParamClauses +``` + + + + diff --git a/docs/docs/reference/contextual/inferable-by-name-parameters.md b/docs/docs/reference/contextual/inferable-by-name-parameters.md new file mode 100644 index 000000000000..9ebdbe9f5c49 --- /dev/null +++ b/docs/docs/reference/contextual/inferable-by-name-parameters.md @@ -0,0 +1,66 @@ +--- +layout: doc-page +title: "Inferable By-Name Parameters" +--- + +Inferable by-name parameters can be used to avoid a divergent inferred expansion. Example: + +```scala +trait Codec[T] { + def write(x: T): Unit +} + +implied intCodec for Codec[Int] = ??? + +implied optionCodec[T] given (ev: => Codec[T]) for Codec[Option[T]] { + def write(xo: Option[T]) = xo match { + case Some(x) => ev.write(x) + case None => + } +} + +val s = infer[Codec[Option[Int]]] + +s.write(Some(33)) +s.write(None) +``` +As is the case for a normal by-name parameter, the argument for the inferable parameter `ev` +is evaluated on demand. In the example above, if the option value `x` is `None`, it is +not evaluated at all. + +The synthesized argument for an inferable parameter is backed by a local val +if this is necessary to prevent an otherwise diverging expansion. + +The precise steps for constructing an inferable argument for a by-name parameter of type `=> T` are as follows. + + 1. Create a new implied instance of type `T`: + + ```scala + implied lv for T = ??? + ``` + where `lv` is an arbitrary fresh name. + + 1. This instance is not immediately available as candidate for argument inference (making it immediately available could result in a loop in the synthesized computation). But it becomes available in all nested contexts that look again for an inferred argument to a by-name parameter. + + 1. If this search succeeds with expression `E`, and `E` contains references to the implied instance `lv`, replace `E` by + + + ```scala + { implied lv for T = E; lv } + ``` + + Otherwise, return `E` unchanged. + +In the example above, the definition of `s` would be expanded as follows. + +```scala +val s = infer[Test.Codec[Option[Int]]]( + optionCodec[Int](intCodec)) +``` + +No local instance was generated because the synthesized argument is not recursive. + +### Reference + +For more info, see [Issue #1998](https://github.com/lampepfl/dotty/issues/1998) +and the associated [Scala SIP](https://docs.scala-lang.org/sips/byname-implicits.html). diff --git a/docs/docs/reference/contextual/inferable-params.md b/docs/docs/reference/contextual/inferable-params.md new file mode 100644 index 000000000000..a652e5a8f6f2 --- /dev/null +++ b/docs/docs/reference/contextual/inferable-params.md @@ -0,0 +1,112 @@ +--- +layout: doc-page +title: "Inferable Parameters" +--- + +Functional programming tends to express most dependencies as simple function parameterization. +This is clean and powerful, but it sometimes leads to functions that take many parameters and +call trees where the same value is passed over and over again in long call chains to many +functions. Inferable parameters can help here since they enable the compiler to synthesize +repetitive arguments instead of the programmer having to write them explicitly. + +For example, given the [implied instances](./instance-defs.md) defined previously, +a maximum function that works for any arguments for which an ordering exists can be defined as follows: +```scala +def max[T](x: T, y: T) given (ord: Ord[T]): T = + if (ord.compare(x, y) < 1) y else x +``` +Here, `ord` is an _inferable parameter_. Inferable parameters are introduced with a `given` clause. +The `max` method can be applied as follows: +```scala +max(2, 3) given IntOrd +``` +The `given IntOrd` part provides the `IntOrd` instance as an argument for the `ord` parameter. But the point of inferable parameters is that this argument can also be left out (and it usually is). So the following +applications are equally valid: +```scala +max(2, 3) +max(List(1, 2, 3), Nil) +``` + +## Anonymous Inferable Parameters + +In many situations, the name of an inferable parameter of a method need not be +mentioned explicitly at all, since it is only used in synthesized arguments for +other inferable parameters. In that case one can avoid defining a parameter name +and just provide its type. Example: +```scala +def maximum[T](xs: List[T]) given Ord[T]: T = + xs.reduceLeft(max) +``` +`maximum` takes an inferable parameter of type `Ord` only to pass it on as an +inferred argument to `max`. The name of the parameter is left out. + +Generally, inferable parameters may be given either as a parameter list `(p_1: T_1, ..., p_n: T_n)` +or as a sequence of types, separated by commas. To distinguish the two, a leading +`(` always indicates a parameter list. + +## Inferring Complex Arguments + +Here are two other methods that have an inferable parameter of type `Ord[T]`: +```scala +def descending[T] given (asc: Ord[T]): Ord[T] = new Ord[T] { + def compare(x: T, y: T) = asc.compare(y, x) +} + +def minimum[T](xs: List[T]) given Ord[T] = + maximum(xs) given descending +``` +The `minimum` method's right hand side passes `descending` as an explicit argument to `maximum(xs)`. +With this setup, the following calls are all well-formed, and they all normalize to the last one: +```scala +minimum(xs) +maximum(xs) given descending +maximum(xs) given (descending given ListOrd) +maximum(xs) given (descending given (ListOrd given IntOrd)) +``` + +## Mixing Inferable And Normal Parameters + +Inferable parameters can be freely mixed with normal parameters. +An inferable parameter may be followed by a normal parameter and _vice versa_. +There can be several inferable parameter lists in a definition. Example: +```scala +def f given (u: Universe) (x: u.T) given Context = ... + +implied global for Universe { type T = String ... } +implied ctx for Context { ... } +``` +Then the following calls are all valid (and normalize to the last one) +```scala +f("abc") +(f given global)("abc") +f("abc") given ctx +(f given global)("abc") given ctx +``` + +## Querying Implied Instances + +A method `infer` in `Predef` creates an implied instance of a given type. For example, +the implied instance of `Ord[List[Int]]` is generated by +```scala +infer[Ord[List[Int]]] // reduces to ListOrd given IntOrd +``` +The `infer` method is simply defined as the identity function over an inferable parameter. +```scala +def infer[T] given (x: T) = x +``` +Functions like `infer` that have only inferable parameters are also called _context queries_. + +## Syntax + +Here is the new syntax of parameters and arguments seen as a delta from the [standard context free syntax of Scala 3](http://dotty.epfl.ch/docs/internals/syntax.html). +``` +ClsParamClause ::= ... + | ‘given’ (‘(’ [ClsParams] ‘)’ | ContextTypes) +DefParamClause ::= ... + | InferParamClause +InferParamClause ::= ‘given’ (‘(’ DefParams ‘)’ | ContextTypes) +ContextTypes ::= RefinedType {‘,’ RefinedType} + +InfixExpr ::= ... + | InfixExpr ‘given’ (InfixExpr | ParArgumentExprs) +``` diff --git a/docs/docs/reference/contextual/instance-defs.md b/docs/docs/reference/contextual/instance-defs.md new file mode 100644 index 000000000000..d985f31a928f --- /dev/null +++ b/docs/docs/reference/contextual/instance-defs.md @@ -0,0 +1,77 @@ +--- +layout: doc-page +title: "Implied Instances" +--- + +Implied instance definitions define "canonical" values of given types +that serve for synthesizing arguments to [inferable parameters](./inferable-params.html). Example: + +```scala +trait Ord[T] { + def compare(x: T, y: T): Int + def (x: T) < (y: T) = compare(x, y) < 0 + def (x: T) > (y: T) = compare(x, y) > 0 +} + +implied IntOrd for Ord[Int] { + def compare(x: Int, y: Int) = + if (x < y) -1 else if (x > y) +1 else 0 +} + +implied ListOrd[T] given (ord: Ord[T]) for Ord[List[T]] { + def compare(xs: List[T], ys: List[T]): Int = (xs, ys) match { + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs1, y :: ys1) => + val fst = ord.compare(x, y) + if (fst != 0) fst else xs1.compareTo(ys1) + } +} +``` +This code defines a trait `Ord` and two implied instance definitions. `IntOrd` defines +an implied instance for the type `Ord[Int]` whereas `ListOrd[T]` defines implied +instances of `Ord[List[T]]` for all types `T` that come with an implied `Ord[T]` instance themselves. +The `given` clause in `ListOrd` defines an [inferable parameter](./inferable-params.html). +Inferable parameters are further explained in the next section. + +## Anonymous Implied Instances + +The name of an implied instance can be left out. So the implied instance definitions +of the last section can also be expressed like this: +```scala +implied for Ord[Int] { ... } +implied [T] given (ord: Ord[T]) for Ord[List[T]] { ... } +``` +If the name of an instance is missing, the compiler will synthesize a name from +the type(s) in the `for` clause. + +## Implied Alias Instances + +An implied alias instance defines an implied instance that is equal to some expression. E.g., assuming a global method `currentThreadPool` returning a value with a member `context`, one could define: +```scala +implied ctx for ExecutionContext = currentThreadPool().context +``` +This creates an implied instance `ctx` of type `ExecutionContext` that resolves to the right hand side `currentThreadPool().context`. Each time an implied instance of `ExecutionContext` is demanded, the result of evaluating the right-hand side expression is returned. + +Alias instances may be anonymous, e.g. +```scala +implied for Position = enclosingTree.position +``` +An implied alias instance can have type and context parameters just like any other implied instance definition, but it can only implement a single type. + +## Syntax + +Here is the new syntax of implied instance definitions, seen as a delta from the [standard context free syntax of Scala 3](http://dotty.epfl.ch/docs/internals/syntax.html). +``` +TmplDef ::= ... + | ‘implied’ InstanceDef +InstanceDef ::= [id] InstanceParams InstanceBody +InstanceParams ::= [DefTypeParamClause] {InferParamClause} +InferParamClause ::= ‘given’ (‘(’ [DefParams] ‘)’ | ContextTypes) +InstanceBody ::= [‘for’ ConstrApp {‘,’ ConstrApp }] [TemplateBody] + | ‘for’ Type ‘=’ Expr +ContextTypes ::= RefinedType {‘,’ RefinedType} +``` +The identifier `id` can be omitted only if either the `for` part or the template body is present. +If the `for` part is missing, the template body must define at least one extension method. diff --git a/docs/docs/reference/contextual/query-types-spec.md b/docs/docs/reference/contextual/query-types-spec.md new file mode 100644 index 000000000000..67c627ce79f4 --- /dev/null +++ b/docs/docs/reference/contextual/query-types-spec.md @@ -0,0 +1,79 @@ +--- +layout: doc-page +title: "Context Query Types - More Details" +--- + +## Syntax + + Type ::= ... + | `given' FunArgTypes `=>' Type + Expr ::= ... + | `given' FunParams `=>' Expr + +Context query types associate to the right, e.g. +`given S => given T => U` is the same as `given S => (given T => U)`. + +## Implementation + +Context query types are shorthands for class types that define `apply` +methods with inferable parameters. Specifically, the `N`-ary function type +`T1, ..., TN => R` is a shorthand for the class type +`ImplicitFunctionN[T1 , ... , TN, R]`. Such class types are assumed to have the following definitions, for any value of `N >= 1`: +```scala +package scala +trait ImplicitFunctionN[-T1 , ... , -TN, +R] { + def apply given (x1: T1 , ... , xN: TN): R +} +``` +Context query types erase to normal function types, so these classes are +generated on the fly for typechecking, but not realized in actual code. + +Context query literals `given (x1: T1, ..., xn: Tn) => e` map +inferable parameters `xi` of types `Ti` to a result given by expression `e`. +The scope of each implicit parameter `xi` is `e`. The parameters must have pairwise distinct names. + +If the expected type of the query literal is of the form +`scala.ImplicitFunctionN[S1, ..., Sn, R]`, the expected type of `e` is `R` and +the type `Ti` of any of the parameters `xi` can be omitted, in which case `Ti += Si` is assumed. If the expected type of the query literal is +some other type, all inferable parameter types must be explicitly given, and the expected type of `e` is undefined. The type of the query literal is `scala.ImplicitFunctionN[S1, ...,Sn, T]`, where `T` is the widened +type of `e`. `T` must be equivalent to a type which does not refer to any of +the inferable parameters `xi`. + +The query literal is evaluated as the instance creation +expression: +```scala +new scala.ImplicitFunctionN[T1, ..., Tn, T] { + def apply given (x1: T1, ..., xn: Tn): T = e +} +``` +In the case of a single untyped parameter, `given (x) => e` can be +abbreviated to `given x => e`. + +An inferable parameter may also be a wildcard represented by an underscore `_`. In +that case, a fresh name for the parameter is chosen arbitrarily. + +Note: The closing paragraph of the +[Anonymous Functions section](https://www.scala-lang.org/files/archive/spec/2.12/06-expressions.html#anonymous-functions) +of Scala 2.12 is subsumed by query types and should be removed. + +Query literals `given (x1: T1, ..., xn: Tn) => e` are +automatically created for any expression `e` whose expected type is +`scala.ImplicitFunctionN[T1, ..., Tn, R]`, unless `e` is +itself a query literal. This is analogous to the automatic +insertion of `scala.Function0` around expressions in by-name argument position. + +Context query types generalize to `N > 22` in the same way that function types do, see [the corresponding +documentation](https://dotty.epfl.ch/docs/reference/dropped-features/limit22.html). + +## Examples + +See the section on Expressiveness from [Simplicitly: foundations and +applications of implicit function +types](https://dl.acm.org/citation.cfm?id=3158130). I've extracted it in [this +Gist](https://gist.github.com/OlivierBlanvillain/234d3927fe9e9c6fba074b53a7bd9 +592), it might easier to access than the pdf. + +### Type Checking + +After desugaring no additional typing rules are required for context query types. diff --git a/docs/docs/reference/contextual/query-types.md b/docs/docs/reference/contextual/query-types.md new file mode 100644 index 000000000000..68adcb79b4d5 --- /dev/null +++ b/docs/docs/reference/contextual/query-types.md @@ -0,0 +1,160 @@ +--- +layout: doc-page +title: "Context Queries" +--- + +_Context queries_ are functions with (only) inferable parameters. +_Context query types_ are the types of first-class context queries. +Here is an example for a context query type: +```scala +type Contextual[T] = given Context => T +``` +A value of context query type is applied to inferred arguments, in +the same way a method with inferable parameters is applied. For instance: +```scala + implied ctx for Context = ... + + def f(x: Int): Contextual[Int] = ... + + f(2) given ctx // explicit argument + f(2) // argument is inferred +``` +Conversely, if the expected type of an expression `E` is a context query +type `given (T_1, ..., T_n) => U` and `E` is not already a +context query literal, `E` is converted to a context query literal by rewriting to +```scala + given (x_1: T1, ..., x_n: Tn) => E +``` +where the names `x_1`, ..., `x_n` are arbitrary. This expansion is performed +before the expression `E` is typechecked, which means that `x_1`, ..., `x_n` +are available as implied instances in `E`. + +Like query types, query literals are written with a `given` prefix. They differ from normal function literals in two ways: + + 1. Their parameters are inferable. + 2. Their types are context query types. + +For example, continuing with the previous definitions, +```scala + def g(arg: Contextual[Int]) = ... + + g(22) // is expanded to g(given ctx => 22) + + g(f(2)) // is expanded to g(given ctx => f(2) given ctx) + + g(given ctx => f(22) given ctx) // is left as it is +``` +### Example: Builder Pattern + +Context query types have considerable expressive power. For +instance, here is how they can support the "builder pattern", where +the aim is to construct tables like this: +```scala + table { + row { + cell("top left") + cell("top right") + } + row { + cell("bottom left") + cell("bottom right") + } + } +``` +The idea is to define classes for `Table` and `Row` that allow +addition of elements via `add`: +```scala + class Table { + val rows = new ArrayBuffer[Row] + def add(r: Row): Unit = rows += r + override def toString = rows.mkString("Table(", ", ", ")") + } + + class Row { + val cells = new ArrayBuffer[Cell] + def add(c: Cell): Unit = cells += c + override def toString = cells.mkString("Row(", ", ", ")") + } + + case class Cell(elem: String) +``` +Then, the `table`, `row` and `cell` constructor methods can be defined +in terms of query types to avoid the plumbing boilerplate +that would otherwise be necessary. +```scala + def table(init: given Table => Unit) = { + implied t for Table + init + t + } + + def row(init: given Row => Unit) given (t: Table) = { + implied r for Row + init + t.add(r) + } + + def cell(str: String) given (r: Row) = + r.add(new Cell(str)) +``` +With that setup, the table construction code above compiles and expands to: +```scala + table { given $t: Table => + row { given $r: Row => + cell("top left") given $r + cell("top right") given $r + } given $t + row { given $r: Row => + cell("bottom left") given $r + cell("bottom right") given $r + } given $t + } +``` +### Example: Postconditions + +As a larger example, here is a way to define constructs for checking arbitrary postconditions using an extension method `ensuring`so that the checked result can be referred to simply by `result`. The example combines opaque aliases, context query types, and extension methods to provide a zero-overhead abstraction. + +```scala +object PostConditions { + opaque type WrappedResult[T] = T + + private object WrappedResult { + def wrap[T](x: T): WrappedResult[T] = x + def unwrap[T](x: WrappedResult[T]): T = x + } + + def result[T] given (r: WrappedResult[T]): T = WrappedResult.unwrap(r) + + def (x: T) ensuring [T](condition: given WrappedResult[T] => Boolean): T = { + implied for WrappedResult[T] = WrappedResult.wrap(x) + assert(condition) + x + } +} + +object Test { + import PostConditions.ensuring + val s = List(1, 2, 3).sum.ensuring(result == 6) +} +``` +**Explanations**: We use a context query type `given WrappedResult[T] => Boolean` +as the type of the condition of `ensuring`. An argument to `ensuring` such as +`(result == 6)` will therefore have an implied instance of type `WrappedResult[T]` in +scope to pass along to the `result` method. `WrappedResult` is a fresh type, to make sure +that we do not get unwanted implied instances in scope (this is good practice in all cases +where inferable parameters are involved). Since `WrappedResult` is an opaque type alias, its +values need not be boxed, and since `ensuring` is added as an extension method, its argument +does not need boxing either. Hence, the implementation of `ensuring` is as about as efficient +as the best possible code one could write by hand: + + { val result = List(1, 2, 3).sum + assert(result == 6) + result + } + +### Reference + +For more info, see the [blog article](https://www.scala-lang.org/blog/2016/12/07/implicit-function-types.html), +(which uses a different syntax that has been superseded). + +[More details](./query-types-spec.html) diff --git a/docs/docs/reference/contextual/relationship-implicits.md b/docs/docs/reference/contextual/relationship-implicits.md new file mode 100644 index 000000000000..bfb4e3517070 --- /dev/null +++ b/docs/docs/reference/contextual/relationship-implicits.md @@ -0,0 +1,163 @@ +--- +layout: doc-page +title: Relationship with Scala 2 Implicits" +--- + +Many, but not all, of the new contextual abstraction features in Scala 3 can be mapped to Scala 2's implicits. This page gives a rundown on the relationships between new and old features. + +## Simulating Contextual Abstraction with Implicits + +### Implied Instance Definitions + +Implied instance definitions can be mapped to combinations of implicit objects, classes and implicit methods. + + 1. Instance definitions without parameters are mapped to implicit objects. E.g., + ```scala + implied IntOrd for Ord[Int] { ... } + ``` + maps to + ```scala + implicit object IntOrd extends Ord[Int] { ... } + ``` + 2. Parameterized instance definitions are mapped to combinations of classes and implicit methods. E.g., + ```scala + implied ListOrd[T] given (ord: Ord[T]) for Ord[List[T]] { ... } + ``` + maps to + ```scala + class ListOrd[T](implicit ord: Ord[T]) extends Ord[List[T]] { ... } + final implicit def ListOrd[T](implicit ord: Ord[T]): ListOrd[T] = new ListOrd[T] + ``` + 3. Implied alias instances map to implicit methods. E.g., + ```scala + implied ctx for ExecutionContext = ... + ``` + maps to + ```scala + final implicit def ctx: ExecutionContext = ... + ``` + +### Anonymous Implied Instances + +Anonymous instances get compiler synthesized names, which are generated in a reproducible way from the implemented type(s). For example, if the names of the `IntOrd` and `ListOrd` instances above were left out, the following names would be synthesized instead: +```scala + implied Ord_Int_instance for Ord[Int] { ... } + implied Ord_List_instance[T] for Ord[List[T]] { ... } +``` +The synthesized type names are formed from + + - the simple name(s) of the implemented type(s), leaving out any prefixes, + - the simple name(s) of the toplevel argument type constructors to these types + - the suffix `_instance`. + +Anonymous instances that define extension methods without also implementing a type +get their name from the name of the first extension method and the toplevel type +constructor of its first parameter. For example, the instance +```scala + implied { + def (xs: List[T]) second[T] = ... + } +``` +gets the synthesized name `second_of_List_T_instance`. + +### Inferable Parameters + +The new inferable parameter syntax with `given` corresponds largely to Scala-2's implicit parameters. E.g. +```scala + def max[T](x: T, y: T) given (ord: Ord[T]): T +``` +would be written +```scala + def max[T](x: T, y: T)(implicit ord: Ord[T]): T +``` +in Scala 2. The main difference concerns applications of such parameters. +Explicit arguments to inferable parameters _must_ be written using `given`, +mirroring the definition syntax. E.g, `max(2, 3) given IntOrd`. +Scala 2 uses normal applications `max(2, 3)(IntOrd)` instead. The Scala 2 syntax has some inherent ambiguities and restrictions which are overcome by the new syntax. For instance, multiple implicit parameter lists are not available in the old syntax, even though they can be simulated using auxiliary objects in the "Aux" pattern. + +The `infer` method corresponds to `implicitly` in Scala 2. + +### Context Bounds + +Context bounds are the same in both language versions. They expand to the respective forms of implicit parameters. + +**Note:** To ease migration, context bounds in Dotty map for a limited time to old-style implicit parameters for which arguments can be passed either with `given` or +with a normal application. Once old-style implicits are deprecated, context bounds +will map to inferable parameters instead. + +### Extension Methods + +Extension methods have no direct counterpart in Scala 2, but they can be simulated with implicit classes. For instance, the extension method +```scala + def (c: Circle) circumference: Double = c.radius * math.Pi * 2 +``` +could be simulated to some degree by +```scala + implicit class CircleDeco(c: Circle) extends AnyVal { + def circumference: Double = c.radius * math.Pi * 2 + } +``` +Extension methods in implicit instances have no direct counterpart in Scala-2. The only way to simulate these is to make implicit classes available through imports. The Simulacrum macro library can automate this process in some cases. + +### Typeclass Derivation + +Typeclass derivation has no direct counterpart in the Scala 2 language. Comparable functionality can be achieved by macro-based libraries such as Shapeless, Magnolia, or scalaz-deriving. + +### Implicit Function Types + +Implicit function types have no analogue in Scala 2. + +### Implicit By-Name Parameters + +Implicit by-name parameters are not supported in Scala 2, but can be emulated to some degree by the `Lazy` type in Shapeless. + +## Simulating Scala 2 Implicits in Dotty + +### Implicit Conversions + +Implicit conversion methods in Scala 2 can be expressed as implied instances +of the `scala.Conversion` class in Dotty. E.g. instead of +```scala + implicit def stringToToken(str: String): Token = new Keyword(str) +``` +one can write +```scala + implied stringToToken for Conversion[String, Token] { + def apply(str: String): Token = new KeyWord(str) + } +``` + +### Implicit Classes + +Implicit classes in Scala 2 are often used to define extension methods, which are directly supported in Dotty. Other uses of implicit classes can be simulated by a pair of a regular class and a conversion instance. + +### Implicit Values + +Implicit `val` definitions in Scala 2 can be expressed in Dotty using a regular `val` definition and an instance alias. E.g., Scala 2's +```scala + lazy implicit val pos: Position = tree.sourcePos +``` +can be expressed in Dotty as +```scala + lazy val pos: Position = tree.sourcePos + implied for Position = pos +``` + +### Abstract Implicits + +An abstract implicit `val` or `def` in Scala 2 can be expressed in Dotty using a regular abstract definition and an implied alias. E.g., Scala 2's +```scala + implicit def symDeco: SymDeco +``` +can be expressed in Dotty as +```scala + def symDeco: SymDeco + implied for SymDeco = symDeco +``` + +## Implementation Status and Timeline + +The Dotty implementation implements both Scala-2's implicits and the new abstractions. In fact, support for Scala-2's implicits is an essential part of the common language subset between 2.13/2.14 and Dotty. +Migration to the new abstractions will be supported by making automatic rewritings available. + +Depending on adoption patterns, old style implicits might start to be deprecated in a version following Scala 3.0. diff --git a/docs/docs/reference/contextual/typeclasses.md b/docs/docs/reference/contextual/typeclasses.md new file mode 100644 index 000000000000..f14516692afc --- /dev/null +++ b/docs/docs/reference/contextual/typeclasses.md @@ -0,0 +1,64 @@ +--- +layout: doc-page +title: "Implementing Typeclasses" +--- + +Implied instance definitions, extension methods and context bounds +allow a concise and natural expression of _typeclasses_. Typeclasses are just traits +with canonical implementations defined by implied instance definitions. Here are some examples of standard typeclasses: + +### Semigroups and monoids: + +```scala +trait SemiGroup[T] { + def (x: T) combine (y: T): T +} +trait Monoid[T] extends SemiGroup[T] { + def unit: T +} +object Monoid { + def apply[T] = infer[Monoid[T]] +} + +implied for Monoid[String] { + def (x: String) combine (y: String): String = x.concat(y) + def unit: String = "" +} + +implied for Monoid[Int] { + def (x: Int) combine (y: Int): Int = x + y + def unit: String = 0 +} + +def sum[T: Monoid](xs: List[T]): T = + xs.foldLeft(Monoid[T].unit)(_.combine(_)) +``` + +### Functors and monads: + +```scala +trait Functor[F[_]] { + def (x: F[A]) map [A, B] (f: A => B): F[B] +} + +trait Monad[F[_]] extends Functor[F] { + 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] +} + +implied ListMonad for Monad[List] { + 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) +} + +implied ReaderMonad[Ctx] for Monad[[X] => Ctx => X] { + 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 +} +``` diff --git a/docs/docs/reference/dropped-features/limit22.md b/docs/docs/reference/dropped-features/limit22.md index 7a7cbf931bf9..285dcb543629 100644 --- a/docs/docs/reference/dropped-features/limit22.md +++ b/docs/docs/reference/dropped-features/limit22.md @@ -3,11 +3,11 @@ layout: doc-page title: Dropped: Limit 22 --- -The limit of 22 for the maximal number of parameters of function types -has been dropped. Functions can now have an arbitrary number of +The limits of 22 for the maximal number of parameters of function types +and the maximal number of fields in tuple types have been dropped. + +Functions can now have an arbitrary number of parameters. Functions beyond Function22 are represented with a new trait `scala.FunctionXXL`. -The limit of 22 for the size of tuples is about to be dropped. Tuples -will in the future be represented by an HList-like structure which can -be arbitrarily large. +Tuples can also have an arbitrary number of fields. Furthermore, they support generic operation such as concatenation and indexing. diff --git a/docs/docs/reference/instances/context-params.md b/docs/docs/reference/instances/context-params.md deleted file mode 100644 index ef58b3d82ece..000000000000 --- a/docs/docs/reference/instances/context-params.md +++ /dev/null @@ -1,89 +0,0 @@ ---- -layout: doc-page -title: "Context Parameters and Arguments" ---- - -Context parameters are the name of a new syntax for implicit parameters that aligns definition and call syntax. Parameter definitions -and method arguments both follow a `with` connective. On the definition side, the old syntax -```scala -def f(a: A)(implicit b: B) -``` -is now expressed as -```scala -def f(a: A) with (b: B) -``` -or, leaving out the parameter name, -```scala -def f(a: A) with B -``` -Implicit parameters defined with the new syntax are also called _context parameters_. -They come with a matching syntax for applications: explicit arguments for context parameters are also given after a `with`. - -The following example shows shows three methods that each have a context parameter for `Ord[T]`. -```scala -def maximum[T](xs: List[T]) with Ord[T]: T = - xs.reduceLeft((x, y) => if (x < y) y else x) - -def descending[T] with (asc: Ord[T]): Ord[T] = new Ord[T] { - def (x: T) compareTo (y: T) = asc.compareTo(y)(x) -} - -def minimum[T](xs: List[T]) with Ord[T] = - maximum(xs) with descending -``` -The `minimum` method's right hand side passes `descending` as an explicit argument to `maximum(xs)`. -But usually, explicit arguments for context parameters are be left out. For instance, -given `xs: List[Int]`, the following calls are all possible (and they all normalize to the last one:) -```scala -maximum(xs) -maximum(xs) with descending -maximum(xs) with (descending with IntOrd) -``` -Arguments for context parameters must be given using the `with` syntax. So the expression `maximum(xs)(descending)` would give a type error. - -The `with` connective is treated like an infix operator with the same precedence as other operators that start with a letter. The expression following a `with` may also be an argument list consisting of several implicit arguments separated by commas. If a tuple should be passed as a single implicit argument (probably an uncommon case), it has to be put in a pair of extra parentheses: -```scala -def f with (x: A, y: B) -f with (a, b) - -def g with (xy: (A, B)) -g with ((a, b)) -``` -Unlike existing implicit parameters, context parameters can be freely mixed with normal parameter lists. -A context parameter may be followed by a normal parameter and _vice versa_. There can be several context parameter -lists in a definition. Example: -```scala -def f with (u: Universe) (x: u.T) with Context = ... - -instance global for Universe { type T = String ... } -instance ctx for Context { ... } -``` -Then the following calls are all valid (and normalize to the last one) -```scala -f("abc") -(f with global)("abc") -f("abc") with ctx -(f with global)("abc") with ctx -``` -Context parameters may be given either as a normal parameter list `(...)` -or as a sequence of types. To distinguish the two, a leading `(` always indicates a parameter list. - -## Summoning an Instance - -A method `summon` in `Predef` creates an implicit instance value for a given type, analogously to what `implicitly[T]` did. The only difference between the two is that -`summon` takes a context parameter, where `implicitly` took an old-style implicit parameter: -```scala -def summon[T] with (x: T) = x -``` - -## Syntax - -Here is the new syntax of parameters and arguments seen as a delta from the [standard context free syntax of Scala 3](http://dotty.epfl.ch/docs/internals/syntax.html). -``` -ClsParamClause ::= ... - | ‘with’ (‘(’ [ClsParams] ‘)’ | ContextTypes) -DefParamClause ::= ... - | InstParamClause -InfixExpr ::= ... - | InfixExpr ‘with’ (InfixExpr | ParArgumentExprs) -``` diff --git a/docs/docs/reference/instances/discussion/context-params.md b/docs/docs/reference/instances/discussion/context-params.md deleted file mode 100644 index afd6992ef5f0..000000000000 --- a/docs/docs/reference/instances/discussion/context-params.md +++ /dev/null @@ -1,121 +0,0 @@ ---- -layout: doc-page -title: "Context Parameters and Arguments" ---- - -This page presents new syntax for defining implicit parameters that aligns definition and call syntax. In both cases, the implicit parameter or argument now follows a `with` connective. -On the definition side, the old syntax -```scala -def f(a: A)(implicit b: B) -``` -is now expressed as -```scala -def f(a: A) with (b: B) -``` -Implicit parameters defined with the new syntax are also called _context parameters_. -They come with a matching syntax for applications: explicit arguments for context parameters are also given after a `with`. - -The following example shows shows three methods that each have a context parameter for `Ord[T]`. -```scala -def maximum[T](xs: List[T]) with (cmp: Ord[T]): T = - xs.reduceLeft((x, y) => if (x < y) y else x) - -def descending[T] with (asc: Ord[T]): Ord[T] = new Ord[T] { - def (x: T) compareTo (y: T) = asc.compareTo(y)(x) -} - -def minimum[T](xs: List[T]) with (cmp: Ord[T]) = - maximum(xs) with descending -``` -The `minimum` method's right hand side defines the explicit argument `descending`. -Explicit arguments for context parameters can be left out. For instance, -given `xs: List[Int]`, the following calls are all possible (and they all normalize to the last one:) -```scala -maximum(xs) -maximum(xs) with descending -maximum(xs) with (descending with IntOrd) -``` -Arguments for context parameters must be given using the `with` syntax. So the expression `maximum(xs)(descending)` would give a type error. - -The `with` connective is treated like an infix operator with the same precedence as other operators that start with a letter. The expression following a `with` may also be an argument list consisting of several implicit arguments separated by commas. If a tuple should be passed as a single implicit argument (probably an uncommon case), it has to be put in a pair of extra parentheses: -```scala -def f with (x: A, y: B) -f with (a, b) - -def g with (xy: (A, B)) -g with ((a, b)) -``` - -## Implicit Function Types and Closures - -Implicit function types are expressed using the new reserved operator `|=>`. Examples: -```scala -Context |=> T -A |=> B |=> T -(A, B) |=> T -(x: A, y: B) |=> T -``` -The `|=>` syntax was chosen for its resemblance with a turnstile symbol `|-` which signifies context dependencies. - -The old syntax `implicit A => B` is no longer available. -Implicit function types are applied using `with`: -```scala -val f: A |=> B -val a: A -f with a // OK -f(a) // error: `f` does not take parameters -``` -Since application of regular function types and implicit function types different, implicit function types are no longer subtypes of regular function types. - -The `|=>` syntax can also be used for closures. It turns the parameter bindings into implicit -parameters and makes the closure's type an implicit function type -```scala -case class Context(value: String) -val f1: Context |=> String = ctx |=> ctx.value -val f2: Context |=> String = (ctx: Context) |=> ctx.value -val f3: (A, B) |=> T = (a: A, b: B) |=> t -``` -The old syntax `implicit (a: A) => B` now creates a closure of a regular function type `A => B` instead of an implicit function type `A |=> B`. This matches the types of implicit closures in Scala 2.x. - -## Example - -Implementing postconditions via `ensuring`: -```scala -object PostConditions { - opaque type WrappedResult[T] = T - - private instance WrappedResult { - def apply[T](x: T): WrappedResult[T] = x - def (x: WrappedResult[T]) unwrap[T]: T = x - } - - def result[T] with (wrapped: WrappedResult[T]): T = wrapped.unwrap - - instance { - def (x: T) ensuring[T] (condition: WrappedResult[T] |=> Boolean): T = { - assert(condition with WrappedResult(x)) - x - } - } -} - -object Test { - import PostConditions._ - val s = List(1, 2, 3).sum.ensuring(result == 6) -} -``` -## Syntax - -Here is the new syntax of parameters, arguments, and implicit function types seen as a delta from the [standard context free syntax of Scala 3](http://dotty.epfl.ch/docs/internals/syntax.html). -``` -ClsParamClause ::= ... - | ‘with’ ‘(’ [ClsParams] ‘)’ -DefParamClause ::= ... - | ‘with’ ‘(’ [DefParams] ‘)’ -Type ::= ... - | [‘erased’] FunArgTypes ‘|=>’ Type -Expr ::= ... - | [‘erased’] FunParams ‘|=>’ Expr -InfixExpr ::= ... - | InfixExpr ‘with’ (InfixExpr | ParArgumentExprs) -``` diff --git a/docs/docs/reference/instances/discussion/discussion.md b/docs/docs/reference/instances/discussion/discussion.md deleted file mode 100644 index ba38f004aa92..000000000000 --- a/docs/docs/reference/instances/discussion/discussion.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -layout: doc-page -title: "Discussion" ---- - -## Summary - -The instance proposal consists of two main parts: - - - Define a new [high-level syntax for instance definitions](./instance-defs.html) that works out better the intent underlying implicit definitions. - - Define a [new syntax for implicit parameters](./context-params.html) that aligns formal parameters and arguments. - - Define [abstract and alias instances](./replacing-implicits.html) and replace all existing usages of `implicit` in the language. - - -## Migration - -New and old syntax would co-exist initially. Rewrite rules could rewrite old syntax to new automatically. This is trivial in the case of implicit parameters and implicit function types. It is a bit more involved in the case of implicit definitions, since more extensive pattern matching is required to recognize a definition that can be rewritten to a instance. - -To make gradual change possible, we allow the new `with` application syntax also for -old style implicit parameters. - -One tricky question concerns context bounds. During the migration period, should they map to old style implicit parameters or new style context parameters? Mapping them to context parameters could break things in difficult to diagnose ways since then an explicit argument for a context bound would be treated as a type error, or, in the worst case, would be constructed as an argument for an `apply` of the result of method with the -context bound. Also, it would remove context bounds from the common subset that is -treated the same in Scala 2 and 3. We therefore opt to map context bounds to old style implicit parameters for the time being. In the future, migrating context bounds implies a three-step process: - - - Step 1: Deprecate passing arguments to evidence parameters defined by context bounds - directly. All such parameters should be passed with `with`. - - Step 2: Remove ability to pass context bound arguments directly. - - Step 3: Map context bounds to context parameters. - -The third part (replacing existing implicits) should be adopted well after the first two parts are implemented. Alias and abstract instances could be introduced together with the other instance definitions, but could also come later. - -## Discussion - -This is a rather sweeping proposal, which will affect most Scala code. Here are some supporting arguments and a summary of alternatives that were considered. - -The instance definition syntax makes the definition of implicit instances clearer and more concise. People have repeatedly asked for specific "typeclass syntax" in Scala. I believe that instance definitions together with extension methods address this ask quite well. - -Probably the most far reaching and contentious changes affect implicit parameters. There might be resistance to change, because the existing scheme seems to work "well enough". However, I believe there is a price to pay for the status quo. The need to write `.apply` occasionally to force implicit arguments is already bad. Worse is the fact that implicits always have to come last, which makes useful program patterns much more cumbersome than before and makes the language less regular. - -Two alternatives to the proposed syntax changes for implicit parameters were considered: - - 1. Leave `implicit` parameters as they are. This suffers from the problems stated - in the [motivation section](./motivation.md). - 2. Leave the syntax of `implicit` parameters but institute two changes: First, applications - of implicit parameters must be via the pseudo method `.explicitly(...)`. Second, there can be more than one implicit parameter list and implicit parameters may precede explicit ones. This fixes most of the discussed problems, but at the expense of a bulky explicit application syntax. Bulk can be a problem, for instance when the programmer tries to - construct an extensive explicit argument tree to figure out what went wrong with a missing - implicit. Another issue is that migration from old to new scheme would be tricky and - would likely take multiple language versions. - -A contentious point is whether we want abstract and alias instances. As an alternative, one would could also keep the current syntax for implicit vals and defs, which can express the same concepts. The main advantage to introduce abstract and alias instances is that it would -allow us to drop implicit definitions altogether. \ No newline at end of file diff --git a/docs/docs/reference/instances/discussion/instance-defs.md b/docs/docs/reference/instances/discussion/instance-defs.md deleted file mode 100644 index d0968ad03144..000000000000 --- a/docs/docs/reference/instances/discussion/instance-defs.md +++ /dev/null @@ -1,186 +0,0 @@ ---- -layout: doc-page -title: "Instance Definitions" ---- - -Instance definitions provide a concise and uniform syntax for defining implicit values. Example: - -```scala -trait Ord[T] { - 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 -} - -instance IntOrd of Ord[Int] { - def (x: Int) compareTo (y: Int) = - if (x < y) -1 else if (x > y) +1 else 0 -} - -instance ListOrd[T: Ord] of Ord[List[T]] { - 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) - } -} -``` - -Instance can be seen as shorthands for what is currently expressed as implicit definitions. The instance definitions above could also have been formulated as implicits as follows: -```scala -implicit object IntOrd extends Ord[Int] { - 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 { - 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] -``` -In fact, a plausible compilation strategy would map the instance definitions given above to exactly these implicit definitions. - -Implicit definitions are kept for the moment but should be be deprecated eventually. As we will see, the only kind of implicit definitions that cannot be directly emulated by instance definitions are implicit conversions. - -Why prefer instance over implicit definitions? Their definitions are shorter, more uniform, and they focus on intent rather than mechanism: I.e. we define an _instance of_ a type, instead of an _implicit object_ that happens to _extend_ a type. Likewise, the `ListOrd` instance is shorter and clearer than the class/implicit def combo that emulates it. Arguably, `implicit` was always a misnomer. An `implicit object` is every bit as explicit as a plain object, it's just that the former is eligible as a synthesized (implicit) argument to an _implicit parameter_. So, "implicit" makes sense as an adjective for arguments and at a stretch for parameters, but not so much for the other kinds of definitions. - -## Instances for Extension Methods - -Instances can also be defined without an `of` clause. A typical application is to use a instance to package some extension methods. Examples: - -```scala -instance StringOps { - def (xs: Seq[String]) longestStrings: Seq[String] = { - val maxLength = xs.map(_.length).max - xs.filter(_.length == maxLength) - } -} - -instance ListOps { - def (xs: List[T]) second[T] = xs.tail.head -} -``` -Instances like these translate to `implicit` objects without an extends clause. - -## Anonymous Instances - -The name of an instance definition can be left out. Examples: -```scala -instance of Ord[Int] { ... } -instance [T: Ord] of Ord[List[T]] { ... } - -instance { - def (xs: List[T]) second[T] = xs.tail.head -} -``` -If the name of an instance is missing, the compiler will synthesize a name from -the type in the of clause, or, if that is missing, from the first defined -extension method. Details remain to be specified. - -## Conditional Instances - -An instance definition can depend on another instance being defined. For instance: -```scala -trait Convertible[From, To] { - def convert(x: From): To -} - -instance [From, To] with (c: Convertible[From, To]) of Convertible[List[From], List[To]] { - def convert(x: List[From]): List[To] = x.map(c.convert) -} -``` - -The `with` clause instance defines required instances. The instance of `Convertible[List[From], List[To]]` above is defined only if an instance of `Convertible[From, To]` exists. -`with` clauses translate to implicit parameters of implicit methods. Here is the expansion of the anonymous instance above in terms of a class and an implicit method (the example demonstrates well the reduction in boilerplate afforded by instance syntax): -```scala -class Convertible_List_List_instance[From, To](implicit c: Convertible[From, To]) -extends Convertible[List[From], List[To]] { - def convert (x: List[From]): List[To] = x.map(c.convert) -} -implicit def Convertible_List_List_instance[From, To](implicit c: Convertible[From, To]) - : Convertible[List[From], List[To]] = - new Convertible_List_List_instance[From, To] -``` -Context bounds in instance definitions also translate to implicit parameters, and therefore they can be represented alternatively as with clauses. For instance, here is an equivalent definition of the `ListOrd` instance: -```scala -instance ListOrd[T] with (ord: Ord[T]) of List[Ord[T]] { ... } -``` -An underscore ‘_’ can be used as the name of a required instance, if that instance does not -need to be referred to directly. For instance, the last `ListOrd` instance could also have been written like this: -```scala -instance ListOrd[T] with (_: Ord[T]) of List[Ord[T]] { ... } -``` - -**Design note:** An alternative to the underscore syntax would be to allow the `name:` part to be left out entirely. I.e. it would then be `instance ListOrd[T] with (Ord[T]) of ...`. I am not yet sure which is preferable. - - -## Typeclass Instances - -Here are some examples of standard typeclass instances: - -Semigroups and monoids: - -```scala -trait SemiGroup[T] { - def (x: T) combine (y: T): T -} -trait Monoid[T] extends SemiGroup[T] { - def unit: T -} - -instance of Monoid[String] { - 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(_)) -``` -Functors and monads: -```scala -trait Functor[F[_]] { - def (x: F[A]) map[A, B] (f: A => B): F[B] -} - -trait Monad[F[_]] extends Functor[F] { - 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] -} - -instance ListMonad of Monad[List] { - 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) -} - -instance ReaderMonad[Ctx] of Monad[[X] => Ctx => X] { - 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 -} -``` - -## Syntax - -Here is the new syntax of instance definitions, seen as a delta from the [standard context free syntax of Scala 3](http://dotty.epfl.ch/docs/internals/syntax.html). -``` -TmplDef ::= ... - | ‘instance’ InstanceDef -InstanceDef ::= [id] InstanceParams [‘of’ ConstrApps] [TemplateBody] -InstanceParams ::= [DefTypeParamClause] {‘with’ ‘(’ [DefParams] ‘)} -``` -The identifier `id` can be omitted only if either the `of` part or the template body is present. If the `of` part is missing, the template body must define at least one extension method. \ No newline at end of file diff --git a/docs/docs/reference/instances/discussion/motivation.md b/docs/docs/reference/instances/discussion/motivation.md deleted file mode 100644 index 8df60ed25258..000000000000 --- a/docs/docs/reference/instances/discussion/motivation.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -layout: doc-page -title: "Motivation" ---- - -### Critique - -Scala's implicits are its most distinguished feature. They are _the_ fundamental way to abstract over context. They represent a single concept with an extremely varied number of use cases, among them: implementing type classes, establishing context, dependency injection, expressing capabilities, computing new types and proving relationships between them. - -At the same time, implicits are also a controversial feature. I believe there are several reasons for this. - -_First_, being very powerful, implicits are easily over-used and mis-used. This observation holds in almost all cases when we talk about _implicit conversions_, which, even though conceptually different, share the same syntax with other implicit definitions. For instance, -regarding the two definitions - - implicit def i1(implicit x: T): C[T] = ... - implicit def i2(x: T): C[T] = ... - -the first of these is a conditional implicit _value_, the second an implicit _conversion_. Conditional implicit values are a cornerstone for expressing type classes, whereas most applications of implicit conversions have turned out to be of dubious value. The problem is that many newcomers to the language start with defining implicit conversions since they are easy to understand and seem powerful and convenient. Scala 3 will put under a language flag both definitions and applications of "undisciplined" implicit conversions between types defined elsewhere. This is a useful step to push back against overuse of implicit conversions. But the problem remains that syntactically, conversions and values just look too similar for comfort. - -_Second_, implicits pose challenges for tooling. The set of available implicits depends on context, so command completion has to take context into account. This is feasible in an IDE but docs like ScalaDoc that are based static web pages can only provide an approximation. Another problem is that failed implicit searches often give very unspecific error messages, in particular if some deeply recursive implicit search has failed. The dotty compiler implements some improvements in this case, but further progress would be desirable. - -_Third_, the syntax of implicit definitions might be a bit too minimal. It consists of a single modifier, `implicit`, that can be attached to a large number of language constructs. A problem with this for newcomers is that it often conveys mechanism better than intent. For instance, a typeclass instance is an implicit object or val if unconditional and an implicit def with implicit parameters if conditional. This describes precisely what the implicit definitions translate to -- just drop the `implicit` modifier, and that's it! But the cues that define intent are rather indirect and can be easily misread, as demonstrated by the definitions of `i1` and `i2` above. - -_Fourth_, the syntax of implicit parameters has also some shortcomings. It starts with the position of `implicit` as a pseudo-modifier that applies to a whole parameter section instead of a single parameter. This represents an irregular case wrt to the rest of Scala's syntax. Furthermore, while implicit _parameters_ are designated specifically, arguments are not. Passing an argument to an implicit parameter looks like a regular application `f(arg)`. This is problematic because it means there can be confusion regarding what parameter gets instantiated in a call. For instance, in -```scala -def currentMap(implicit ctx: Context): Map[String, Int] -``` -one cannot write `currentMap("abc")` since the string "abc" is taken as explicit argument to the implicit `ctx` parameter. One has to write `currentMap.apply("abc")` instead, which is awkward and irregular. For the same reason, a method definition can only have one implicit parameter section and it must always come last. This restriction not only reduces orthogonality, but also prevents some useful program constructs, such as a method with a regular parameter whose type depends on an implicit value. Finally, it's also a bit annoying that implicit parameters must have a name, even though in many cases that name is never referenced. - -None of the shortcomings is fatal, after all implicits are very widely used, and many libraries and applications rely on them. But together, they make code using implicits more cumbersome and less clear than it could be. - -Can implicit function types help? Implicit function types allow to abstract over implicit parameterization. They are a key part of the program to make as many aspects of methods as possible first class. Implicit function types can avoid much of the repetition in programs that use implicits widely. But they do not directly address the issues mentioned here. - -### Alternative Design - -`implicit` is a modifier that gets attached to various constructs. I.e. we talk about implicit vals, defs, objects, parameters, or arguments. This conveys mechanism rather than intent. What _is_ the intent that we want to convey? Ultimately it's "trade types for terms". The programmer specifies a type and the compiler fills in the term matching that type automatically. So the concept we are after would serve to express definitions that provide the canonical _instances_ for certain types. - -The next sections elaborate such an alternative design. It consists of three proposals: - - - A proposal to replace implicit _definitions_ by [instance definitions](./instance-defs.html). - - A proposal for a [new syntax](./context-params.html) of implicit _parameters_ and their _arguments_. - - A proposal to [replace all remaining usages](./replacing-implicits.html) of `implicit` in the language. - -The first two proposals are independent of each other. The last one would work only if the first two are adopted. -A [discussion page](./discussion.html) summarizes and evaluates the proposal. diff --git a/docs/docs/reference/instances/discussion/replacing-implicits.md b/docs/docs/reference/instances/discussion/replacing-implicits.md deleted file mode 100644 index 514379d2e75b..000000000000 --- a/docs/docs/reference/instances/discussion/replacing-implicits.md +++ /dev/null @@ -1,111 +0,0 @@ ---- -layout: doc-page -title: "Replacing Implicits" ---- - -The previous two pages proposed high-level syntax for implicit definitions and a new syntax for implicit parameters. - -This addresses all the issues mentioned in the [Motivation](./motivation.md), but it leaves us with two related constructs: new style instance definitions and context parameters and traditional implicits. This page discusses what would be needed to get rid of `implicit` entirely. - -## Abstract and Alias Instances - -Instance definitions can be abstract. -As an example of an abstract instance, consider the following fragment that's derived from Scala's Tasty extractor framework: -```scala -trait TastyAPI { - type Symbol - trait SymDeco { - def (sym: Symbol) name: Name - def (sym: Symbol) tpe: Type - } - instance symDeco: SymDeco -} -``` -Here, `symDeco` is available as a instance of the `SymDeco` trait but its actual implementation -is deferred to subclasses of the `TastyAPI` trait. - -An example of an alias instance would be an implementation of `symDeco` in terms of some internal compiler structure: -```scala -trait TastyImpl extends TastyAPI { - instance symDeco: SymDeco = compilerSymOps -} -``` -Note that the result type of an abstract or alias instance is introduced with a colon instead of an `of`. This seems more natural since it evokes the similarity to implicit parameters, whose type is also given following a `:`. It also avoids the syntactic ambiguity with an instance definition of a class that does not add any new definitions. I.e. -```scala -instance a of C // concrete instance of class C, no definitions added -instance b: C // abstract instance of class C -``` -Further examples of alias instances: -```scala -instance ctx = outer.ctx -instance ctx: Context = outer.ctx -instance byNameCtx with (): Context = outer.ctx -instance f[T]: C[T] = new C[T] -instance g with (ctx: Context): D = new D(ctx) -``` -As another example, if one had already defined classes `IntOrd` and `ListOrd`, instances for them could be defined as follows: -```scala -class IntOrd extends Ord[Int] { ... } -class ListOrd[T: Ord] extends Ord[List[T]] { ... } - -instance intOrd: Ord[Int] = new IntOrd -instance listOrd[T: Ord]: Ord[List[T]] = new ListOrd[T] -``` -The result type of a alias instance is mandatory unless the instance definition -occurs as a statement in a block and lacks any type or value parameters. This corresponds to the same restriction for implicit vals in Dotty. - -Abstract instances are equivalent to abstract implicit defs. Alias instances are equivalent to implicit defs if they are parameterized or to implicit vals otherwise. For instance, the instances defined so far in this section are equivalent to: -```scala -implicit def symDeco: SymDeco -implicit val symDeco: SymDeco = compilerSymOps - -implicit val ctx = outer.ctx -implicit val ctx: Context = outer.ctx -implicit def byNameCtx: Ctx = outer.ctx -implicit def f[T]: C[T] = new C[T] -implicit def g(implicit ctx: Context): D = new D(ctx) - -implicit val intOrd: Ord[Int] = new IntOrd -implicit def listOrd(implicit ev: Ord[T]): Ord[List[T]] = new ListOrd[T] -``` -The `lazy` modifier is applicable to unparameterized alias instances. If present, the resulting implicit val is lazy. For instance, -```scala -lazy instance intOrd2: Ord[Int] = new IntOrd -``` -would be equivalent to -```scala -lazy implicit val intOrd2: Ord[Int] = new IntOrd -``` - -## Implicit Conversions and Classes - -The only use cases that are not yet covered by the proposal are implicit conversions and implicit classes. We do not propose to use `instance` in place of `implicit` for these, since that would bring back the uncomfortable similarity between implicit conversions and parameterized implicit aliases. However, there is a way to drop implicit conversions entirely. Scala 3 already [defines](https://github.com/lampepfl/dotty/pull/2065) a class `Conversion` whose instances are available as implicit conversions. -```scala - abstract class Conversion[-T, +U] extends Function1[T, U] -``` -One can define all implicit conversions as instances of this class. E.g. -```scala -instance StringToToken of Conversion[String, Token] { - def apply(str: String): Token = new KeyWord(str) -} -``` -The fact that this syntax is more verbose than simple implicit defs could be a welcome side effect since it might dampen any over-enthusiasm for defining implicit conversions. - -That leaves implicit classes. Most use cases of implicit classes are probably already covered by extension methods. For the others, one could always fall back to a pair of a regular class and an `Conversion` instance. It would be good to do a survey to find out how many classes would be affected. - -## Summoning an Instance - -Besides `implicit`, there is also `implicitly`, a method defined in `Predef` that computes an implicit value for a given type. A possible replacement could be `instanceOf`. Or, keeping with common usage, one could introduce the name `summon` for this operation. So `summon[T]` summons an instance of `T`, in the same way as `implicitly[T]` did. The definition of `summon` is straightforward: -```scala -def summon[T] with (x: T) = x -``` - -## Syntax - -The syntax changes for this page are summarized as follows: -``` -InstanceDef ::= ... - | id InstanceParams ‘:’ Type ‘=’ Expr - | id ‘=’ Expr -``` -In addition, the `implicit` modifier is removed together with all [productions]((http://dotty.epfl.ch/docs/internals/syntax.html) that reference it. diff --git a/docs/docs/reference/instances/implicit-function-types-spec.md b/docs/docs/reference/instances/implicit-function-types-spec.md deleted file mode 100644 index 80f50964e586..000000000000 --- a/docs/docs/reference/instances/implicit-function-types-spec.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -layout: doc-page -title: "Implicit Function Types - More Details" ---- - -Initial implementation in (#1775)[https://github.com/lampepfl/dotty/pull/1775]. - -## Syntax - - Type ::= ... - | FunArgTypes `|=>' Type - Expr ::= ... - | FunParams `|=>' Expr - -Implicit function types associate to the right, e.g. -`S |=> T => U` is the same as `S => (T => U)`. - -## Implementation - -Implicit function types are shorthands for class types that define `apply` -methods with implicit parameters. Specifically, the `N`-ary function type -`T1, ..., TN => R` is a shorthand for the class type -`ImplicitFunctionN[T1 , ... , TN, R]`. Such class types are assumed to have -the following definitions, for any value of `N >= 1`: -```scala -package scala -trait ImplicitFunctionN[-T1 , ... , -TN, +R] { - def apply with (x1: T1 , ... , xN: TN): R -} -``` -Implicit function types erase to normal function types, so these classes are -generated on the fly for typechecking, but not realized in actual code. - -Anonymous implicit function values `(x1: T1, ..., xn: Tn) |=> e` map -implicit parameters `xi` of types `Ti` to a result given by expression `e`. -The scope of each implicit parameter `xi` is `e`. Implicit parameters must -have pairwise distinct names. - -If the expected type of the anonymous implicit function is of the form -`scala.ImplicitFunctionN[S1, ..., Sn, R]`, the expected type of `e` is `R` and -the type `Ti` of any of the parameters `xi` can be omitted, in which case `Ti -= Si` is assumed. If the expected type of the anonymous implicit function is -some other type, all implicit parameter types must be explicitly given, and -the expected type of `e` is undefined. The type of the anonymous implicit -function is `scala.ImplicitFunctionN[S1, ...,Sn, T]`, where `T` is the widened -type of `e`. `T` must be equivalent to a type which does not refer to any of -the implicit parameters `xi`. - -The anonymous implicit function is evaluated as the instance creation -expression: - - new scala.ImplicitFunctionN[T1, ..., Tn, T] { - def apply with (x1: T1, ..., xn: Tn): T = e - } - -In the case of a single untyped implicit parameter, `(x) |=> e` can be -abbreviated to `x |=> e`. - -A implicit parameter may also be a wildcard represented by an underscore `_`. In -that case, a fresh name for the parameter is chosen arbitrarily. - -Note: The closing paragraph of the [Anonymous Functions section](https://www -.scala-lang.org/files/archive/spec/2.12/06-expressions.html#anonymous- -functions) of the Scala 2.12 is subsumed by implicit function types and should -be removed. - -Anonymous implicit functions `(x1: T1, ..., xn: Tn) |=> e` are -automatically inserted around any expression `e` whose expected type is -`scala.ImplicitFunctionN[T1, ..., Tn, R]`, unless `e` is -itself a function literal. This is analogous to the automatic -insertion of `scala.Function0` around expressions in by-name argument position. - -Implicit functions generalize to `N > 22` in the same way that functions do, -see [the corresponding -documentation](https://dotty.epfl.ch/docs/reference/dropped-features/limit22.html). - -## Examples - -See the section on Expressiveness from [Simplicitly: foundations and -applications of implicit function -types](https://dl.acm.org/citation.cfm?id=3158130). I've extracted it in [this -Gist](https://gist.github.com/OlivierBlanvillain/234d3927fe9e9c6fba074b53a7bd9 -592), it might easier to access than the pdf. - -### Type Checking - -After desugaring no additional typing rules are required for implicit function -types. diff --git a/docs/docs/reference/instances/implicit-function-types.md b/docs/docs/reference/instances/implicit-function-types.md deleted file mode 100644 index 4474711990bd..000000000000 --- a/docs/docs/reference/instances/implicit-function-types.md +++ /dev/null @@ -1,112 +0,0 @@ ---- -layout: doc-page -title: "Implicit Function Types and Closures" ---- - -An implicit function type describes functions with implicit (context) parameters. Example: - - type Contextual[T] = Context |=> T - -A value of implicit function type is applied to context arguments, in -the same way a method with context parameters is applied. For instance: - - implicit val ctx: Context = ... - - def f(x: Int): Contextual[Int] = ... - - f(2) with ctx // explicit argument - f(2) // argument left implicit - -Conversely, if the expected type of an expression `E` is an implicit -function type `(T_1, ..., T_n) |=> U` and `E` is not already an -implicit function value, `E` is converted to an implicit function value -by rewriting to - - (x_1: T1, ..., x_n: Tn) |=> E - -where the names `x_1`, ..., `x_n` are arbitrary. Implicit closures are written -with a `|=>` connective instead of `=>` for normal closures. They differ from normal closures in two ways: - - 1. Their parameters are implicit context parameters - 2. Their types are implicit function types. - -For example, continuing with the previous definitions, - - def g(arg: Contextual[Int]) = ... - - g(22) // is expanded to g(ctx |=> 22) - - g(f(2)) // is expanded to g(ctx |=> f(2) with ctx) - - g(ctx |=> f(22) with ctx) // is left as it is - -Implicit function types have considerable expressive power. For -instance, here is how they can support the "builder pattern", where -the aim is to construct tables like this: - - table { - row { - cell("top left") - cell("top right") - } - row { - cell("bottom left") - cell("bottom right") - } - } - -The idea is to define classes for `Table` and `Row` that allow -addition of elements via `add`: - - class Table { - val rows = new ArrayBuffer[Row] - def add(r: Row): Unit = rows += r - override def toString = rows.mkString("Table(", ", ", ")") - } - - class Row { - val cells = new ArrayBuffer[Cell] - def add(c: Cell): Unit = cells += c - override def toString = cells.mkString("Row(", ", ", ")") - } - - case class Cell(elem: String) - -Then, the `table`, `row` and `cell` constructor methods can be defined -in terms of implicit function types to avoid the plumbing boilerplate -that would otherwise be necessary. - - def table(init: Table |=> Unit) = { - instance t of Table - init - t - } - - def row(init: Row |=> Unit) with (t: Table) = { - instance r of Row - init - t.add(r) - } - - def cell(str: String) with (r: Row) = - r.add(new Cell(str)) - -With that setup, the table construction code above compiles and expands to: - - table { $t: Table |=> - row { $r: Row |=> - cell("top left") with $r - cell("top right") with $r - } with $t - row { $r: Row |=> - cell("bottom left") with $r - cell("bottom right") with $r - } with $t - } - -### Reference - -For more info, see the [blog article](https://www.scala-lang.org/blog/2016/12/07/implicit-function-types.html), -(which uses a different syntax that has been superseded). - -[More details](./implicit-function-types-spec.html) diff --git a/docs/docs/reference/instances/instance-defs.md b/docs/docs/reference/instances/instance-defs.md deleted file mode 100644 index 17436a508ca6..000000000000 --- a/docs/docs/reference/instances/instance-defs.md +++ /dev/null @@ -1,195 +0,0 @@ ---- -layout: doc-page -title: "Instance Definitions" ---- - -Instance definitions provide a concise and uniform syntax for defining implicit values. Example: - -```scala -trait Ord[T] { - 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 -} - -instance IntOrd of Ord[Int] { - def (x: Int) compareTo (y: Int) = - if (x < y) -1 else if (x > y) +1 else 0 -} - -instance ListOrd[T: Ord] of Ord[List[T]] { - 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) - } -} -``` -Instance definitions can be seen as shorthands for what is currently expressed with implicit object and method definitions. -For instance, the definition of instance `IntOrd` above defines an implicit value of type `Ord[Int]`. It is hence equivalent -to the following implicit object definition: -```scala -implicit object IntOrd extends Ord[Int] { - def (x: Int) compareTo (y: Int) = - if (x < y) -1 else if (x > y) +1 else 0 -} -``` -The definition of instance `ListOrd` defines an ordering for `List[T]` provided there is an ordering for type `T`. With existing -implicits, this could be expressed as a pair of a class and an implicit method: -```scala -class ListOrd[T: Ord] extends Ord[List[T]] { - 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] -``` -## Instances for Extension Methods - -Instances can also be defined without an `of` clause. A typical application is to use a instance to package some extension methods. Examples: - -```scala -instance StringOps { - def (xs: Seq[String]) longestStrings: Seq[String] = { - val maxLength = xs.map(_.length).max - xs.filter(_.length == maxLength) - } -} - -instance ListOps { - def (xs: List[T]) second[T] = xs.tail.head -} -``` -## Anonymous Instances - -The name of an instance definition can be left out. Examples: -```scala -instance of Ord[Int] { ... } -instance [T: Ord] of Ord[List[T]] { ... } - -instance { - def (xs: List[T]) second[T] = xs.tail.head -} -``` -If the name of an instance is missing, the compiler will synthesize a name from -the type in the of clause, or, if that is missing, from the first defined -extension method. - -## Conditional Instances - -An instance definition can depend on another instance being defined. Example: -```scala -trait Conversion[-From, +To] { - def apply(x: From): To -} - -instance [S, T] with (c: Conversion[S, T]) of Conversion[List[S], List[T]] { - def convert(x: List[From]): List[To] = x.map(c.apply) -} -``` -This defines an implicit conversion from `List[S]` to `List[T]` provided there is an implicit conversion from `S` to `T`. -The `with` clause instance defines required instances. The instance of `Conversion[List[From], List[To]]` above is defined only if an instance of `Conversion[From, To]` exists. - -Context bounds in instance definitions also translate to implicit parameters, and therefore they can be represented alternatively as with clauses. For instance, here is an equivalent definition of the `ListOrd` instance: -```scala -instance ListOrd[T] with (ord: Ord[T]) of List[Ord[T]] { ... } -``` -The name of a parameter in a `with` clause can also be left out, as shown in the following variant of `ListOrd`: -```scala -instance ListOrd[T] with Ord[T] of List[Ord[T]] { ... } -``` -As usual one can then infer to implicit parameter only indirectly, by passing it as implicit argument to another function. - -## Typeclass Instances - -Instance definitions allow a concise and natural expression of typeclasses. -Here are some examples of standard typeclass instances: - -Semigroups and monoids: - -```scala -trait SemiGroup[T] { - def (x: T) combine (y: T): T -} -trait Monoid[T] extends SemiGroup[T] { - def unit: T -} -object Monoid { - def apply[T] = implicitly[Monoid[T]] -} - -instance of Monoid[String] { - def (x: String) combine (y: String): String = x.concat(y) - def unit: String = "" -} - -def sum[T: Monoid](xs: List[T]): T = - xs.foldLeft(Monoid[T].unit)(_.combine(_)) -``` -Functors and monads: -```scala -trait Functor[F[_]] { - def (x: F[A]) map[A, B] (f: A => B): F[B] -} - -trait Monad[F[_]] extends Functor[F] { - 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] -} - -instance ListMonad of Monad[List] { - 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) -} - -instance ReaderMonad[Ctx] of Monad[[X] => Ctx => X] { - 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 -} -``` -## Alias Instances - -An alias instance creates an instance that is equal to some expression. E.g., -``` -instance ctx of ExecutionContext = currentThreadPool().context -``` -Here, we create an instance `ctx` of type `ExecutionContext` that resolves to the -right hand side `currentThreadPool().context`. Each time an instance of `ExecutionContext` -is demanded, the result of evaluating the right-hand side expression is returned. The instance definition is equivalent to the following implicit definition: -``` -final implicit def ctx: ExecutionContext = currentThreadPool().context -``` -Alias instances may be anonymous, e.g. -``` -instance of Position = enclosingTree.position -``` -An alias instance can have type and context parameters just like any other instance definition, but it can only implement a single type. - -## Syntax - -Here is the new syntax of instance definitions, seen as a delta from the [standard context free syntax of Scala 3](http://dotty.epfl.ch/docs/internals/syntax.html). -``` -TmplDef ::= ... - | ‘instance’ InstanceDef -InstanceDef ::= [id] InstanceParams InstanceBody -InstanceParams ::= [DefTypeParamClause] {InstParamClause} -InstParamClause ::= ‘with’ (‘(’ [DefParams] ‘)’ | ContextTypes) -InstanceBody ::= [‘of’ ConstrApp {‘,’ ConstrApp }] [TemplateBody] - | ‘of’ Type ‘=’ Expr -ContextTypes ::= RefinedType {‘,’ RefinedType} -``` -The identifier `id` can be omitted only if either the `of` part or the template body is present. If the `of` part is missing, the template body must define at least one extension method. diff --git a/docs/docs/reference/instances/replacing-implicits.md b/docs/docs/reference/instances/replacing-implicits.md deleted file mode 100644 index 93bf50c1b75f..000000000000 --- a/docs/docs/reference/instances/replacing-implicits.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -layout: doc-page -title: "Replacing Implicits" ---- - -The previous pages describe a new, high-level syntax for implicit definitions, parameters, function literals, and function types. With the exception of context parameters - -These idioms can by-and-large be mapped to existing implicits. The only exception concerns context parameters which give genuinely more freedom in the way parameters can be organized. The new idioms are preferable to existing implicits since they are both more concise and better behaved. The better expressiveness comes at a price, however, since it leaves us with two related constructs: new style instance definitions and context parameters and traditional implicits. This page discusses what would be needed to get rid of `implicit` entirely. - -The contents of this page are more tentative than the ones of the previous pages. The concepts described in the previous pages are useful independently whether the changes on this page are adopted. - -The current Dotty implementation implements the new concepts described on this page (alias instances and the summon method), but it does not remove any of the old-style implicit constructs. It cannot do this since support -for old-style implicits is an essential part of the common language subset of Scala 2 and Scala 3.0. Any deprecation and subsequent removal of these constructs would have to come later, in a version following 3.0. The `implicit` modifier can be removed from the language at the end of this development, if it happens. - -## Alias Instances - -An alias instance creates an instance that is equal to some expression. -``` -instance ctx of ExecutionContext = currentThreadPool().context -``` -Here, we create an instance `ctx` of type `ExecutionContext` that resolves to the -right hand side `currentThreadPool().context`. Each time an instance of `ExecutionContext` -is demanded, the result of evaluating the right-hand side expression is returned. The instance definition is equivalent to the following implicit definition: -``` -final implicit def ctx: ExecutionContext = currentThreadPool().context -``` -Alias instances may be anonymous, e.g. -``` -instance of Position = enclosingTree.position -``` -An alias instance can have type and context parameters just like any other instance definition, but it can only implement a single type. - -## Replaced: Implicit Conversions - -Implicit conversions using the `implicit def` syntax are no longer needed, since they -can be expressed as instances of the `scala.Conversion` class: This class is defined in package `scala` as follows: -```scala -abstract class Conversion[-T, +U] extends (T => U) -``` -For example, here is an implicit conversion from `String` to `Token`: -```scala -instance of Conversion[String, Token] { - def apply(str: String): Token = new KeyWord(str) -} -``` -The fact that this syntax is more verbose than simple implicit defs could be a welcome side effect since it might dampen any over-enthusiasm for defining implicit conversions. - -## Dropped: Implicit Classes - -Most use cases of implicit classes are already covered by extension methods. For the others, one can always fall back to a pair of a regular class and a `Conversion` instance. - -## Summoning an Instance - -Besides `implicit`, there is also `implicitly`, a method defined in `Predef` that computes an implicit value for a given type. We propose to rename this operation to `summon`. So `summon[T]` summons an instance of `T`, in the same way as `implicitly[T]` did. The definition of `summon` is straightforward: -```scala -def summon[T] with (x: T) = x -``` - -## Syntax - -The syntax changes for this page are summarized as follows: -``` -InstanceBody ::= ... - | ‘of’ Type ‘=’ Expr -``` -In addition, the `implicit` modifier is removed together with all [productions]((http://dotty.epfl.ch/docs/internals/syntax.html) that reference it. - -## Further Reading - -Here is the [original proposal](./discussion/motivation.html) that makes the case for the changes described in these pages. diff --git a/docs/docs/reference/other-new-features/extension-methods.md b/docs/docs/reference/other-new-features/extension-methods.md deleted file mode 100644 index 3545a99144d2..000000000000 --- a/docs/docs/reference/other-new-features/extension-methods.md +++ /dev/null @@ -1,302 +0,0 @@ ---- -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 an eligible implicit value at the point of the application. - -As an example, consider an extension method `longestStrings` on `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 an implicit instance of `StringSeqOps`, like this: -```scala -implicit object ops1 extends StringSeqOps -``` -Then -```scala -List("here", "is", "a", "list").longestStrings -``` -is legal everywhere `StringSeqOps1` is available as an implicit value. 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 an implicit value `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 an eligible implicit (i.e. it is visible at the point of call or it is defined in the companion object of `Circle`). - -### 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: -``` - def (x: String) < (y: String) = ... - def (x: Elem) +: (xs: Seq[Elem]) = ... - - "ab" + "c" - 1 +: List(2, 3) -``` -The two definitions above translate to -``` - 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: - -```scala -def (xs: List[T]) second [T] = xs.tail.head -``` - -or: - - -```scala -def (xs: List[List[T]]) flattened [T] = xs.foldLeft[List[T]](Nil)(_ ++ _) -``` - -or: - -```scala -def (x: T) + [T : Numeric](y: T): T = implicitly[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. - -### A Larger Example - -As a larger example, here is a way to define constructs for checking arbitrary postconditions using `ensuring` so that the checked result can be referred to simply by `result`. The example combines opaque aliases, implicit function types, and extensions to provide a zero-overhead abstraction. - -```scala -object PostConditions { - opaque type WrappedResult[T] = T - - private object WrappedResult { - def wrap[T](x: T): WrappedResult[T] = x - def unwrap[T](x: WrappedResult[T]): T = x - } - - def result[T](implicit er: WrappedResult[T]): T = WrappedResult.unwrap(er) - - implicit object Ensuring { - def (x: T) ensuring [T](condition: implicit WrappedResult[T] => Boolean): T = { - implicit val wrapped = WrappedResult.wrap(x) - assert(condition) - x - } - } -} - -object Test { - import PostConditions._ - val s = List(1, 2, 3).sum.ensuring(result == 6) -} -``` -**Explanations**: We use an implicit function type `implicit WrappedResult[T] => Boolean` -as the type of the condition of `ensuring`. An argument condition to `ensuring` such as -`(result == 6)` will therefore have an implicit value of type `WrappedResult[T]` in scope -to pass along to the `result` method. `WrappedResult` is a fresh type, to make sure that we do not get unwanted implicits in scope (this is good practice in all cases where implicit parameters are involved). Since `WrappedResult` is an opaque type alias, its values need not be boxed, and since `ensuring` is added as an extension method, its argument does not need boxing either. Hence, the implementation of `ensuring` is as about as efficient as the best possible code one could write by hand: - - { val result = List(1, 2, 3).sum - assert(result == 6) - result - } - -**A note on formatting** Having a parameter section in front of the defined -method name makes it visually harder to discern what is being defined. To address -that problem, it is recommended that the name of an extension method is -preceded by a space and is also followed by a space if there are more parameters -to come. - -### Extension Methods and Type Classes - -The rules for expanding extension methods make sure that they work seamlessly with type classes. For instance, consider `SemiGroup` and `Monoid`. -```scala - // Two type classes: - trait SemiGroup[T] { - def (x: T) combine(y: T): T - } - trait Monoid[T] extends SemiGroup[T] { - def unit: T - } - object Monoid { - // An instance declaration: - implicit object StringMonoid extends Monoid[String] { - def (x: String) combine (y: String): String = x.concat(y) - def unit: String = "" - } - } - - // Abstracting over a typeclass with a context bound: - def sum[T: Monoid](xs: List[T]): T = - xs.foldLeft(implicitly[Monoid[T]].unit)(_.combine(_)) -``` - -In the last line, the call to `_.combine(_)` expands to `(x1, x2) => x1.combine(x2)`, -which expands in turn to `(x1, x2) => ev.combine(x1, x2)` where `ev` is the implicit -evidence parameter summoned by the context bound `[T: Monoid]`. This works since -extension methods apply everywhere their enclosing object is available as an implicit. - -To avoid having to write `implicitly[Monoid[T]].unit` to access the `unit` method in `Monoid[T]`, -we can make `unit` itself an extension method on the `Monoid` _companion object_, -as shown below: - -```scala - trait Monoid[T] extends SemiGroup[T] { - def (self: Monoid.type) unit: T - } - object Monoid { - implicit object StringMonoid extends Monoid[String] { - def (x: String) combine (y: String): String = x.concat(y) - def (self: Monoid.type) unit: String = "" - } - } -``` - -This allows us to write `Monoid.unit` instead of `implicitly[Monoid[T]].unit`, -letting the expected type distinguish which instance we want to use: - -```scala - def sum[T: Monoid](xs: List[T]): T = - xs.foldLeft(Monoid.unit)(_.combine(_)) -``` - - -### Generic Extension Classes - -As another example, consider implementations of an `Ord` type class with a `minimum` value: -```scala - trait Ord[T] { - 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 - val minimum: T - } - - implicit object IntOrd extends Ord[Int] { - def (x: Int) compareTo (y: Int) = - if (x < y) -1 else if (x > y) +1 else 0 - val minimum = Int.MinValue - } - - class ListOrd[T: Ord] extends Ord[List[T]] { - 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) - } - val minimum: List[T] = Nil - } - implicit def ListOrd[T: Ord]: ListOrd[T] = new ListOrd[T] - - - def max[T: Ord](x: T, y: T): T = if (x < y) y else x - - def max[T: Ord](xs: List[T]): T = (implicitly[Ord[T]].minimum /: xs)(max(_, _)) -``` - -### Higher Kinds - -Extension methods generalize to higher-kinded types without requiring special provisions. Example: - -```scala - trait Functor[F[_]] { - def (x: F[A]) map [A, B](f: A => B): F[B] - } - - trait Monad[F[_]] extends Functor[F] { - 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] - } - - implicit object ListMonad extends Monad[List] { - 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) - } - - implicit class ReaderMonad[Ctx] extends Monad[[X] => Ctx => X] { - 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 - } -``` -### Syntax - -The required syntax extension just adds one clause for extension methods relative -to the [current syntax](https://github.com/lampepfl/dotty/blob/master/docs/docs/internals/syntax.md). -``` -DefSig ::= ... - | ‘(’ DefParam ‘)’ [nl] id [DefTypeParamClause] DefParamClauses -``` - - - - diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 6672df319fc3..c0d67dab2276 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -39,10 +39,29 @@ sidebar: url: docs/reference/enums/adts.html - title: Translation url: docs/reference/enums/desugarEnums.html - - title: Other New Features - subsection: + - title: Contextual Abstractions + - title: Implied Instances + url: docs/reference/contextual/instance-defs.html + - title: Inferable Parameters + url: docs/reference/contextual/inferable-params.html + - title: Context Bounds + url: docs/reference/contextual/context-bounds.html + - title: Extension Methods + url: docs/reference/contextual/extension-methods.html + - title: Implementing Typeclasses + url: docs/reference/contextual/typeclasses.html - title: Typeclass Derivation url: docs/reference/derivation.html + - title: Context Queries + url: docs/reference/contextual/query-types.html + - title: Implicit Conversions + url: docs/reference/contextual/conversions.html + - title: Inferable By-Name Parameters + url: docs/reference/contextual/inferable-by-name-parameters.html + - title: Relationship with Scala 2 Implicits + url: docs/reference/contextual/relationship-implicits.html + - title: Other New Features + subsection: - title: Multiversal Equality url: docs/reference/other-new-features/multiversal-equality.html - title: Trait Parameters @@ -55,10 +74,6 @@ sidebar: url: docs/reference/other-new-features/tasty-reflect.html - title: Opaque Type Aliases url: docs/reference/other-new-features/opaques.html - - title: Extension Methods - url: docs/reference/other-new-features/extension-methods.html - - title: By-Name Implicits - url: docs/reference/other-new-features/implicit-by-name-parameters.html - title: Auto Parameter Tupling url: docs/reference/other-new-features/auto-parameter-tupling.html - title: Named Type Arguments @@ -67,20 +82,6 @@ sidebar: url: docs/reference/other-new-features/erased-terms.html - title: Kind Polymorphism url: docs/reference/other-new-features/kind-polymorphism.html - - title: New Implicits - subsection: - - title: Instance Definitions - url: docs/reference/instances/instance-defs.html - - title: Context Parameters - url: docs/reference/instances/context-params.html - - title: Implicit Function Types - url: docs/reference/instances/implicit-function-types.html - - title: Implicit Conversions - url: docs/reference/instances/implicit-conversions.html - - title: Replacing Implicits - url: docs/reference/instances/replacing-implicits.html - - title: Discussion - url: docs/reference/instances/discussion.html - title: Other Changed Features subsection: - title: Volatile Lazy Vals diff --git a/language-server/test/dotty/tools/languageserver/DiagnosticsTest.scala b/language-server/test/dotty/tools/languageserver/DiagnosticsTest.scala index 4f9b8f47a232..85a3895189e9 100644 --- a/language-server/test/dotty/tools/languageserver/DiagnosticsTest.scala +++ b/language-server/test/dotty/tools/languageserver/DiagnosticsTest.scala @@ -18,10 +18,10 @@ class DiagnosticsTest { @Test def diagnosticMissingLambdaBody: Unit = code"""object Test { - | Nil.map(x => x).filter(x$m1 =>$m2) - |$m3}""".withSource + | Nil.map(x => x).filter(x$m1 =>$m2)$m3 + |}""".withSource .diagnostics(m1, - (m2 to m3, "expression expected", Error, Some(IllegalStartSimpleExprID)), + (m2 to m2, "expression expected", Error, Some(IllegalStartSimpleExprID)), (m1 to m1, """Found: Null |Required: Boolean""".stripMargin, Error, Some(TypeMismatchID)) ) diff --git a/language-server/test/dotty/tools/languageserver/util/CodeTester.scala b/language-server/test/dotty/tools/languageserver/util/CodeTester.scala index 20bf80a3d543..44f553832e89 100644 --- a/language-server/test/dotty/tools/languageserver/util/CodeTester.scala +++ b/language-server/test/dotty/tools/languageserver/util/CodeTester.scala @@ -237,7 +237,7 @@ class CodeTester(projects: List[Project]) { private def doAction(action: Action): this.type = { try { - action.execute() with (testServer, testServer.client, positions) + action.execute() given (testServer, testServer.client, positions) } catch { case ex: AssertionError => val sourcesStr = @@ -252,7 +252,7 @@ class CodeTester(projects: List[Project]) { | |$sourcesStr | - |while executing action: ${action.show with positions} + |while executing action: ${action.show given positions} | """.stripMargin val assertionError = new AssertionError(msg + ex.getMessage) diff --git a/language-server/test/dotty/tools/languageserver/util/PositionContext.scala b/language-server/test/dotty/tools/languageserver/util/PositionContext.scala index 1697e68eb2d1..b892cd978181 100644 --- a/language-server/test/dotty/tools/languageserver/util/PositionContext.scala +++ b/language-server/test/dotty/tools/languageserver/util/PositionContext.scala @@ -24,5 +24,5 @@ class PositionContext(positionMap: Map[CodeMarker, (TestFile, Int, Int)]) { } object PositionContext { - type PosCtx[T] = PositionContext |=> T + type PosCtx[T] = given PositionContext => T } diff --git a/language-server/test/dotty/tools/languageserver/util/actions/Action.scala b/language-server/test/dotty/tools/languageserver/util/actions/Action.scala index 209960902d91..2549a15c40a4 100644 --- a/language-server/test/dotty/tools/languageserver/util/actions/Action.scala +++ b/language-server/test/dotty/tools/languageserver/util/actions/Action.scala @@ -11,7 +11,7 @@ import PositionContext._ * definition, etc.) */ trait Action { - type Exec[T] = (TestServer, TestClient, PositionContext) |=> T + type Exec[T] = given (TestServer, TestClient, PositionContext) => T /** Execute the action. */ def execute(): Exec[Unit] diff --git a/library/src/dotty/DottyPredef.scala b/library/src/dotty/DottyPredef.scala index 0bdd5719c39d..be0c1af3ad94 100644 --- a/library/src/dotty/DottyPredef.scala +++ b/library/src/dotty/DottyPredef.scala @@ -37,5 +37,5 @@ object DottyPredef { */ @forceInline def valueOf[T](implicit vt: ValueOf[T]): T = vt.value - @forceInline def summon[T](implicit x: T) = x + @forceInline def infer[T](implicit x: T) = x } diff --git a/tests/neg/contextual-params.scala b/tests/neg/contextual-params.scala deleted file mode 100644 index 2d44284955d0..000000000000 --- a/tests/neg/contextual-params.scala +++ /dev/null @@ -1,22 +0,0 @@ -object Test { - - case class C(x: Int) - - def f(x: Int) with (c: C) = x + c.x - - def g(x: Int) with (c: C) (y: Int) = x + c.x + y - - implicit object C extends C(11) - - f(1) - f(1) with C - f with 2 // error - f(1)(C) // error - - g(1)(2) // OK - (g(1) with C)(2) // OK - g(1) with 2 // error - g(1) with C with 2 // error - g(1)(C)(2) // error - g(1)(C) with 2 // error -} \ No newline at end of file diff --git a/tests/neg/i2006.scala b/tests/neg/i2006.scala index 7142da89619a..11740777ab9c 100644 --- a/tests/neg/i2006.scala +++ b/tests/neg/i2006.scala @@ -4,7 +4,7 @@ object Test { inline def bar(f: ImplicitFunction1[Int, Int]) = f // error def main(args: Array[String]) = { - foo(thisTransaction |=> 43) - bar(thisTransaction |=> 44) + foo(given thisTransaction => 43) + bar(given thisTransaction => 44) } } diff --git a/tests/neg/i2146.scala b/tests/neg/i2146.scala index 32be8734efde..05086d78dc58 100644 --- a/tests/neg/i2146.scala +++ b/tests/neg/i2146.scala @@ -2,7 +2,7 @@ object Test { case class A() case class B() - def foo[A, B]: A |=> B |=> Int = { b: B |=> - 42 // error: found Int, required: A |=> B |=> Int + def foo[A, B]: given A => given B => Int = { given b: B => + 42 // error: found Int, required: given A => given B => Int } } diff --git a/tests/neg/i2514.scala b/tests/neg/i2514.scala index c05c4b434f42..e1d58a87d20f 100644 --- a/tests/neg/i2514.scala +++ b/tests/neg/i2514.scala @@ -1,7 +1,7 @@ object Foo { def foo(): Int = { val f: implicit Int => Int = (implicit x: Int) => 2 * x // error // error - f with 2 + f given 2 } val f = (implicit x: Int) => x // error // error diff --git a/tests/neg/i2514a.scala b/tests/neg/i2514a.scala index 59c954f31a25..96de78aabc01 100644 --- a/tests/neg/i2514a.scala +++ b/tests/neg/i2514a.scala @@ -1,10 +1,10 @@ object Foo { def foo(): Int = { - val f: Int |=> Int = (x: Int) |=> 2 * x - f with 2 + val f: given Int => Int = given (x: Int) => 2 * x + f given 2 } val f = implicit (x: Int) => x - ((x: Int) |=> x): (Int |=> Int) // error: no implicit argument found + (given (x: Int) => x): (given Int => Int) // error: no implicit argument found } diff --git a/tests/neg/i2642.scala b/tests/neg/i2642.scala index bb52d06c0c3e..8a441dc0276c 100644 --- a/tests/neg/i2642.scala +++ b/tests/neg/i2642.scala @@ -1,10 +1,10 @@ object Foo { - type X = () |=> Int // now ok, used to be: implicit function needs parameters + type X = given () => Int // now ok, used to be: implicit function needs parameters def ff: X = () // error: found: Unit, expected: Int type Y = erased () => Int // error: empty function may not be erased def gg: Y = () // error: found: Unit, expected: Y - type Z = erased () |=> Int // error: empty function may not be erased + type Z = erased given () => Int // error: empty function may not be erased def hh: Z = () // error: found: Unit, expected: Int } diff --git a/tests/neg/i2960.scala b/tests/neg/i2960.scala index a134d3721350..f7c1dbe4f6f2 100644 --- a/tests/neg/i2960.scala +++ b/tests/neg/i2960.scala @@ -22,9 +22,9 @@ class Tag(val name: String, this } - def apply[U](f: Tag |=> U)(implicit t: Tag = null): this.type = { + def apply[U](f: given Tag => U)(implicit t: Tag = null): this.type = { if(t != null) t.children += this - f with this + f given this this } } diff --git a/tests/neg/i4196.scala b/tests/neg/i4196.scala index 1eea1e565d15..0930d3102903 100644 --- a/tests/neg/i4196.scala +++ b/tests/neg/i4196.scala @@ -1,6 +1,6 @@ object Test { @annotation.tailrec - def foo(i: Unit |=> Int): Unit |=> Int = + def foo(i: given Unit => Int): given Unit => Int = if (i == 0) 0 else diff --git a/tests/neg/i4611a.scala b/tests/neg/i4611a.scala index 31a210ad63fa..7527a0cb113b 100644 --- a/tests/neg/i4611a.scala +++ b/tests/neg/i4611a.scala @@ -1,6 +1,6 @@ // Don't qualify as SAM type because result type is an implicit function type trait Foo { - def foo(x: Int): Int |=> Int + def foo(x: Int): given Int => Int } trait Bar[T] { @@ -12,10 +12,10 @@ class Test { def foo(x: Int) = 1 } - val good2 = new Bar[Int |=> Int] { + val good2 = new Bar[given Int => Int] { def bar(x: Int) = 1 } val bad1: Foo = (x: Int) => 1 // error - val bad2: Bar[implicit Int => Int] = (x: Int) => 1 // error + val bad2: Bar[given Int => Int] = (x: Int) => 1 // error } diff --git a/tests/neg/i4611b.scala b/tests/neg/i4611b.scala index dc53e0fd74cb..d25710a466d1 100644 --- a/tests/neg/i4611b.scala +++ b/tests/neg/i4611b.scala @@ -3,7 +3,7 @@ import scala.concurrent.Future class Response class Request object Request { - type To[T] = Request |=> T + type To[T] = given Request => T } // Don't qualify as SAM type because result type is an implicit function type diff --git a/tests/neg/implicit-params.scala b/tests/neg/implicit-params.scala new file mode 100644 index 000000000000..ec38f07fa2aa --- /dev/null +++ b/tests/neg/implicit-params.scala @@ -0,0 +1,24 @@ +object Test { + + case class C(x: Int) + + def f(x: Int) given (c: C) = x + c.x + + def g(x: Int) given (c: C) (y: Int) = x + c.x + y + + def h(x: Int) given () = x // error + + implicit object C extends C(11) + + f(1) + f(1) given C + f given 2 // error + f(1)(C) // error + + g(1)(2) // OK + (g(1) given C)(2) // OK + g(1) given 2 // error + g(1) given C given 2 // error + g(1)(C)(2) // error + g(1)(C) given 2 // error +} \ No newline at end of file diff --git a/tests/neg/implicit-shadowing.scala b/tests/neg/implicit-shadowing.scala index f263b3f81761..0ecba452841f 100644 --- a/tests/neg/implicit-shadowing.scala +++ b/tests/neg/implicit-shadowing.scala @@ -20,8 +20,8 @@ object Test { } } - def h[T]: C1[T] |=> Unit = { - def g[U]: C2[U] |=> Unit = { + def h[T]: given C1[T] => Unit = { + def g[U]: given C2[U] => Unit = { implicitly[C1[T]] // OK: no shadowing for evidence parameters implicitly[C2[U]] } diff --git a/tests/neg/trailingCommas.scala b/tests/neg/trailingCommas.scala index 356f735ce656..ad5a0f3ea942 100644 --- a/tests/neg/trailingCommas.scala +++ b/tests/neg/trailingCommas.scala @@ -18,7 +18,7 @@ trait SimpleExpr { (23, "bar", ) } // error trait TypeArgs { def f: ValidGeneric[Int, String, ] } // error trait TypeParamClause { type C[A, B, ] } // error -trait FunTypeParamClause { def f[A, B, ] } // error +trait FunTypeParamClause { def f[A, B, ] } // error // error trait SimpleType { def f: (Int, String, ) } // error trait FunctionArgTypes { def f: (Int, String, ) => Boolean } // error diff --git a/tests/new/test.scala b/tests/new/test.scala index 3e568da314df..728f2173d100 100644 --- a/tests/new/test.scala +++ b/tests/new/test.scala @@ -4,10 +4,10 @@ trait T { object Test0 { trait A[T] - instance a[T] of A[T] + implied a[T] for A[T] class B[T] - instance b[T] of B[T] + implied b[T] for B[T] } class C extends T diff --git a/tests/pos/Orderings.scala b/tests/pos/Orderings.scala index ec5aa60a745b..64dd7fa7eded 100644 --- a/tests/pos/Orderings.scala +++ b/tests/pos/Orderings.scala @@ -15,6 +15,6 @@ object Orderings { else ev.less(xs.head, ys.head) } - def isLess[T]: T => T => Ord[T] |=> Boolean = + def isLess[T]: T => T => given Ord[T] => Boolean = x => y => implicitly[Ord[T]].less(x, y) } diff --git a/tests/pos/case-getters.scala b/tests/pos/case-getters.scala index dc6476e10674..aac0f194bc99 100644 --- a/tests/pos/case-getters.scala +++ b/tests/pos/case-getters.scala @@ -1,8 +1,8 @@ -case class Foo(x: 1, y: Int |=> Int) +case class Foo(x: 1, y: given Int => Int) object Test { - val f = Foo(1, (i: Int) |=> i) + val f = Foo(1, given (i: Int) => i) val fx1: 1 = f.x val fx2: 1 = f._1 - val fy1: Int = f.y with 1 - val fy2: Int = f._2 with 1 + val fy1: Int = f.y given 1 + val fy2: Int = f._2 given 1 } diff --git a/tests/pos/depfuntype.scala b/tests/pos/depfuntype.scala index c31978af8536..eef9f86ac5b3 100644 --- a/tests/pos/depfuntype.scala +++ b/tests/pos/depfuntype.scala @@ -19,15 +19,15 @@ object Test { // Reproduced here because the one from DottyPredef is lacking a parameter dependency of the return type `ev.type` inline final def implicitly[T](implicit ev: T): ev.type = ev - type IDF = (x: C) |=> x.M + type IDF = given (x: C) => x.M implicit val ic: C = ??? val ifun: IDF = implicitly[C].m - val u = ifun with c + val u = ifun given c val u1: Int = u - val v = ifun with d + val v = ifun given d val v1: d.M = v } diff --git a/tests/pos/eff-compose.scala b/tests/pos/eff-compose.scala index 80e43b70d199..9165ae215d77 100644 --- a/tests/pos/eff-compose.scala +++ b/tests/pos/eff-compose.scala @@ -5,7 +5,7 @@ object Test { // Type X => Y abstract class Fun[-X, Y] { type Eff <: Effect - def apply(x: X): Eff |=> Y + def apply(x: X): given Eff => Y } // Type X -> Y @@ -13,7 +13,7 @@ object Test { // def map(f: A => B)(xs: List[A]): List[B] def map[A, B, E <: Effect](f: Fun[A, B] { type Eff = E})(xs: List[A]) - : E |=> List[B] = + : given E => List[B] = xs.map(f.apply) // def mapFn[A, B]: (A => B) -> List[A] -> List[B] @@ -31,19 +31,19 @@ object Test { def apply(f: Fun[A, B] { type Eff = E}) = new Fun[List[A], List[B]] { type Eff = E - def apply(xs: List[A]): Eff |=> List[B] = + def apply(xs: List[A]): given Eff => List[B] = map(f)(xs) } } - implicit def combine[E1 <: Effect, E2 <: Effect] with (x: E1, y: E2): E1 & E2 = ??? + implicit def combine[E1 <: Effect, E2 <: Effect] given (x: E1, y: E2): E1 & E2 = ??? // def compose(f: A => B)(g: B => C)(x: A): C def compose[A, B, C, E1 <: Effect, E2 <: Effect] (f: Fun[A, B] { type Eff = E1}) (g: Fun[B, C] { type Eff = E2}) (x: A): - E1 & E2 |=> C = g(f(x)) + given E1 & E2 => C = g(f(x)) // def composeFn: (A => B) -> (B => C) -> A -> C def composeFn[A, B, C, E1 <: Effect, E2 <: Effect]: diff --git a/tests/pos/assumeIn.scala b/tests/pos/givenIn.scala similarity index 76% rename from tests/pos/assumeIn.scala rename to tests/pos/givenIn.scala index 6c367f826b2c..798d0411d5ff 100644 --- a/tests/pos/assumeIn.scala +++ b/tests/pos/givenIn.scala @@ -2,15 +2,15 @@ object Test { import scala.compiletime.constValue class Context { - inline def assumeIn[T](op: => Context |=> T) = { - instance of Context = this + inline def givenIn[T](op: => given Context => T) = { + implied for Context = this op } } def ctx: Context = new Context - def g with Context = () - ctx.assumeIn(g) + def g given Context = () + ctx.givenIn(g) /* The last three statements shoudl generate the following code: diff --git a/tests/pos/ho-implicits.scala b/tests/pos/ho-implicits.scala index 4dbb2a2e47a3..957926ee380c 100644 --- a/tests/pos/ho-implicits.scala +++ b/tests/pos/ho-implicits.scala @@ -1,9 +1,9 @@ object Test2 { - implicit def __1: Int |=> String = s"implicit: ${implicitly[Int]}" + implicit def __1: given Int => String = s"implicit: ${implicitly[Int]}" implicit def __2: Int = 42 - def f: String |=> Int = implicitly[String].length + def f: given String => Int = implicitly[String].length f: Int } \ No newline at end of file diff --git a/tests/pos/i2146.scala b/tests/pos/i2146.scala index 674094e9914b..98816b2c2269 100644 --- a/tests/pos/i2146.scala +++ b/tests/pos/i2146.scala @@ -2,12 +2,12 @@ object Test { case class A() case class B() - def simple[A]: A |=> A = implicitly[A] + def simple[A]: given A => A = implicitly[A] - def foo[A, B]: A |=> B |=> (A, B) = + def foo[A, B]: given A => given B => (A, B) = (implicitly[A], implicitly[B]) - def bar[A, B]: A |=> B |=> (A, B) = { a: A |=> + def bar[A, B]: given A => given B => (A, B) = { given (a: A) => (implicitly[A], implicitly[B]) } @@ -16,17 +16,17 @@ object Test { def main(args: Array[String]) = { println(foo[A, B]) - println(foo[A, B] with a) - println(foo with a with b) - val s: A |=> A = simple[A] + println(foo[A, B] given a) + println(foo given a given b) + val s: given A => A = simple[A] println(s) - val x0: A |=> B |=> (A, B) = foo[A, B] + val x0: given A => given B => (A, B) = foo[A, B] println(x0) - val x1: B |=> (A, B) = foo[A, B] + val x1: given B => (A, B) = foo[A, B] println(x1) println(bar[A, B]) - println(bar[A, B] with a) - println(bar with a with b) + println(bar[A, B] given a) + println(bar given a given b) } } diff --git a/tests/pos/i2278.scala b/tests/pos/i2278.scala index 13b7d9dddc7c..2f3a4d3e3a25 100644 --- a/tests/pos/i2278.scala +++ b/tests/pos/i2278.scala @@ -4,7 +4,7 @@ object Fluent { } trait CC[T] - type Context[Alg[x[_]] <: Foo[x], E] = Alg[CC] |=> CC[E] + type Context[Alg[x[_]] <: Foo[x], E] = given Alg[CC] => CC[E] def meth1[T]() : Context[Foo, T] = { implicitly[Foo[CC]].meth1() diff --git a/tests/pos/i2671.scala b/tests/pos/i2671.scala index 90ec7c91bb9e..e187b98a1ced 100644 --- a/tests/pos/i2671.scala +++ b/tests/pos/i2671.scala @@ -1,10 +1,10 @@ object Foo { - def map[E](f: E |=> Int): (E |=> Int) = ??? + def map[E](f: given E => Int): (given E => Int) = ??? implicit def i: Int = ??? - def f: Int |=> Int = ??? + def f: given Int => Int = ??? val a: Int = map(f) diff --git a/tests/pos/i2749.scala b/tests/pos/i2749.scala index 0897f8a386e0..959ce858a9b1 100644 --- a/tests/pos/i2749.scala +++ b/tests/pos/i2749.scala @@ -1,23 +1,23 @@ object Test { - val f: (Int |=> Char) |=> Boolean = ??? + val f: given (given Int => Char) => Boolean = ??? implicit val n: Int = 3 - implicit val g: Int |=> Char = ??? + implicit val g: given Int => Char = ??? f : Boolean } object Test2 { - val f: (Int |=> Char) |=> Boolean = ??? + val f: given (given Int => Char) => Boolean = ??? implicit val s: String = null - implicit val g: Int |=> String |=> Char = ??? + implicit val g: given Int => given String => Char = ??? f : Boolean } object Test3 { - val f: (Int |=> String |=> Char) |=> Boolean = ??? + val f: given (given Int => given String => Char) => Boolean = ??? implicit val n: Int = 3 - implicit val g: Int |=> Char = ??? + implicit val g: given Int => Char = ??? f : Boolean } diff --git a/tests/pos/i3692.scala b/tests/pos/i3692.scala index 2911f548f56f..cf799d4de02b 100644 --- a/tests/pos/i3692.scala +++ b/tests/pos/i3692.scala @@ -6,9 +6,9 @@ object Main { //val b: Int => Int = a def main(args: Array[String]): Unit = { - val choose: (c: C) |=> Set[Int] = Set.empty - val b0: (C) => Set[Int] = choose with _ - val b1: (c: C) => Set[Int] = choose with _ + val choose: given (c: C) => Set[Int] = Set.empty + val b0: (C) => Set[Int] = choose given _ + val b1: (c: C) => Set[Int] = choose given _ def applyF(f: (c: C) => Set[Int]) = f(new C{type T=Int}) //applyF(choose) } diff --git a/tests/pos/i4125.scala b/tests/pos/i4125.scala index 61310f14b246..49ca8fd8daf3 100644 --- a/tests/pos/i4125.scala +++ b/tests/pos/i4125.scala @@ -1,4 +1,4 @@ object Test { def foo: (erased (x: Int, y: Int) => Int) = erased (x, y) => 1 - def bar: (erased (x: Int, y: Int) |=> Int) = erased (x, y) |=> 1 + def bar: (erased given (x: Int, y: Int) => Int) = erased given (x, y) => 1 } diff --git a/tests/pos/i4196.scala b/tests/pos/i4196.scala index e880f01cd7a4..669ac0bc935c 100644 --- a/tests/pos/i4196.scala +++ b/tests/pos/i4196.scala @@ -1,6 +1,6 @@ object Test { @annotation.tailrec - def foo(i: Unit |=> Int): Unit |=> Int = + def foo(i: given Unit => Int): given Unit => Int = if (i == 0) 0 else diff --git a/tests/pos/i4203.scala b/tests/pos/i4203.scala index 3c75e7dee3df..4577d1709dbe 100644 --- a/tests/pos/i4203.scala +++ b/tests/pos/i4203.scala @@ -1,7 +1,7 @@ case class Box[Z](unbox: Z) object Test { - def foo(b: Box[Int |=> Int]): Int = b match { + def foo(b: Box[given Int => Int]): Int = b match { case Box(f) => implicit val i: Int = 1 f diff --git a/tests/pos/i4725.scala b/tests/pos/i4725.scala index 46a9a7ecaee8..1ccd34d3a328 100644 --- a/tests/pos/i4725.scala +++ b/tests/pos/i4725.scala @@ -1,8 +1,8 @@ object Test1 { trait T[A] - def foo[S[_], A] with (ev: T[A] |=> T[S[A]]): Unit = () - implicit def bar[A] with (ev: T[A]): T[List[A]] = ??? + def foo[S[_], A] given (ev: given T[A] => T[S[A]]): Unit = () + implicit def bar[A] given (ev: T[A]): T[List[A]] = ??? foo[List, Int] } @@ -11,8 +11,8 @@ object Test2 { trait T trait S - def foo with (ev: T |=> S): Unit = () - implicit def bar with (ev: T): S = ??? + def foo given (ev: given T => S): Unit = () + implicit def bar given (ev: T): S = ??? foo } diff --git a/tests/pos/i4753.scala b/tests/pos/i4753.scala index bc83d980a6b6..dc5999506a8a 100644 --- a/tests/pos/i4753.scala +++ b/tests/pos/i4753.scala @@ -1,13 +1,13 @@ class A trait Foo { - def foo: A |=> Int + def foo: given A => Int } class Test { - println(new FooI{}) + new FooI{} } class FooI extends Foo { - def foo: A |=> Int = 3 + def foo: given A => Int = 3 } \ No newline at end of file diff --git a/tests/pos/i4753b.scala b/tests/pos/i4753b.scala index 606986f2ddbc..a6a2ce0103d9 100644 --- a/tests/pos/i4753b.scala +++ b/tests/pos/i4753b.scala @@ -1,7 +1,7 @@ class Foo1 { - def foo: String |=> Int = 1 + def foo: given String => Int = 1 } class Foo2 extends Foo1 { - override def foo: String |=> Int = 2 + override def foo: given String => Int = 2 } diff --git a/tests/pos/implicit-conversion.scala b/tests/pos/implicit-conversion.scala new file mode 100644 index 000000000000..bffc6745060c --- /dev/null +++ b/tests/pos/implicit-conversion.scala @@ -0,0 +1,6 @@ +object Test { + // a problematic implicit conversion, should we flag it? + implied for Conversion[String, Int] { + def apply(x: String): Int = Integer.parseInt(toString) + } +} \ No newline at end of file diff --git a/tests/pos/implicit-dep.scala b/tests/pos/implicit-dep.scala index f6fe80cc43c1..620b95e96a7d 100644 --- a/tests/pos/implicit-dep.scala +++ b/tests/pos/implicit-dep.scala @@ -5,5 +5,5 @@ trait HasT { object Test { - def foo: Int |=> (g: HasT) |=> g.T = ??? + def foo: given Int => given (g: HasT) => g.T = ??? } diff --git a/tests/pos/implicitFuns.scala b/tests/pos/implicitFuns.scala index 555acf7310d0..7eb2ca40f58b 100644 --- a/tests/pos/implicitFuns.scala +++ b/tests/pos/implicitFuns.scala @@ -10,7 +10,7 @@ class ConfManagement(papers: List[Paper], realScore: Map[Paper, Int]) extends Ap private def hasConflict(ps1: Set[Person], ps2: Iterable[Person]) = ps2.exists(ps1 contains _) - type Viewable[T] = Viewers |=> T + type Viewable[T] = given Viewers => T def vs: Viewable[Viewers] = implicitly @@ -52,7 +52,7 @@ object Orderings extends App { x => y => x < y } - implicit def __2[T]: Ord[T] |=> Ord[List[T]] = new Ord[List[T]] { + implicit def __2[T]: given Ord[T] => Ord[List[T]] = new Ord[List[T]] { def less: List[T] => List[T] => Boolean = xs => ys => if ys.isEmpty then false @@ -61,7 +61,7 @@ object Orderings extends App { else isLess(xs.head)(ys.head) } - def isLess[T]: T => T => Ord[T] |=> Boolean = + def isLess[T]: T => T => given Ord[T] => Boolean = x => y => implicitly[Ord[T]].less(x)(y) println(isLess(Nil)(List(1, 2, 3))) diff --git a/tests/pos/inline-apply.scala b/tests/pos/inline-apply.scala index 60c024af2227..358b89abcd92 100644 --- a/tests/pos/inline-apply.scala +++ b/tests/pos/inline-apply.scala @@ -3,9 +3,9 @@ class Context object Test { def transform()(implicit ctx: Context) = { - inline def withLocalOwner[T](op: Context |=> T) = op with ctx + inline def withLocalOwner[T](op: given Context => T) = op given ctx - withLocalOwner { ctx |=> () } + withLocalOwner { given ctx => } } } diff --git a/tests/pos/reference/instances.scala b/tests/pos/reference/instances.scala index 3618b5a257a9..925fec9ca6ae 100644 --- a/tests/pos/reference/instances.scala +++ b/tests/pos/reference/instances.scala @@ -32,12 +32,12 @@ class Common { object Instances extends Common { - instance IntOrd of Ord[Int] { + implied IntOrd for Ord[Int] { def (x: Int) compareTo (y: Int) = if (x < y) -1 else if (x > y) +1 else 0 } - instance ListOrd[T] with Ord[T] of Ord[List[T]] { + implied ListOrd[T] given Ord[T] for Ord[List[T]] { def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match { case (Nil, Nil) => 0 case (Nil, _) => -1 @@ -48,56 +48,56 @@ object Instances extends Common { } } - instance StringOps { + implied StringOps { def (xs: Seq[String]) longestStrings: Seq[String] = { val maxLength = xs.map(_.length).max xs.filter(_.length == maxLength) } } - instance ListOps { + implied ListOps { def (xs: List[T]) second[T] = xs.tail.head } - instance ListMonad of Monad[List] { + implied ListMonad for Monad[List] { 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) } - instance ReaderMonad[Ctx] of Monad[[X] => Ctx => X] { + implied ReaderMonad[Ctx] for Monad[[X] => Ctx => X] { 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]) with Ord[T]: T = + def maximum[T](xs: List[T]) given Ord[T]: T = xs.reduceLeft((x, y) => if (x < y) y else x) - def descending[T] with (asc: Ord[T]): Ord[T] = new Ord[T] { + def descending[T] given (asc: Ord[T]): Ord[T] = new Ord[T] { def (x: T) compareTo (y: T) = asc.compareTo(y)(x) } - def minimum[T](xs: List[T]) with Ord[T] = - maximum(xs) with descending + def minimum[T](xs: List[T]) given Ord[T] = + maximum(xs) given descending def test(): Unit = { val xs = List(1, 2, 3) println(maximum(xs)) - println(maximum(xs) with descending) - println(maximum(xs) with (descending with IntOrd)) + println(maximum(xs) given descending) + println(maximum(xs) given (descending given IntOrd)) println(minimum(xs)) } case class Context(value: String) - val c0: Context |=> String = ctx |=> ctx.value - val c1: (Context |=> String) = (ctx: Context) |=> ctx.value + val c0: given Context => String = given ctx => ctx.value + val c1: (given Context => String) = given (ctx: Context) => ctx.value class A class B - val ab: (x: A, y: B) |=> Int = (a: A, b: B) |=> 22 + val ab: given (x: A, y: B) => Int = given (a: A, b: B) => 22 trait TastyAPI { type Symbol @@ -105,7 +105,7 @@ object Instances extends Common { def (sym: Symbol) name: String } def symDeco: SymDeco - instance of SymDeco = symDeco + implied for SymDeco = symDeco } object TastyImpl extends TastyAPI { type Symbol = String @@ -116,31 +116,31 @@ object Instances extends Common { class D[T] - class C with (ctx: Context) { + class C given (ctx: Context) { def f() = { locally { - instance of Context = this.ctx - println(summon[Context].value) + implied for Context = this.ctx + println(infer[Context].value) } locally { lazy val ctx1 = this.ctx - instance of Context = ctx1 - println(summon[Context].value) + implied for Context = ctx1 + println(infer[Context].value) } locally { - instance d[T] of D[T] - println(summon[D[Int]]) + implied d[T] for D[T] + println(infer[D[Int]]) } locally { - instance with Context of D[Int] - println(summon[D[Int]]) + implied given Context for D[Int] + println(infer[D[Int]]) } } } class Token(str: String) - instance StringToToken of Conversion[String, Token] { + implied StringToToken for Conversion[String, Token] { def apply(str: String): Token = new Token(str) } @@ -150,28 +150,28 @@ object Instances extends Common { object PostConditions { opaque type WrappedResult[T] = T - private instance WrappedResult { + private implied WrappedResult { def apply[T](x: T): WrappedResult[T] = x def (x: WrappedResult[T]) unwrap[T]: T = x } - def result[T] with (wrapped: WrappedResult[T]): T = wrapped.unwrap + def result[T] given (wrapped: WrappedResult[T]): T = wrapped.unwrap - instance { - def (x: T) ensuring[T] (condition: WrappedResult[T] |=> Boolean): T = { - assert(condition with WrappedResult(x)) + implied { + def (x: T) ensuring[T] (condition: given WrappedResult[T] => Boolean): T = { + assert(condition given WrappedResult(x)) x } } } object AnonymousInstances extends Common { - instance of Ord[Int] { + implied for Ord[Int] { def (x: Int) compareTo (y: Int) = if (x < y) -1 else if (x > y) +1 else 0 } - instance [T: Ord] of Ord[List[T]] { + implied [T: Ord] for Ord[List[T]] { def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match { case (Nil, Nil) => 0 case (Nil, _) => -1 @@ -182,28 +182,28 @@ object AnonymousInstances extends Common { } } - instance { + implied { def (xs: Seq[String]) longestStrings: Seq[String] = { val maxLength = xs.map(_.length).max xs.filter(_.length == maxLength) } } - instance { + implied { def (xs: List[T]) second[T] = xs.tail.head } - instance [From, To] with (c: Convertible[From, To]) of Convertible[List[From], List[To]] { + implied [From, To] given (c: Convertible[From, To]) for Convertible[List[From], List[To]] { def (x: List[From]) convert: List[To] = x.map(c.convert) } - instance of Monoid[String] { + implied for Monoid[String] { 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(_)) + xs.foldLeft(infer[Monoid[T]].unit)(_.combine(_)) } object Implicits extends Common { @@ -272,13 +272,13 @@ object Completions { } // conversions defining the possible arguments to pass to `complete` - instance stringArg of Conversion[String, CompletionArg] { + implied stringArg for Conversion[String, CompletionArg] { def apply(s: String) = CompletionArg.Error(s) } - instance responseArg of Conversion[Future[HttpResponse], CompletionArg] { + implied responseArg for Conversion[Future[HttpResponse], CompletionArg] { def apply(f: Future[HttpResponse]) = CompletionArg.Response(f) } - instance statusArg of Conversion[Future[StatusCode], CompletionArg] { + implied statusArg for Conversion[Future[StatusCode], CompletionArg] { def apply(code: Future[StatusCode]) = CompletionArg.Status(code) } } \ No newline at end of file diff --git a/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/TastyInterpreter.scala b/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/TastyInterpreter.scala index ba0c87ce5191..bb389a21cd0b 100644 --- a/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/TastyInterpreter.scala +++ b/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/TastyInterpreter.scala @@ -14,7 +14,7 @@ class TastyInterpreter extends TastyConsumer { case DefDef("main", _, _, _, Some(rhs)) => val interpreter = new jvm.Interpreter(reflect) - interpreter.eval(rhs) with Map.empty + interpreter.eval(rhs) given Map.empty // TODO: recurse only for PackageDef, ClassDef case tree => super.traverseTree(tree) diff --git a/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/TreeInterpreter.scala b/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/TreeInterpreter.scala index 3ce4283ff4f6..bd96c338677a 100644 --- a/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/TreeInterpreter.scala +++ b/tests/run-with-compiler-custom-args/tasty-interpreter/interpreter/TreeInterpreter.scala @@ -13,15 +13,15 @@ abstract class TreeInterpreter[R <: Reflection & Singleton](val reflect: R) { /** Representation of objects and values in the interpreter */ type AbstractAny - type Result = Env |=> AbstractAny + type Result = given Env => AbstractAny def localValue(sym: Symbol)(implicit env: Env): LocalValue = env(sym) - def withLocalValue[T](sym: Symbol, value: LocalValue)(in: Env |=> T)(implicit env: Env): T = - in with env.updated(sym, value) + def withLocalValue[T](sym: Symbol, value: LocalValue)(in: given Env => T)(implicit env: Env): T = + in given env.updated(sym, value) - def withLocalValues[T](syms: List[Symbol], values: List[LocalValue])(in: Env |=> T)(implicit env: Env): T = - in with (env ++ syms.zip(values)) + def withLocalValues[T](syms: List[Symbol], values: List[LocalValue])(in: given Env => T)(implicit env: Env): T = + in given (env ++ syms.zip(values)) def interpretCall(inst: AbstractAny, sym: DefSymbol, args: List[AbstractAny]): Result = { // TODO @@ -65,7 +65,7 @@ abstract class TreeInterpreter[R <: Reflection & Singleton](val reflect: R) { def interpretBlock(stats: List[Statement], expr: Term): Result = { val newEnv = stats.foldLeft(implicitly[Env])((accEnv, stat) => stat match { case ValDef(name, tpt, Some(rhs)) => - def evalRhs = eval(rhs) with accEnv + def evalRhs = eval(rhs) given accEnv val evalRef: LocalValue = if (stat.symbol.flags.is(Flags.Lazy)) LocalValue.lazyValFrom(evalRhs) else if (stat.symbol.flags.is(Flags.Mutable)) LocalValue.varFrom(evalRhs) @@ -76,10 +76,10 @@ abstract class TreeInterpreter[R <: Reflection & Singleton](val reflect: R) { // TODO: record the environment for closure purposes accEnv case stat => - eval(stat) with accEnv + eval(stat) given accEnv accEnv }) - eval(expr) with newEnv + eval(expr) given newEnv } def interpretUnit(): AbstractAny diff --git a/tests/run/builder.scala b/tests/run/builder.scala index 436b14ed421f..b9db863ea3bb 100644 --- a/tests/run/builder.scala +++ b/tests/run/builder.scala @@ -16,13 +16,13 @@ case class Cell(elem: String) object Test { - def table(init: Table |=> Unit) = { + def table(init: given Table => Unit) = { implicit val t = new Table init t } - def row(init: Row |=> Unit)(implicit t: Table) = { + def row(init: given Row => Unit)(implicit t: Table) = { implicit val r = new Row init t.add(r) diff --git a/tests/run/config.scala b/tests/run/config.scala index 7d30001be36c..c60731587ca3 100644 --- a/tests/run/config.scala +++ b/tests/run/config.scala @@ -29,13 +29,13 @@ object Imperative { ).onError(None) def main(args: Array[String]) = { - println(readPerson with Config("John Doe", 20)) - println(readPerson with Config("Incognito", 99)) + println(readPerson given Config("John Doe", 20)) + println(readPerson given Config("Incognito", 99)) } } object Configs { - type Configured[T] = Config |=> T + type Configured[T] = given Config => T def config: Configured[Config] = implicitly[Config] } @@ -47,7 +47,7 @@ object Exceptions { private[Exceptions] def throwE() = throw new E } - type Possibly[T] = CanThrow |=> T + type Possibly[T] = given CanThrow => T def require(p: Boolean)(implicit ct: CanThrow): Unit = if (!p) ct.throwE() @@ -56,7 +56,7 @@ object Exceptions { class OnError[T](op: Possibly[T]) { def onError(fallback: => T): T = - try op with (new CanThrow) + try op given (new CanThrow) catch { case ex: E => fallback } } } @@ -85,8 +85,8 @@ object Test extends App { val config1 = Config("John Doe", 20) val config2 = Config("Incognito", 99) - println(readPerson with config1) - println(readPerson with config2) + println(readPerson given config1) + println(readPerson given config2) } object OptionTest extends App { diff --git a/tests/run/eff-dependent.scala b/tests/run/eff-dependent.scala index 5ecfe0a08c8a..59be3c89e4b6 100644 --- a/tests/run/eff-dependent.scala +++ b/tests/run/eff-dependent.scala @@ -5,7 +5,7 @@ object Test extends App { // Type X => Y abstract class Fun[-X, +Y] { type Eff <: Effect - def apply(x: X): Eff |=> Y + def apply(x: X): given Eff => Y } class CanThrow extends Effect @@ -18,18 +18,18 @@ object Test extends App { implicit val ci: CanIO = new CanIO // def map(f: A => B)(xs: List[A]): List[B] - def map[A, B](f: Fun[A, B])(xs: List[A]): f.Eff |=> List[B] = + def map[A, B](f: Fun[A, B])(xs: List[A]): given f.Eff => List[B] = xs.map(f.apply) // def mapFn[A, B]: (A => B) -> List[A] -> List[B] - def mapFn[A, B]: (f: Fun[A, B]) => List[A] => f.Eff |=> List[B] = + def mapFn[A, B]: (f: Fun[A, B]) => List[A] => given f.Eff => List[B] = f => xs => map(f)(xs) // def compose(f: A => B)(g: B => C)(x: A): C - def compose[A, B, C](f: Fun[A, B])(g: Fun[B, C])(x: A): f.Eff |=> g.Eff |=> C = g(f(x)) + def compose[A, B, C](f: Fun[A, B])(g: Fun[B, C])(x: A): given f.Eff => given g.Eff => C = g(f(x)) // def composeFn: (A => B) -> (B => C) -> A -> C - def composeFn[A, B, C]: (f: Fun[A, B]) => (g: Fun[B, C]) => A => f.Eff |=> g.Eff |=> C = + def composeFn[A, B, C]: (f: Fun[A, B]) => (g: Fun[B, C]) => A => given f.Eff => given g.Eff => C = f => g => x => compose(f)(g)(x) assert(mapFn(i2s)(List(1, 2, 3)).mkString == "123") diff --git a/tests/run/erased-23.check b/tests/run/erased-23.check index aac6a431ff8a..efbbe4dddf06 100644 --- a/tests/run/erased-23.check +++ b/tests/run/erased-23.check @@ -1 +1,2 @@ lambda1 +lambda2 diff --git a/tests/run/erased-23.scala b/tests/run/erased-23.scala index e61a0ad4a981..bb5738c349e8 100644 --- a/tests/run/erased-23.scala +++ b/tests/run/erased-23.scala @@ -1,13 +1,22 @@ object Test { def main(args: Array[String]): Unit = { - fun { erased (x: Int) |=> + fun { given erased (x: Int) => println("lambda1") "abc" } + + fun2 { erased given (x: Int) => + println("lambda2") + "abc" + } + } + + def fun(f: given erased Int => String): String = { + f given 35 } - def fun(f: erased Int |=> String): String = { - f with 35 + def fun2(f: erased given Int => String): String = { + f given 35 } } diff --git a/tests/run/i2642.scala b/tests/run/i2642.scala index d0f4bcaf0b00..b3b849f45a2f 100644 --- a/tests/run/i2642.scala +++ b/tests/run/i2642.scala @@ -1,7 +1,7 @@ // Tests nullary implicit function types object Test extends App { class I - type X = () |=> Int + type X = given () => Int def ff: X = 2 assert(ff == 2) } diff --git a/tests/run/i2939.scala b/tests/run/i2939.scala index 1e2937617bde..4046a7fd347c 100644 --- a/tests/run/i2939.scala +++ b/tests/run/i2939.scala @@ -7,8 +7,8 @@ class Tag(val name: String, val buffer: Buffer[Tag] = ArrayBuffer()) { s"${" " * n}" } - def apply[U](f: Tag |=> U)(implicit tag: Tag = null): this.type = { - f with this + def apply[U](f: given Tag => U)(implicit tag: Tag = null): this.type = { + f given this if(tag != null) tag.buffer += this this } diff --git a/tests/run/i3448.scala b/tests/run/i3448.scala index 6905c1cbcdbf..f412ba61f3c8 100644 --- a/tests/run/i3448.scala +++ b/tests/run/i3448.scala @@ -1,13 +1,13 @@ object Test extends App { case class C(x: Int) - type IF[T] = C |=> T + type IF[T] = given C => T val x: IF[Int] = implicitly[C].x - val xs0: List[IF[Int]] = List(_ |=> x) + val xs0: List[IF[Int]] = List(given _ => x) val xs: List[IF[Int]] = List(x) val ys: IF[List[Int]] = xs.map(x => x) - val zs = ys with C(22) + val zs = ys given C(22) assert(zs == List(22)) } diff --git a/tests/run/implicit-shortcut-bridge.scala b/tests/run/implicit-shortcut-bridge.scala index 7ad474ea507f..fed9b9772e96 100644 --- a/tests/run/implicit-shortcut-bridge.scala +++ b/tests/run/implicit-shortcut-bridge.scala @@ -1,17 +1,17 @@ abstract class A[T] { def foo: T } -class B extends A[Int |=> Int] { +class B extends A[given Int => Int] { // No bridge needed for foo$direct - def foo: Int |=> Int = 1 + def foo: given Int => Int = 1 } -abstract class X[T] extends A[T |=> T] { - def foo: T |=> T +abstract class X[T] extends A[given T => T] { + def foo: given T => T } class Y extends X[Int] { - def foo: Int |=> Int = 1 + def foo: given Int => Int = 1 } object Test { diff --git a/tests/run/implicitFunctionXXL.scala b/tests/run/implicitFunctionXXL.scala index 7c3093890e00..0ba6c3ffc089 100644 --- a/tests/run/implicitFunctionXXL.scala +++ b/tests/run/implicitFunctionXXL.scala @@ -5,7 +5,7 @@ object Test { implicit val intWorld: Int = 42 implicit val strWorld: String = "Hello " - val i1 = ( (x1: Int, + val i1 = (given (x1: Int, x2: String, x3: Int, x4: Int, @@ -30,7 +30,7 @@ object Test { x23: Int, x24: Int, x25: Int, - x26: Int) |=> x2 + x1) + x26: Int) => x2 + x1) println(i1) } diff --git a/tests/run/implicitFuns.scala b/tests/run/implicitFuns.scala index 30893ed544c9..508d54c31569 100644 --- a/tests/run/implicitFuns.scala +++ b/tests/run/implicitFuns.scala @@ -3,44 +3,44 @@ object Test { implicit val world: String = "world!" - val i1 = ((s: String) |=> s.length > 2) - val i2 = {(s: String) |=> s.length > 2} + val i1 = (given (s: String) => s.length > 2) + val i2 = {given (s: String) => s.length > 2} assert(i1) assert(i2) - val x: String |=> Boolean = { (s: String) |=> s.length > 2 } + val x: given String => Boolean = { given (s: String) => s.length > 2 } - val xx: (String, Int) |=> Int = (x: String, y: Int) |=> x.length + y + val xx: given (String, Int) => Int = given (x: String, y: Int) => x.length + y - val y: String => Boolean = x with _ + val y: String => Boolean = x given _ object nested { implicit val empty: String = "" assert(!x) } - val yy: (String, Int) => Any = xx with (_, _) + val yy: (String, Int) => Any = xx given (_, _) - val z1: String |=> Boolean = implicitly[String].length >= 2 + val z1: given String => Boolean = implicitly[String].length >= 2 assert(z1) - type StringlyBool = String |=> Boolean + type StringlyBool = given String => Boolean val z2: StringlyBool = implicitly[String].length >= 2 assert(z2) - type Stringly[T] = String |=> T + type Stringly[T] = given String => T val z3: Stringly[Boolean] = implicitly[String].length >= 2 assert(z3) - type GenericImplicit[X] = X |=> Boolean + type GenericImplicit[X] = given X => Boolean val z4: GenericImplicit[String] = implicitly[String].length >= 2 assert(z4) - val b = x with "hello" + val b = x given "hello" val b1: Boolean = b @@ -48,7 +48,7 @@ object Test { val bi1: Boolean = bi - val c = xx with ("hh", 22) + val c = xx given ("hh", 22) val c1: Int = c @@ -56,7 +56,7 @@ object Test { def foo(s: String): Stringly[Int] = 42 - (if ("".isEmpty) foo("") else foo("")).apply with "" + (if ("".isEmpty) foo("") else foo("")).apply given "" } } @@ -76,16 +76,16 @@ object Contextual { val Source = new Key[String] val Options = new Key[List[String]] - type Ctx[T] = Context |=> T + type Ctx[T] = given Context => T def ctx: Ctx[Context] = implicitly[Context] def compile(s: String): Ctx[Boolean] = - (runOn(new java.io.File(s)) with ctx.withBinding(Source, s)) >= 0 + (runOn(new java.io.File(s)) given ctx.withBinding(Source, s)) >= 0 def runOn(f: java.io.File): Ctx[Int] = { val options = List("-verbose", "-explaintypes") - process(f).apply with ctx.withBinding(Options, options) + process(f).apply given ctx.withBinding(Options, options) } def process(f: java.io.File): Ctx[Int] = @@ -151,7 +151,7 @@ object TransactionalExplicit { } object Transactional { - type Transactional[T] = Transaction |=> T + type Transactional[T] = given Transaction => T def transaction[T](op: Transactional[T]) = { implicit val trans: Transaction = new Transaction @@ -216,7 +216,7 @@ object TransactionalExpansion { } object TransactionalAbstracted { - type Transactional[T] = Transaction |=> T + type Transactional[T] = given Transaction => T trait TransOps { def thisTransaction: Transactional[Transaction] diff --git a/tests/run/implicitFuns2.scala b/tests/run/implicitFuns2.scala index 8f59819015a9..e947066bdc67 100644 --- a/tests/run/implicitFuns2.scala +++ b/tests/run/implicitFuns2.scala @@ -2,27 +2,27 @@ class A class B trait Foo { - def foo: A |=> B |=> Int + def foo: given A => given B => Int } class Foo1 extends Foo { - def foo: A |=> B |=> Int = 1 + def foo: given A => given B => Int = 1 } class Foo2 extends Foo1 { - override def foo: A |=> B |=> Int = 2 + override def foo: given A => given B => Int = 2 } trait Foo3 extends Foo { - override def foo: A |=> B |=> Int = 3 + override def foo: given A => given B => Int = 3 } class Bar[T] { - def bar: A |=> T = null.asInstanceOf[T] + def bar: given A => T = null.asInstanceOf[T] } -class Bar1 extends Bar[B |=> Int] { - override def bar: A |=> B |=> Int = 1 +class Bar1 extends Bar[given B => Int] { + override def bar: given A => given B => Int = 1 } object Test { diff --git a/tests/run/implicitShortcut/Base_1.scala b/tests/run/implicitShortcut/Base_1.scala index bbba570bcf38..91db5ee94880 100644 --- a/tests/run/implicitShortcut/Base_1.scala +++ b/tests/run/implicitShortcut/Base_1.scala @@ -3,6 +3,6 @@ package implicitShortcut class C abstract class Base[T] { - def foo(x: T): C |=> T = x + def foo(x: T): given C => T = x } \ No newline at end of file diff --git a/tests/run/implicitShortcut/Derived_2.scala b/tests/run/implicitShortcut/Derived_2.scala index b66bfcc17c02..045fcbc10e6d 100644 --- a/tests/run/implicitShortcut/Derived_2.scala +++ b/tests/run/implicitShortcut/Derived_2.scala @@ -1,5 +1,5 @@ package implicitShortcut class Derived extends Base[Int] { - override def foo(x: Int): C |=> Int = 42 + override def foo(x: Int): given C => Int = 42 } \ No newline at end of file diff --git a/tests/run/instances-anonymous.scala b/tests/run/instances-anonymous.scala index a4b29bdcb9b8..1c41cb25e19e 100644 --- a/tests/run/instances-anonymous.scala +++ b/tests/run/instances-anonymous.scala @@ -8,7 +8,7 @@ object Test extends App { case class Circle(x: Double, y: Double, radius: Double) - instance { + implied { def (c: Circle) circumference: Double = c.radius * math.Pi * 2 } @@ -16,7 +16,7 @@ object Test extends App { println(circle.circumference) - instance { + implied { def (xs: Seq[String]) longestStrings: Seq[String] = { val maxLength = xs.map(_.length).max xs.filter(_.length == maxLength) @@ -25,13 +25,13 @@ object Test extends App { val names = List("hi", "hello", "world") assert(names.longestStrings == List("hello", "world")) - instance { + implied { def (xs: Seq[T]) second[T] = xs.tail.head } assert(names.longestStrings.second == "world") - instance { + implied { def (xs: List[List[T]]) flattened[T] = xs.foldLeft[List[T]](Nil)(_ ++ _) } @@ -45,8 +45,8 @@ object Test extends App { def unit: T } - // An instance declaration: - instance of Monoid[String] { + // An implied declaration: + implied for Monoid[String] { def (x: String) combine (y: String): String = x.concat(y) def unit: String = "" } @@ -64,13 +64,13 @@ object Test extends App { val minimum: T } - instance of Ord[Int] { + implied for Ord[Int] { def (x: Int) compareTo (y: Int) = if (x < y) -1 else if (x > y) +1 else 0 val minimum = Int.MinValue } - instance [T: Ord] of Ord[List[T]] { + implied [T: Ord] for Ord[List[T]] { def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match { case (Nil, Nil) => 0 case (Nil, _) => -1 @@ -102,14 +102,14 @@ object Test extends App { def pure[A](x: A): F[A] } - instance of Monad[List] { + implied for Monad[List] { 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) } - instance [Ctx] of Monad[[X] => Ctx => X] { + implied [Ctx] for Monad[[X] => Ctx => X] { 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 = diff --git a/tests/run/instances.scala b/tests/run/instances.scala index c971238330f2..fc3fe8e9f5c2 100644 --- a/tests/run/instances.scala +++ b/tests/run/instances.scala @@ -8,7 +8,7 @@ object Test extends App { case class Circle(x: Double, y: Double, radius: Double) - instance CircleOps { + implied CircleOps { def (c: Circle) circumference: Double = c.radius * math.Pi * 2 } @@ -16,7 +16,7 @@ object Test extends App { assert(circle.circumference == CircleOps.circumference(circle)) - instance StringOps { + implied StringOps { def (xs: Seq[String]) longestStrings: Seq[String] = { val maxLength = xs.map(_.length).max xs.filter(_.length == maxLength) @@ -25,18 +25,18 @@ object Test extends App { val names = List("hi", "hello", "world") assert(names.longestStrings == List("hello", "world")) - instance SeqOps { + implied SeqOps { def (xs: Seq[T]) second[T] = xs.tail.head } assert(names.longestStrings.second == "world") - instance ListListOps { + implied ListListOps { def (xs: List[List[T]]) flattened[T] = xs.foldLeft[List[T]](Nil)(_ ++ _) } // A right associative op - instance Prepend { + implied Prepend { def (x: T) ::[T] (xs: Seq[T]) = x +: xs } val ss: Seq[Int] = List(1, 2, 3) @@ -53,8 +53,8 @@ object Test extends App { def unit: T } - // An instance declaration: - instance StringMonoid of Monoid[String] { + // An implied declaration: + implied StringMonoid for Monoid[String] { def (x: String) combine (y: String): String = x.concat(y) def unit: String = "" } @@ -72,13 +72,13 @@ object Test extends App { val minimum: T } - instance of Ord[Int] { + implied for Ord[Int] { def (x: Int) compareTo (y: Int) = if (x < y) -1 else if (x > y) +1 else 0 val minimum = Int.MinValue } - instance ListOrd[T: Ord] of Ord[List[T]] { + implied ListOrd[T: Ord] for Ord[List[T]] { def (xs: List[T]) compareTo (ys: List[T]): Int = (xs, ys) match { case (Nil, Nil) => 0 case (Nil, _) => -1 @@ -110,14 +110,14 @@ object Test extends App { def pure[A](x: A): F[A] } - instance ListMonad of Monad[List] { + implied ListMonad for Monad[List] { 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) } - instance ReaderMonad[Ctx] of Monad[[X] => Ctx => X] { + implied ReaderMonad[Ctx] for Monad[[X] => Ctx => X] { 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 = diff --git a/tests/run/returning.scala b/tests/run/returning.scala index 28d65ed7056d..2271bb443016 100644 --- a/tests/run/returning.scala +++ b/tests/run/returning.scala @@ -14,9 +14,9 @@ object NonLocalReturns { def throwReturn[T](result: T)(implicit returner: ReturnThrowable[T]): Nothing = returner.throwReturn(result) - def returning[T](op: ReturnThrowable[T] |=> T): T = { + def returning[T](op: given ReturnThrowable[T] => T): T = { val returner = new ReturnThrowable[T] - try op with returner + try op given returner catch { case ex: ReturnThrowable[_] => if (ex `eq` returner) ex.result.asInstanceOf[T] else throw ex diff --git a/tests/run/tagless.scala b/tests/run/tagless.scala index d59b091b39bb..e14a0c12b25e 100644 --- a/tests/run/tagless.scala +++ b/tests/run/tagless.scala @@ -1,7 +1,7 @@ // A rewrite of Olivier Blanvillain's [adaptation](https://gist.github.com/OlivierBlanvillain/48bb5c66dbb0557da50465809564ee80) // of Oleg Kislyov's [lecture notes](http://okmij.org/ftp/tagless-final/course/lecture.pdf) // on tagless final interpreters. -// Main win: Replace Either by an "algebraic effect" using an implicit function type. +// Main win: Replace Either by an "algebraic effect" using a query type. object Test extends App { // Explicit ADT @@ -24,29 +24,29 @@ object Test extends App { } // An example tree - def tf0[T] with (e: Exp[T]): T = + def tf0[T] given (e: Exp[T]): T = e.add(e.lit(8), e.neg(e.add(e.lit(1), e.lit(2)))) // Typeclass-style Exp syntax object ExpSyntax { - def lit[T](i: Int) with (e: Exp[T]): T = e.lit(i) - def neg[T](t: T) with (e: Exp[T]): T = e.neg(t) - def add[T](l: T, r: T) with (e: Exp[T]): T = e.add(l, r) + def lit[T](i: Int) given (e: Exp[T]): T = e.lit(i) + def neg[T](t: T) given (e: Exp[T]): T = e.neg(t) + def add[T](l: T, r: T) given (e: Exp[T]): T = e.add(l, r) } import ExpSyntax._ // It's safe to always have these in scope // Another tree - def tf1[T] with Exp[T]: T = + def tf1[T] given Exp[T]: T = add(lit(8), neg(add(lit(1), lit(2)))) // Base operations as typeclasses - instance of Exp[Int] { + implied for Exp[Int] { def lit(i: Int): Int = i def neg(t: Int): Int = -t def add(l: Int, r: Int): Int = l + r } - instance of Exp[String] { + implied for Exp[String] { def lit(i: Int): String = i.toString def neg(t: String): String = s"(-$t)" def add(l: String, r: String): String = s"($l + $r)" @@ -60,18 +60,18 @@ object Test extends App { def mul(l: T, r: T): T } object MultSyntax { - def mul[T](l: T, r: T) with (e: Mult[T]): T = e.mul(l, r) + def mul[T](l: T, r: T) given (e: Mult[T]): T = e.mul(l, r) } import MultSyntax._ def tfm1[T: Exp : Mult] = add(lit(7), neg(mul(lit(1), lit(2)))) def tfm2[T: Exp : Mult] = mul(lit(7), tf1) - instance of Mult[Int] { + implied for Mult[Int] { def mul(l: Int, r: Int): Int = l * r } - instance of Mult[String] { + implied for Mult[String] { def mul(l: String, r: String): String = s"$l * $r" } @@ -87,7 +87,7 @@ object Test extends App { } import Tree._ - instance of Exp[Tree], Mult[Tree] { + implied for Exp[Tree], Mult[Tree] { def lit(i: Int): Tree = Node("Lit", Leaf(i.toString)) def neg(t: Tree): Tree = Node("Neg", t) def add(l: Tree, r: Tree): Tree = Node("Add", l , r) @@ -106,9 +106,9 @@ object Test extends App { object CanThrow { private class Exc(msg: String) extends Exception(msg) - def _throw(msg: String) with CanThrow: Nothing = throw new Exc(msg) - def _try[T](op: CanThrow |=> T)(handler: String => T): T = { - instance of CanThrow + def _throw(msg: String) given CanThrow: Nothing = throw new Exc(msg) + def _try[T](op: Maybe[T])(handler: String => T): T = { + implied for CanThrow try op catch { case ex: Exception => handler(ex.getMessage) @@ -117,7 +117,7 @@ object Test extends App { } import CanThrow._ - type Maybe[T] = CanThrow |=> T + type Maybe[T] = given CanThrow => T def show[T](op: Maybe[T]): Unit = println(_try(op.toString)(identity)) @@ -138,7 +138,7 @@ object Test extends App { show(readInt("2")) show(readInt("X")) - def fromTree[T](t: Tree) with Exp[T]: Maybe[T] = t match { + def fromTree[T](t: Tree) given Exp[T]: Maybe[T] = t match { case Node("Lit", Leaf(n)) => lit(readInt(n)) case Node("Neg", t) => neg(fromTree(t)) case Node("Add", l , r) => add(fromTree(l), fromTree(r)) @@ -150,18 +150,18 @@ object Test extends App { show(fromTree[Tree](tf1Tree)) trait Wrapped { - def value[T] with Exp[T]: T + def value[T] given Exp[T]: T } - instance of Exp[Wrapped] { + implied for Exp[Wrapped] { def lit(i: Int) = new Wrapped { - def value[T] with (e: Exp[T]): T = e.lit(i) + def value[T] given (e: Exp[T]): T = e.lit(i) } def neg(t: Wrapped) = new Wrapped { - def value[T] with (e: Exp[T]): T = e.neg(t.value) + def value[T] given (e: Exp[T]): T = e.neg(t.value) } def add(l: Wrapped, r: Wrapped) = new Wrapped { - def value[T] with (e: Exp[T]): T = e.add(l.value, r.value) + def value[T] given (e: Exp[T]): T = e.add(l.value, r.value) } } @@ -170,7 +170,7 @@ object Test extends App { s"${t.value[Int]}\n${t.value[String]}" } - def fromTreeExt[T](recur: => Tree => Maybe[T]) with Exp[T]: Tree => Maybe[T] = { + def fromTreeExt[T](recur: => Tree => Maybe[T]) given Exp[T]: Tree => Maybe[T] = { case Node("Lit", Leaf(n)) => lit(readInt(n)) case Node("Neg", t) => neg(recur(t)) case Node("Add", l , r) => add(recur(l), recur(r)) @@ -181,7 +181,7 @@ object Test extends App { def fromTree2[T: Exp](t: Tree): Maybe[T] = fix(fromTreeExt[T])(t) - def fromTreeExt2[T](recur: => Tree => Maybe[T]) with Exp[T], Mult[T]: Tree => Maybe[T] = { + def fromTreeExt2[T](recur: => Tree => Maybe[T]) given Exp[T], Mult[T]: Tree => Maybe[T] = { case Node("Mult", l , r) => mul(recur(l), recur(r)) case t => fromTreeExt(recur)(t) } @@ -196,7 +196,7 @@ object Test extends App { // Added operation: negation pushdown enum NCtx { case Pos, Neg } - instance [T] with (e: Exp[T]) of Exp[NCtx => T] { + implied [T] given (e: Exp[T]) for Exp[NCtx => T] { import NCtx._ def lit(i: Int) = { case Pos => e.lit(i) @@ -216,7 +216,7 @@ object Test extends App { println(pushNeg(tf1[NCtx => String])) println(pushNeg(pushNeg(pushNeg(tf1))): String) - instance [T] with (e: Mult[T]) of Mult[NCtx => T] { + implied [T] given (e: Mult[T]) for Mult[NCtx => T] { import NCtx._ def mul(l: NCtx => T, r: NCtx => T): NCtx => T = { case Pos => e.mul(l(Pos), r(Pos)) @@ -230,21 +230,21 @@ object Test extends App { import IExp._ // Going from type class encoding to ADT encoding - instance initialize of Exp[IExp] { + implied initialize for Exp[IExp] { def lit(i: Int): IExp = Lit(i) def neg(t: IExp): IExp = Neg(t) def add(l: IExp, r: IExp): IExp = Add(l, r) } // Going from ADT encoding to type class encoding - def finalize[T](i: IExp) with (e: Exp[T]): T = i match { + def finalize[T](i: IExp) given (e: Exp[T]): T = i match { case Lit(l) => e.lit(l) case Neg(n) => e.neg(finalize[T](n)) case Add(l, r) => e.add(finalize[T](l), finalize[T](r)) } // Abstracting over multiple typeclasses - type Ring[T] = Exp[T] |=> Mult[T] |=> T + type Ring[T] = given Exp[T] => given Mult[T] => T def tfm1a[T]: Ring[T] = add(lit(7), neg(mul(lit(1), lit(2)))) def tfm2a[T]: Ring[T] = mul(lit(7), tf1) diff --git a/tests/run/tasty-getfile-implicit-fun-context/Macro_1.scala b/tests/run/tasty-getfile-implicit-fun-context/Macro_1.scala index afc84c7e67d3..ae0fef057ab6 100644 --- a/tests/run/tasty-getfile-implicit-fun-context/Macro_1.scala +++ b/tests/run/tasty-getfile-implicit-fun-context/Macro_1.scala @@ -3,7 +3,7 @@ import scala.tasty.Reflection object SourceFiles { - type Macro[X] = Reflection |=> Expr[X] + type Macro[X] = given Reflection => Expr[X] def tastyContext(implicit ctx: Reflection): Reflection = ctx implicit inline def getThisFile: String = diff --git a/tests/run/tasty-implicit-fun-context-2/Macro_1.scala b/tests/run/tasty-implicit-fun-context-2/Macro_1.scala index 170080aab25b..07c838ee6503 100644 --- a/tests/run/tasty-implicit-fun-context-2/Macro_1.scala +++ b/tests/run/tasty-implicit-fun-context-2/Macro_1.scala @@ -3,13 +3,14 @@ import scala.tasty.Reflection object Foo { - type Macro[X] = Reflection |=> Expr[X] - type Tastier[X] = Reflection |=> X + type Macro[X] = given Reflection => Expr[X] + type Tastier[X] = given Reflection => X implicit inline def foo: String = ~fooImpl - def fooImpl(implicit reflect: Reflection): Reflection |=> Tastier[Reflection |=> Macro[String]] = { + def fooImpl(implicit reflect: Reflection): given Reflection => Tastier[given Reflection => Macro[String]] = { '("abc") } + }