-
Couldn't load subscription status.
- Fork 1.4k
Add HasNoScope type class (#9597)
#9604
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| package zio | ||
|
|
||
| import zio.test._ | ||
|
|
||
| object HasNoScopeSpec extends ZIOSpecDefault { | ||
| def isScopeError(e: Either[String, Unit], typeString: String): Boolean = e match { | ||
| case Left(err) => | ||
| err.startsWith( | ||
| s"""Can not prove that $typeString does not contain Scope. | ||
| |If $typeString contains a zio.Scope, please handle it explicitly. If it contains a generic type, add a context bound""".stripMargin | ||
| ) | ||
| case _ => false | ||
| } | ||
|
|
||
| def noScope[R: HasNoScope]: String = "noScope" | ||
|
|
||
| def genericWithImplicit[R: HasNoScope]: String = | ||
| noScope[R] | ||
|
|
||
| override def spec: Spec[TestEnvironment with Scope, Any] = | ||
| suiteAll("HasNoScope") { | ||
| test("no scope") { | ||
| noScope[Any] | ||
| assertTrue(true) | ||
| } | ||
| test("with implicit") { | ||
| genericWithImplicit[Any] | ||
| assertTrue(true) | ||
| } | ||
| test("with scope") { | ||
| typeCheck( | ||
| // language=Scala | ||
| """noScope[Scope]""" | ||
| ).map(e => assertTrue(e == Left("The type Scope contains a zio.Scope. This is not allowed."))) | ||
| } | ||
| test("generic") { | ||
| typeCheck( | ||
| // language=Scala | ||
| """ | ||
| def genericNoImplicit[R]: String = noScope[R] | ||
| """ | ||
| ).map(e => | ||
| assertTrue( | ||
| e == Left("Can not prove that R does not contain a zio.Scope. Please add a context bound R: HasNoScope.") | ||
| ) | ||
| ) | ||
| } | ||
| test("generic with R") { | ||
| typeCheck( | ||
| // language=Scala | ||
| """ | ||
| def genericNoImplicitWithR[R]: ZIO[Int & R, Nothing, Unit] = | ||
| noScope[Int & R] | ||
| """ | ||
| ).map(e => | ||
| assertTrue( | ||
| e == Left("Can not prove that R does not contain a zio.Scope. Please add a context bound R: HasNoScope.") | ||
| ) | ||
| ) | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| package zio.managed | ||
|
|
||
| import zio._ | ||
| import zio.test._ | ||
|
|
||
| object HasNoScopeSpec extends ZIOSpecDefault { | ||
| def isScopeError(e: Either[String, Unit], typeString: String): Boolean = e match { | ||
| case Left(err) => | ||
| err.startsWith( | ||
| s"""Can not prove that $typeString does not contain Scope. | ||
| |If $typeString contains a zio.Scope, please handle it explicitly. If it contains a generic type, add a context bound""".stripMargin | ||
| ) | ||
| case _ => false | ||
| } | ||
|
|
||
| def noScope[R: HasNoScope]: String = "noScope" | ||
|
|
||
| def genericWithImplicit[R: HasNoScope]: String = | ||
| noScope[R] | ||
|
|
||
| override def spec: Spec[TestEnvironment with Scope, Any] = | ||
| suiteAll("HasNoScope") { | ||
| test("no scope") { | ||
| noScope[Any] | ||
| assertTrue(true) | ||
| } | ||
| test("with implicit") { | ||
| genericWithImplicit[Any] | ||
| assertTrue(true) | ||
| } | ||
| test("with scope") { | ||
| typeCheck( | ||
| // language=Scala | ||
| """noScope[Scope]""" | ||
| ).map(e => assertTrue(isScopeError(e, "zio.Scope"))) | ||
| } | ||
| test("generic") { | ||
| typeCheck( | ||
| // language=Scala | ||
| """ | ||
| def genericNoImplicit[R]: String = noScope[R] | ||
| """ | ||
| ).map(e => assertTrue(isScopeError(e, "R"))) | ||
| } | ||
| test("generic with R") { | ||
| typeCheck( | ||
| // language=Scala | ||
| """ | ||
| def genericNoImplicitWithR[R]: ZIO[Int & R, Nothing, Unit] = | ||
| noScope[Int & R] | ||
| """ | ||
| ).map(e => assertTrue(isScopeError(e, "Int & R"))) | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| package zio | ||
|
|
||
| import scala.language.experimental.macros | ||
| import scala.reflect.macros.blackbox | ||
|
|
||
| private[zio] abstract class HasNoScopeCompanionVersionSpecific { | ||
|
|
||
| val instance: HasNoScope[Any] | ||
|
|
||
| implicit def noScope[R]: HasNoScope[R] = | ||
| macro HasNoScopeMacro.impl[R] | ||
| } | ||
|
|
||
| private[zio] class HasNoScopeMacro(val c: blackbox.Context) { | ||
| import c.universe._ | ||
|
|
||
| /** | ||
| * Given a type `A with B with C` You'll get back List[A,B,C] | ||
| */ | ||
| def intersectionTypes(self: Type): List[Type] = | ||
| self.dealias match { | ||
| case t: RefinedType => | ||
| t.parents.flatMap(intersectionTypes) | ||
| case TypeRef(_, sym, _) if sym.info.isInstanceOf[RefinedTypeApi] => | ||
| intersectionTypes(sym.info) | ||
| case other => | ||
| List(other) | ||
| } | ||
|
|
||
| def impl[R: c.WeakTypeTag]: c.Expr[HasNoScope[R]] = { | ||
| import c._ | ||
|
|
||
| val rType = weakTypeOf[R] | ||
| val rTypes = intersectionTypes(rType.dealias.map(_.dealias)) | ||
| val scopeType = weakTypeOf[zio.Scope] | ||
| if (rTypes.contains(scopeType)) { | ||
| val rName = rType.dealias.typeSymbol.name | ||
| c.abort( | ||
| c.enclosingPosition, | ||
| s"The type $rName contains a zio.Scope. This is not allowed." | ||
| ) | ||
| } else if (rType.typeSymbol.isParameter) { | ||
| val rName = rType.dealias.typeSymbol.name | ||
| c.abort( | ||
| c.enclosingPosition, | ||
| s"Can not prove that $rName does not contain a zio.Scope. Please add a context bound $rName: HasNoScope." | ||
| ) | ||
| } else if (rTypes.exists(_.typeSymbol.isParameter)) { | ||
| val rName = rTypes.find(_.typeSymbol.isParameter).get.typeSymbol.name | ||
| c.abort( | ||
| c.enclosingPosition, | ||
| s"Can not prove that $rName does not contain a zio.Scope. Please add a context bound $rName: HasNoScope." | ||
| ) | ||
| } else { | ||
| reify(HasNoScope.instance.asInstanceOf[HasNoScope[R]]) | ||
| } | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| package zio | ||
|
|
||
| import scala.quoted._ | ||
|
|
||
| private[zio] abstract class HasNoScopeCompanionVersionSpecific { | ||
|
|
||
| val instance: HasNoScope[Any] | ||
|
|
||
| final transparent inline given hasNoScope[R]: HasNoScope[R] = | ||
| ${ HasNoScopeMacro.noScope[R] } | ||
|
|
||
| } | ||
|
|
||
| private[zio] object HasNoScopeMacro { | ||
|
|
||
| def noScope[R: Type](using Quotes): Expr[HasNoScope[R]] = { | ||
| import quotes.reflect._ | ||
|
|
||
| def intersectionTypes(tpe: TypeRepr): List[Symbol] = tpe.dealias match { | ||
| case AndType(left, right) => intersectionTypes(left) ++ intersectionTypes(right) | ||
| case other => List(other.typeSymbol) | ||
| } | ||
|
|
||
| val rTypes = intersectionTypes(TypeRepr.of[R]) | ||
| val scopeType = TypeRepr.of[zio.Scope].typeSymbol | ||
|
|
||
| if (rTypes.contains(scopeType)) { | ||
| val rName = TypeRepr.of[R].typeSymbol.name | ||
| report.errorAndAbort(s"The type $rName contains a zio.Scope. This is not allowed.") | ||
| } else if (rTypes.exists(_.isTypeParam)) { | ||
| val rName = rTypes.find(_.isTypeParam).get.name | ||
| report.errorAndAbort( | ||
| s"Can not prove that $rName does not contain a zio.Scope. Please add a context bound ${rName}: HasNoScope." | ||
| ) | ||
| } else { | ||
| '{ HasNoScope.instance.asInstanceOf[HasNoScope[R]] } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package zio | ||
|
|
||
| @scala.annotation.implicitNotFound( | ||
| """Can not prove that ${R} does not contain Scope. | ||
| If ${R} contains a zio.Scope, please handle it explicitly. If it contains a generic type, add a context bound | ||
| like def myMethod[R: HasNoScope](...) = ...""" | ||
| ) | ||
| sealed trait HasNoScope[R] | ||
|
|
||
| object HasNoScope extends HasNoScopeCompanionVersionSpecific { | ||
| override val instance: HasNoScope[Any] = new HasNoScope[Any] {} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we change this to package-private? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will be called by the inlined code of the macro. If it is not public, the compiler will fail There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. package-private methods / vals are public when compiled. Have you tried changing it to package-private and it didn't work? |
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.