├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build.sbt ├── core └── src │ └── main │ ├── resources │ └── scalac-plugin.xml │ └── scala │ └── plugin │ ├── Constructors.scala │ ├── ContextApplied.scala │ └── Extractors.scala ├── project ├── build.properties └── plugins.sbt └── test └── src ├── main └── scala │ └── tests │ ├── basic.scala │ ├── classbounds.scala │ ├── dotnotation.scala │ ├── ignore.scala │ ├── nbounds.scala │ ├── nested.scala │ ├── nparams.scala │ ├── nslots.scala │ ├── package.scala │ ├── shadowed.scala │ ├── vclass.scala │ └── virtual.scala └── test └── scala └── tests └── dotnotation$test.scala /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .metals 3 | .vscode 4 | **/.bloop 5 | target 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | 3 | scala: 4 | - 2.13.1 5 | 6 | jdk: 7 | - openjdk8 8 | 9 | before_install: 10 | - git fetch --tags 11 | 12 | stages: 13 | - name: test 14 | - name: release 15 | if: ((branch = master AND type = push) OR (tag IS present)) AND NOT fork 16 | 17 | jobs: 18 | include: 19 | # stage="test" if no stage is specified 20 | - script: sbt +test:compile +test 21 | # run ci-release only if previous stages passed 22 | - stage: release 23 | script: sbt ci-release 24 | 25 | cache: 26 | directories: 27 | - $HOME/.ivy2/cache 28 | - $HOME/.sbt/boot/ 29 | 30 | before_cache: 31 | - find $HOME/.ivy2 -name "ivydata-*.properties" -delete 32 | - find $HOME/.sbt -name "*.lock" -delete 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Yuriy Slinkin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | context-applied 2 | ============= 3 | 4 | [![Build Status](https://travis-ci.com/augustjune/context-applied.svg?branch=master)](https://travis-ci.com/augustjune/context-applied) 5 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.augustjune/context-applied_2.13/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.augustjune/context-applied_2.13) 6 | 7 | ### Overview 8 | **context-applied** is a Scala compiler plugin that gives you a handle to the value 9 | that has the abilities specified by type parameter context bounds. 10 | 11 | Example: 12 | ```scala 13 | def fn[F[_]: Monad]: F[Int] = F.pure(12) 14 | ``` 15 | 16 | This scales across multiple contexts as well as multiple type parameters: 17 | ```scala 18 | def fn[F[_]: Applicative: Traverse, G[_]: Applicative]: G[F[Int]] = 19 | F.traverse(F.pure(""))(s => G.pure(s.size)) 20 | ``` 21 | 22 | *This doesn't require any type class specific syntax nor "summoner" method*. 23 | 24 | In fact it is achieved by introducing implicit conversions to the 25 | appropriate value from the implicit scope. 26 | 27 | Roughly speaking, you can pretend like you have a value named after the type parameter 28 | of the type that combines specified contexts: 29 | ```scala 30 | def fn[A: B: C: D] = { 31 | val A: B[A] with C[A] with D[A] = ??? 32 | 33 | // In reality A can be either B[A] or C[A] or D[A] in a particular moment 34 | } 35 | ``` 36 | 37 | ### Usage 38 | Plugin is available for Scala 2.11, 2.12 and 2.13. 39 | ```scala 40 | addCompilerPlugin("org.augustjune" %% "context-applied" % "0.1.4") 41 | ``` 42 | 43 | ### Use cases 44 | 1. **Custom algebras.** 45 | ```scala 46 | trait Console[F[_]] { 47 | def read: F[String] 48 | 49 | def write(s: String): F[Unit] 50 | } 51 | 52 | def reply[F[_]: Console: FlatMap]: F[String] = 53 | for { 54 | s <- F.read 55 | _ <- F.write(s) 56 | } yield s 57 | ``` 58 | 59 | 1. **Non-linear type class hierarchy.** 60 | 61 | If you specify two algebras that derive from the same parent, 62 | because of ambiguity you cannot use that parent's syntax. 63 | Typical example of this problem is `Monad` and `Traverse` from *cats* 64 | since they are both subtypes of `Functor`. 65 | ```scala 66 | import cats.syntax.all._ 67 | def fn[F[_]: Monad: Traverse](fs: F[String]) = 68 | fs.map(_.size) // Compiler error 69 | ``` 70 | With **context-applied** the first context that has *map* method 71 | in function's context bounds is used. 72 | ```scala 73 | def fn[F[_]: Monad: Traverse](fs: F[String]) = 74 | F.map(fs)(_.size) // Monad's map is used 75 | ``` 76 | 77 | ### Supported features 78 | 1. Kind-projector support. 79 | ```scala 80 | def fn[F[_]: ApplicativeError[*[_], Throwable]]: F[Nothing] = 81 | F.raiseError(new RuntimeException) 82 | ``` 83 | 1. Type parameters of any kinds. 84 | ```scala 85 | def fn[F[_]: Applicative, B[_, _]: Bifunctor, A: Monoid] = { 86 | val fa: F[A] = F.pure(A.empty) 87 | val rf: Functor[B[A, *]] = B.rightFunctor[A] 88 | } 89 | ``` 90 | 1. Nested scopes. 91 | 92 | Syntax is available for any context bounds: in classes, methods and nested methods. 93 | ```scala 94 | class Foo[F[_]: Applicative] { 95 | 96 | def bar[A: Monoid] = { 97 | def baz[G[_]: Functor](ga: G[A]) = 98 | G.map(ga)(F.pure) 99 | 100 | baz(List(A.empty)) 101 | } 102 | } 103 | ``` 104 | 105 | ### Special cases 106 | Since **context-applied** introduces additional syntax to your program 107 | it is important not to break any existing code or change its meaning. 108 | For this reason there are cases when the plugin 109 | just gracefully skips parts of the program. 110 | It happens when: 111 | 112 | 1. Name of type parameter is already taken. 113 | ```scala 114 | class Foo[F[_]: Functor] { 115 | val F: Int = 12 // F: Functor[F] will not be introduced inside Foo 116 | def f1[A: Monoid](A: Int) = () // A: Monoid[A] will not be introduced inside f1 117 | def f2[F[_]: Monad] = ??? // F: Monad[F] will be available inside f2 as local value 118 | } 119 | ``` 120 | 1. Inside value classes. 121 | ```scala 122 | class Foo(val dummy: Boolean) extends AnyVal { 123 | def fn[F[_]: Monad] = ??? // F: Monad[F] will not be introduced inside fn 124 | } 125 | ``` 126 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | lazy val `context-applied` = project 2 | .in(file(".")) 3 | .dependsOn(core, test) 4 | .aggregate(core, test) 5 | .settings( 6 | skip.in(publish) := true, 7 | projectSettings, 8 | crossScalaVersions := Nil 9 | ) 10 | 11 | lazy val core = project 12 | .in(file("core")) 13 | .settings( 14 | name := "context-applied", 15 | projectSettings, 16 | libraryDependencies += scalaOrganization.value % "scala-compiler" % scalaVersion.value, 17 | scalacOptions ++= Seq( 18 | "-Xfatal-warnings", 19 | "-Xlint", 20 | "-feature", 21 | "-language:higherKinds", 22 | "-deprecation", 23 | "-unchecked" 24 | ) 25 | ) 26 | 27 | lazy val test = project 28 | .in(file("test")) 29 | .dependsOn(core) 30 | .settings( 31 | skip.in(publish) := true, 32 | projectSettings, 33 | addCompilerPlugin("org.typelevel" % "kind-projector" % "0.11.0" cross CrossVersion.full), 34 | libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % Test, 35 | scalacOptions ++= { 36 | val jar = (core / Compile / packageBin).value 37 | Seq(s"-Xplugin:${jar.getAbsolutePath}", s"-Jdummy=${jar.lastModified}") // ensures recompile 38 | }, 39 | scalacOptions ++= Seq( 40 | "-Xfatal-warnings", 41 | "-language:higherKinds", 42 | "-language:postfixOps", 43 | "-language:implicitConversions", 44 | "-Ywarn-value-discard" // Warn when non-Unit expression results are unused. 45 | ) ++ (if (scalaBinaryVersion.value.startsWith("2.11")) Nil else List( 46 | "-Ywarn-unused:implicits", // Warn if an implicit parameter is unused. 47 | "-Ywarn-unused:imports", // Warn if an import selector is not referenced. 48 | "-Ywarn-unused:locals", // Warn if a local definition is unused. 49 | "-Ywarn-unused:params", // Warn if a value parameter is unused. 50 | "-Ywarn-unused:patvars", // Warn if a variable bound in a pattern is unused. 51 | "-Ywarn-unused:privates", // Warn if a private member is unused. 52 | )) 53 | ) 54 | 55 | lazy val projectSettings = Seq( 56 | organization := "org.augustjune", 57 | licenses ++= Seq(("MIT", url("http://opensource.org/licenses/MIT"))), 58 | homepage := Some(url("https://github.com/augustjune/context-applied")), 59 | developers := List( 60 | Developer("augustjune", "Yura Slinkin", "jurij.jurich@gmail.com", url("https://github.com/augustjune")) 61 | ), 62 | scalaVersion := "2.13.1", 63 | crossScalaVersions := Seq(scalaVersion.value, "2.12.10", "2.11.12") 64 | ) 65 | -------------------------------------------------------------------------------- /core/src/main/resources/scalac-plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | context-applied 3 | plugin.ContextApplied 4 | 5 | -------------------------------------------------------------------------------- /core/src/main/scala/plugin/Constructors.scala: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import scala.reflect.internal.Flags._ 4 | import scala.reflect.internal.SymbolTable 5 | 6 | trait Constructors { 7 | val global: SymbolTable 8 | 9 | import global._ 10 | 11 | /** 12 | * New empty trait declaration. 13 | * Such trait doesn't contain any methods. 14 | * 15 | * @param name Name of the trait 16 | * @param priv Determines whether this object should have private modifier 17 | */ 18 | def newEmptyTrait(name: TypeName, priv: Boolean): Tree = 19 | ClassDef( 20 | if (priv) Modifiers(ABSTRACT | INTERFACE | LOCAL | DEFAULTPARAM / TRAIT) 21 | else Modifiers(SYNTHETIC | ARTIFACT | ABSTRACT | INTERFACE | DEFAULTPARAM / TRAIT), 22 | name, 23 | List(), 24 | Template(List(Ident(tpnme.AnyRef)), noSelfType, List()) 25 | ) 26 | 27 | /** 28 | * New abstract class declaration. 29 | * E.g.: abstract class E {} 30 | * 31 | * @param name Name of the class 32 | * @param parent Classes's parent 33 | * @param inside Method that this class contains 34 | */ 35 | def newAbstractClass(name: String, parent: Option[String], inside: DefDef, priv: Boolean): Tree = 36 | ClassDef( 37 | if (priv) Modifiers(PRIVATE | LOCAL | SYNTHETIC | ARTIFACT | ABSTRACT) 38 | else Modifiers(SYNTHETIC | ARTIFACT | ABSTRACT), 39 | TypeName(name), 40 | List(), 41 | Template(List(Ident(parent.map(TypeName(_)).getOrElse(tpnme.AnyRef))), noSelfType, List(constructor, inside)) 42 | ) 43 | 44 | /** 45 | * New object declaration 46 | * E.g.: object Module {} 47 | * 48 | * @param name Name of the object 49 | * @param parent Object's parent 50 | * @param inside Method that this trait contains 51 | * @param priv Determines whether this object should have private modifier 52 | * @return 53 | */ 54 | def newObject(name: String, parent: Option[String], inside: DefDef, priv: Boolean): ModuleDef = 55 | ModuleDef( 56 | if (priv) Modifiers(PRIVATE | LOCAL | SYNTHETIC | ARTIFACT) 57 | else Modifiers(SYNTHETIC | ARTIFACT), 58 | TermName(name), 59 | Template( 60 | List(Ident(parent.map(TypeName(_)).getOrElse(tpnme.AnyRef))), 61 | noSelfType, 62 | List(constructor, inside) 63 | ) 64 | ) 65 | 66 | /** 67 | * New implicit conversion method declaration. 68 | * This method always returns constant value. 69 | * Example: def magicInt(i: YourClass): Int = 12 70 | * 71 | * @param fromT Argument type 72 | * @param resT Return type 73 | * @param resV Constant value to be returned. 74 | */ 75 | def newImplicitConversion(fromT: TypeName, resultTypeName: String, resT: AppliedTypeTree, resV: String): DefDef = 76 | DefDef( 77 | Modifiers(IMPLICIT | SYNTHETIC | ARTIFACT), 78 | TermName(s"$fromT$$$resultTypeName"), 79 | List(), 80 | List(List(ValDef(Modifiers(PARAM | SYNTHETIC | ARTIFACT), TermName("e"), Ident(fromT), EmptyTree))), 81 | resT, 82 | Ident(TermName(resV)) 83 | ) 84 | 85 | private def constructor: DefDef = 86 | DefDef( 87 | Modifiers(SYNTHETIC | ARTIFACT), 88 | termNames.CONSTRUCTOR, 89 | List(), 90 | List(List()), 91 | TypeTree(), 92 | Block( 93 | List( 94 | Apply( 95 | Select( 96 | Super( 97 | This(typeNames.EMPTY), 98 | typeNames.EMPTY 99 | ), 100 | termNames.CONSTRUCTOR 101 | ), 102 | List() 103 | ) 104 | ), 105 | Literal(Constant(())) 106 | ) 107 | ) 108 | } -------------------------------------------------------------------------------- /core/src/main/scala/plugin/ContextApplied.scala: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import scala.tools.nsc 4 | import nsc.Global 5 | import nsc.plugins.Plugin 6 | import nsc.plugins.PluginComponent 7 | import nsc.transform.Transform 8 | import nsc.transform.TypingTransformers 9 | import nsc.ast.TreeDSL 10 | import scala.collection.mutable.ListBuffer 11 | import scala.reflect.internal.Flags._ 12 | 13 | class ContextApplied(val global: Global) extends Plugin { 14 | val name = "context-applied" 15 | val description = "May the F be with you" 16 | val components = List(new ContextPlugin(this, global)) 17 | } 18 | 19 | class ContextPlugin(plugin: Plugin, val global: Global) 20 | extends PluginComponent with Transform with TypingTransformers with TreeDSL { 21 | 22 | import global._ 23 | 24 | override val runsBefore = List("namer") 25 | val runsAfter = List("parser") 26 | /** 27 | * Name of the phase starts with 'x' to make it run 28 | * after kind-projector phase if such exists 29 | */ 30 | val phaseName = "xcontext-applied" 31 | 32 | def newTransformer(unit: CompilationUnit): MyTransformer = 33 | new MyTransformer(unit) 34 | 35 | class MyTransformer(unit: CompilationUnit) extends TypingTransformer(unit) with Extractors with Constructors { 36 | val global: ContextPlugin.this.global.type = ContextPlugin.this.global 37 | private var inVclass: Boolean = false 38 | private var resultTypeLambdas = 0L 39 | 40 | override def transform(tree: Tree): Tree = 41 | tree match { 42 | case VClass(_) => 43 | // Going to wait until the first complain with a broken codebase 44 | inVclass = true 45 | val t = super.transform(tree) 46 | inVclass = false 47 | t 48 | 49 | case DefDef(mods, _, _, _, _, _) 50 | if mods.isDeferred => 51 | super.transform(tree) 52 | 53 | case ContextBounds(bounds) => 54 | if (inVclass) super.transform(tree) 55 | else super.transform(injectComponents(tree, bounds)) 56 | case _ => super.transform(tree) 57 | } 58 | 59 | private def injectComponents(tree: Tree, bounds: List[ContextBound]): Tree = 60 | tree match { 61 | case d: DefDef => 62 | d.rhs match { 63 | case b: Block => 64 | val legalBounds = bounds.filterNot(cb => containsDeclaration(cb.typ.decode, b.stats ++ d.vparamss.flatten)) 65 | val insert = legalBounds.flatMap(createComponents(_, inclass = false, None)) 66 | d.copy(rhs = b.copy(stats = insert ::: b.stats)) 67 | 68 | case value => 69 | val legalBounds = bounds.filterNot(cb => containsDeclaration(cb.typ.decode, d.vparamss.flatten)) 70 | val insert = legalBounds.flatMap(createComponents(_, inclass = false, None)) 71 | d.copy(rhs = Block(insert, value)) 72 | } 73 | 74 | case d @ ClassDef(_, name, _, Template(_, _, body)) => 75 | val legalBounds = bounds.filterNot(cb => containsDeclaration(cb.typ.decode, body)) 76 | val insert = legalBounds.flatMap(createComponents(_, inclass = true, className = Some(name.decode))) 77 | val updatedBody = insertAfterConstructor(body, insert) 78 | d.copy(impl = d.impl.copy(body = updatedBody)) 79 | 80 | case _ => tree 81 | } 82 | 83 | private def containsDeclaration(s: String, trees: List[Tree]): Boolean = 84 | trees.exists { 85 | case ValOrDefDef(_, TermName(str), _, _) if str == s => true 86 | case _ => false 87 | } 88 | 89 | private def createComponents(bound: ContextBound, inclass: Boolean, className: Option[String]): List[Tree] = { 90 | val trees = new ListBuffer[Tree] 91 | 92 | val empty = TypeName(s"E$$${bound.typ.decode}$$${className.getOrElse("Def")}") 93 | trees.append(newEmptyTrait(empty, inclass)) 94 | 95 | val lastParent = bound.evs.tail.foldRight(Option.empty[String]) { case (ev, parent) => 96 | val resTName = resultTypeName(ev.tree.tpt) 97 | val name = s"$resTName$$${bound.typ.decode}" 98 | trees.append(newAbstractClass(name, parent, newImplicitConversion(empty, resTName, ev.tree, ev.variable), inclass)) 99 | Some(name) 100 | } 101 | 102 | val resTName = resultTypeName(bound.evs.head.tree.tpt) 103 | val moduleName = s"$resTName$$${bound.typ.decode}" 104 | val module = newObject(moduleName, lastParent, newImplicitConversion(empty, resTName, bound.evs.head.tree, bound.evs.head.variable), inclass) 105 | trees.append(module) 106 | 107 | val imp = importModule(moduleName) 108 | trees.append(imp) 109 | 110 | trees.append(nullVal(bound.typ.decode, empty, inclass)) 111 | 112 | trees.toList 113 | } 114 | 115 | /** 116 | * Solves the problem with the name of applied type tree constructed using type lambda. 117 | * For example, following type name will be simplified to meet the requirements of scala naming: 118 | * {{{scala.AnyRef { 119 | * type ?[T[_]] = Console2[T, List] 120 | * }#?[F] 121 | * }}} 122 | */ 123 | private def resultTypeName(t: Tree): String = 124 | if (!t.toString.contains("{")) t.toString.replace(".", "") 125 | else { 126 | resultTypeLambdas += 1 127 | s"TLambda$resultTypeLambdas" 128 | } 129 | 130 | private def nullVal(name: String, typeName: TypeName, inclass: Boolean): Tree = 131 | if (inclass) ValDef(Modifiers(PRIVATE | LOCAL | SYNTHETIC | ARTIFACT), TermName(name), Ident(typeName), Literal(Constant(null))) 132 | else ValDef(Modifiers(SYNTHETIC | ARTIFACT), TermName(name), Ident(typeName), Literal(Constant(null))) 133 | 134 | private def importModule(name: String): Tree = 135 | Import(Ident(TermName(name)), List(ImportSelector.wild)) 136 | 137 | private def insertAfterConstructor(body: List[Tree], insert: List[Tree]): List[Tree] = 138 | body match { 139 | case DefDef(_, termNames.CONSTRUCTOR, _, _, _, _) :: t => body.head :: insert ::: t 140 | case h :: t => h :: insertAfterConstructor(t, insert) 141 | case Nil => insert 142 | } 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /core/src/main/scala/plugin/Extractors.scala: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import scala.reflect.internal.SymbolTable 4 | 5 | trait Extractors { 6 | val global: SymbolTable 7 | 8 | import global._ 9 | 10 | case class ContextBound(typ: TypeName, evs: List[Evidence]) 11 | 12 | //Evidence(Traverse[A],A,evidence$5) 13 | case class Evidence(tree: AppliedTypeTree, typ: TypeName, variable: String) 14 | 15 | object VClass { 16 | /** 17 | * Matches a tree which represents a value class 18 | */ 19 | def unapply(tree: Tree): Option[Unit] = tree match { 20 | case ClassDef(_, _, _, Template(parents, _, _)) 21 | if parents.exists { case Ident(tpnme.AnyVal) => true; case _ => false } => 22 | Some(()) 23 | 24 | case _ => None 25 | } 26 | } 27 | 28 | object ContextBounds { 29 | /** 30 | * Matches an occurrence of context bounds (in function or class/trait constructor), e.g.: 31 | * def fn[A: B] 32 | * class Foo[A: B] 33 | */ 34 | def unapply(tree: Tree): Option[List[ContextBound]] = tree match { 35 | case DefDef(_, _, tparams, vparamss, _, _) => 36 | val tpars = tparams.collect { case TypeDef(_, tp, _, _) => tp } 37 | val evs = vparamss.lastOption.toList.flatMap { params => 38 | params.collect { case Evidence(e) => e } 39 | } 40 | 41 | val bounds = matchBounds(tpars, evs) 42 | if (bounds.isEmpty) None 43 | else Some(bounds) 44 | 45 | case ClassDef(_, _, tparams, Template(_, _, body)) => 46 | val tpars = tparams.collect { case TypeDef(_, tp, _, _) => tp } 47 | val evs = body.collect { case Evidence(e) => e } 48 | 49 | val bounds = matchBounds(tpars, evs) 50 | if (bounds.isEmpty) None 51 | else Some(bounds) 52 | 53 | case _ => None 54 | } 55 | 56 | private def matchBounds(tpars: List[TypeName], evidences: List[Evidence]): List[ContextBound] = 57 | tpars.flatMap { s => 58 | val imps = evidences.filter(ev => ev.typ == s) 59 | if (imps.isEmpty) List() 60 | else List(ContextBound(s, imps)) 61 | } 62 | } 63 | 64 | object Evidence { 65 | def unapply(valDef: Tree): Option[Evidence] = valDef match { 66 | case ValDef(mods, TermName(variable), ap @ AppliedTypeTree(_, List(Ident(typ @ TypeName(_)))), _) 67 | if mods.isImplicit => Some(Evidence(ap, typ, variable)) 68 | case _ => None 69 | } 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 1.3.5 -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.4.31") -------------------------------------------------------------------------------- /test/src/main/scala/tests/basic.scala: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | object basic { 4 | def block[F[_]: Console] = { 5 | val x = "das" 6 | F.put(x) 7 | } 8 | 9 | def appl[F[_]: Console] = F.read 10 | } 11 | -------------------------------------------------------------------------------- /test/src/main/scala/tests/classbounds.scala: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | class classbounds[F[_]: Monad: Console] { 4 | 5 | def fn: F[Int] = F.pure(12) 6 | 7 | def fn2[G[_]: Traverse: Monad]: F[G[Int]] = 8 | G.traverse(G.pure("Hello"))(s => F.pure(s.length)) 9 | 10 | def fn3: F[Int] = F.map(F.read)(_.size) 11 | } 12 | -------------------------------------------------------------------------------- /test/src/main/scala/tests/dotnotation.scala: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | object dotnotation { 4 | 5 | trait Witness { 6 | type T 7 | val value: T {} 8 | } 9 | 10 | object Witness { 11 | type Aux[T0] = Witness { type T = T0 } 12 | } 13 | 14 | class SomeClass[A <: Unit: Monoid: Witness.Aux] { 15 | def fn: Unit = A.value 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/src/main/scala/tests/ignore.scala: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | /** 4 | * Checks if the plugin ignores the cases when it shouldn't be applied 5 | */ 6 | object ignore { 7 | 8 | def declarations[F[_] : Console, G[_] : Console] = { 9 | val F = "Sda" 10 | G.put(F) 11 | } 12 | 13 | def arguments[F[_] : Console, G[_] : Console](F: String) = { 14 | G.put(F) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/src/main/scala/tests/nbounds.scala: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | /** 4 | * Checks if the plugin is able to handle more than one algebra 5 | */ 6 | object nbounds { 7 | 8 | def combined[F[_]: Monad: Traverse] = { 9 | F.traverse(F.pure(12))(_ => F.pure(12)) 10 | } 11 | 12 | def common[F[_]: Monad: Traverse] = { 13 | F.map(F.pure(12))(_ + 1) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/src/main/scala/tests/nested.scala: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | /** 4 | * Checks if the plugin provides the syntax for the nested functions 5 | */ 6 | object nested { 7 | 8 | implicit val listFunctor: Functor[List] = new Functor[List] { 9 | def map[A, B](fa: List[A])(f: A => B): List[B] = fa.map(f) 10 | } 11 | 12 | def fn[F[_]: Console, K[_]: Console](nt: F ~> List) = { 13 | def fk[G[_]: Functor](fa: G[String]) = G.map(fa)(_ => 1) 14 | 15 | fk(nt(F.read)) 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /test/src/main/scala/tests/nparams.scala: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | /** 4 | * Checks if the plugin is able to handle more than one 5 | * type constructor parameter with own context bounds 6 | */ 7 | object nparams { 8 | 9 | def two[F[_] : Console, G[_] : Console] = { 10 | F.read 11 | G.put("sda") 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/src/main/scala/tests/nslots.scala: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | /** 4 | * Checks if the plugin is able to handle kind-projector and type lambda syntax 5 | */ 6 | object nslots { 7 | 8 | trait Console2[F[_], R[_]] { 9 | def put(s: String): F[Unit] 10 | 11 | def read: R[String] 12 | } 13 | 14 | def lambda[F[_] : ({type λ[T[_]] = Console2[T, List]})#λ]: F[Unit] = F.put("") 15 | case class LambdaClass[F[_] : ({type λ[T[_]] = Console2[T, List]})#λ]() 16 | 17 | def projector[F[_] : Console2[*[_], List]]: List[String] = F.read 18 | case class ProjectorClass[F[_] : Console2[*[_], List]]() 19 | 20 | def projectorLambda[F[_]: λ[T[_] => Console[Trace[T, *]]]]: Trace[F, String] = F.read 21 | case class ProjectorLambdaClass[F[_]: λ[T[_] => Console[Trace[T, *]]]]() 22 | } 23 | -------------------------------------------------------------------------------- /test/src/main/scala/tests/package.scala: -------------------------------------------------------------------------------- 1 | package object tests { 2 | 3 | trait ~>[F[_], G[_]] { 4 | def apply[A](fa: F[A]): G[A] 5 | } 6 | 7 | trait Functor[F[_]] { 8 | def map[A, B](fa: F[A])(f: A => B): F[B] 9 | } 10 | 11 | trait Monad[F[_]] extends Functor[F] { 12 | def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] 13 | 14 | def pure[A](a: A): F[A] 15 | } 16 | 17 | trait Traverse[F[_]] extends Functor[F] { 18 | def traverse[G[_], A, B](fa: F[A])(f: A => G[B]): G[F[B]] 19 | } 20 | 21 | trait Console[F[_]] { 22 | def put(s: String): F[Unit] 23 | 24 | def read: F[String] 25 | } 26 | 27 | trait Trace[F[_], A] 28 | 29 | trait Monoid[A] 30 | } 31 | -------------------------------------------------------------------------------- /test/src/main/scala/tests/shadowed.scala: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | class parent[F[_]: Monad] { 4 | def work: F[Int] = F.pure(21) 5 | } 6 | 7 | // starting from Scala 2.13.2 on, this would result in a warning if the class for context-applied had a name 8 | // derived only from the bounds itself as both parent and shadowed would define a class with the same name 9 | class shadowed[F[_]: Monad] extends parent[F] { 10 | override def work: F[Int] = F.pure(42) 11 | } 12 | -------------------------------------------------------------------------------- /test/src/main/scala/tests/vclass.scala: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | /** 4 | * Checks if specifying context bounds in value classes does not break the plugin 5 | */ 6 | class vclass(private val b: Boolean) extends AnyVal { 7 | def fn[F[_] : Console] = "" 8 | } 9 | -------------------------------------------------------------------------------- /test/src/main/scala/tests/virtual.scala: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | object virtual { 4 | 5 | trait T { 6 | def fn[F[_]: Monad]: F[Unit] 7 | } 8 | 9 | abstract class AC[F[_]: Monad] { 10 | def concrete: F[String] = F.pure("") 11 | 12 | def fn[G[_]: Console]: F[Unit] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/src/test/scala/tests/dotnotation$test.scala: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import org.junit.Test 4 | import tests.dotnotation.{SomeClass, Witness} 5 | 6 | class dotnotation$test { 7 | 8 | @Test 9 | def run(): Unit = { 10 | implicit def m[A]: Monoid[A] = new Monoid[A] {} 11 | implicit val witness: Witness.Aux[Unit] = new Witness { 12 | type T = Unit 13 | val value: T = () 14 | } 15 | 16 | new SomeClass[Unit]().fn 17 | } 18 | } 19 | --------------------------------------------------------------------------------