Skip to content

Commit

Permalink
Dotty update #3 (#62)
Browse files Browse the repository at this point in the history
Official Dotty support in master with tests omitted due to lack of serialization support.

* Add Dotty 0.20.0-RC1 to build

* Fix non-local return in ConfigBeanImpl

* Fix non-local return in BadMap

* Fix non-local return in ConfigNodeField

* Fix non-local return in Path

* Fix non-local return in SerializedConfigValue

* Fix non-local return in ConfigNodeRoot

* Fix non-local return in AbstractConfigValue

* Add lines between methods and format

* Fix non-local return in SimpleConfigObject

* Fix non-local return in ConfigNodeObject part 1

* Fix non-local return in ConfigNodeObject part 1 - redo

* Reverse addition of Dotty so CI passes

* Update dotty to 0.21.0-RC1

* Fix non-local return in ConfigNodeObject part 2

* Remove default case causing dotty warning

* Fix non-local return in ConfigDelayedMergeObject

* Add Dotty back in with tests filtered that don't pass
  • Loading branch information
ekrich authored Dec 24, 2019
1 parent 0716d85 commit 1232911
Show file tree
Hide file tree
Showing 11 changed files with 253 additions and 216 deletions.
22 changes: 20 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ scalacOptions in (Compile, console) --= Seq(
val scala211 = "2.11.12"
val scala212 = "2.12.10"
val scala213 = "2.13.1"
val dotty = "0.20.0-RC1"
val dotty = "0.21.0-RC1"

val versionsBase = Seq(scala211, scala212, scala213)
val versionsJVM = versionsBase //:+ dotty
val versionsJVM = versionsBase :+ dotty
val versionsJS = versionsBase
val versionsNative = Seq(scala211)

Expand Down Expand Up @@ -127,6 +127,24 @@ lazy val sconfig = crossProject(JVMPlatform, NativePlatform, JSPlatform)
"-g",
"-Xlint:unchecked"
),
// Dotty is missing serializable support
// Can Filter based on Test name but not method name with "erializ"
// so exclude the Tests with the 19 that cannot pass
// 530 - 19 = 511 Only 346 get run this way so we lose coverage
Test / testOptions := {
if (isDotty.value)
Seq(
Tests.Exclude(
Seq(
"org.ekrich.config.impl.ValidationTest",
"org.ekrich.config.impl.PublicApiTest",
"org.ekrich.config.impl.ConfigValueTest",
"org.ekrich.config.impl.ConfigTest"
)
)
)
else Seq(Tests.Exclude(Seq()))
},
// because we test some global state such as singleton caches,
// we have to run tests in serial.
Test / parallelExecution := false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import java.time.Duration
import scala.reflect.ClassTag
import scala.jdk.CollectionConverters._
import scala.util.control.Breaks._
import scala.util.Try
import org.ekrich.config.Config
import org.ekrich.config.ConfigObject
import org.ekrich.config.ConfigList
Expand Down Expand Up @@ -327,17 +328,17 @@ object ConfigBeanImpl {
else null

private def hasAtLeastOneBeanProperty(clazz: Class[_]): Boolean = {
var beanInfo: BeanInfo = null
try beanInfo = Introspector.getBeanInfo(clazz)
catch {
case e: IntrospectionException =>
return false
}
for (beanProp <- beanInfo.getPropertyDescriptors) {
if (beanProp.getReadMethod != null && beanProp.getWriteMethod != null)
return true
val beanInfoOpt = Try(Introspector.getBeanInfo(clazz)).toOption
beanInfoOpt match {
case None => false
case Some(beanInfo) =>
beanInfo
.getPropertyDescriptors()
.exists(
beanProp =>
beanProp.getReadMethod != null && beanProp.getWriteMethod != null
)
}
false
}

private def isOptionalProperty(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import java.{lang => jl}
import java.{util => ju}
import ju.Collections

import scala.jdk.CollectionConverters._

import org.ekrich.config.ConfigException
import org.ekrich.config.ConfigMergeable
import org.ekrich.config.ConfigObject
Expand Down Expand Up @@ -64,19 +66,13 @@ object AbstractConfigValue {
def hasDescendantInList(
list: ju.List[AbstractConfigValue],
descendant: AbstractConfigValue
): Boolean = {
import scala.jdk.CollectionConverters._
for (v <- list.asScala) {
if (v == descendant) return true
}
// now the expensive traversal
for (v <- list.asScala) {
if (v.isInstanceOf[Container] && v
.asInstanceOf[Container]
.hasDescendant(descendant)) return true
}
false
}
): Boolean =
list.asScala.exists(_ == descendant) ||
// now the expensive traversal
list.asScala.exists { v =>
v.isInstanceOf[Container] &&
v.asInstanceOf[Container].hasDescendant(descendant)
}

def indent(
sb: jl.StringBuilder,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,8 @@ object BadMap {
983, 991, 997, 1009, /* now we start skipping some, this is arbitrary */
2053, 3079, 4057, 7103, 10949, 16069, 32609, 65867, 104729)

private def nextPrime(i: Int): Int = {
for (p <- primes) {
if (p > i) return p
}
/* oh well */
primes(primes.length - 1)
}
private def nextPrime(i: Int): Int =
primes.find(p => p > i).getOrElse(primes(primes.length - 1))
}

final class BadMap[K, V] private (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ package org.ekrich.config.impl

import java.{lang => jl}
import java.{util => ju}

import scala.annotation.tailrec
import scala.jdk.CollectionConverters._
import scala.util.control.Breaks._

import org.ekrich.config.ConfigException
import org.ekrich.config.ConfigList
import org.ekrich.config.ConfigMergeable
Expand Down Expand Up @@ -165,6 +167,9 @@ final class ConfigDelayedMergeObject(
override def keySet = throw ConfigDelayedMergeObject.notResolved
override def size = throw ConfigDelayedMergeObject.notResolved
override def values = throw ConfigDelayedMergeObject.notResolved

// exercised in ValidationTest.validationFailedSerializable
// and ConfigTest.test01Serializable
override def attemptPeekWithPartialResolve(
key: String
): AbstractConfigValue = {
Expand All @@ -179,77 +184,89 @@ final class ConfigDelayedMergeObject(
// spirit.
// we'll be able to return a key if we have a value that ignores
// fallbacks, prior to any unmergeable values.
for (layer <- stack.asScala) {
breakable {
if (layer.isInstanceOf[AbstractConfigObject]) {
val objectLayer =
layer.asInstanceOf[AbstractConfigObject]
val v =
objectLayer.attemptPeekWithPartialResolve(key)
if (v != null) {
if (v.ignoresFallbacks) {
// we know we won't need to merge anything in to this value
return v
} else {
// we can't return this value because we know there are
// unmergeable values later in the stack that may
// contain values that need to be merged with this
// value. we'll throw the exception when we get to those
// unmergeable values, so continue here.
break // continue
}
} else if (layer.isInstanceOf[Unmergeable]) {
// an unmergeable object (which would be another
// ConfigDelayedMergeObject) can't know that a key is
// missing, so it can't return null; it can only return a
// value or throw NotPossibleToResolve
throw new ConfigException.BugOrBroken(
"should not be reached: unmergeable object returned null value"
@tailrec
def loop(
layers: List[AbstractConfigValue]
): Either[ConfigException, AbstractConfigValue] =
layers match {
case Nil =>
// If we get here, then we never found anything unresolved which means
// the ConfigDelayedMergeObject should not have existed. some
// invariant was violated.
Left(
new ConfigException.BugOrBroken(
"Delayed merge stack does not contain any unmergeable values"
)
} else {
// a non-unmergeable AbstractConfigObject that returned null
// for the key in question is not relevant, we can keep
// looking for a value.
break // continue
}
} else if (layer.isInstanceOf[Unmergeable]) {
throw new ConfigException.NotResolved(
s"Key '$key' is not available at '${origin.description}' because value at '${layer.origin.description}'" +
s" has not been resolved and may turn out to contain or hide '$key'." +
" Be sure to Config#resolve() before using a config object."
)
} else if (layer.resolveStatus eq ResolveStatus.UNRESOLVED) {
// if the layer is not an object, and not a substitution or merge,
// then it's something that's unresolved because it _contains_
// an unresolved object... i.e. it's an array
if (!layer.isInstanceOf[ConfigList]) {
throw new ConfigException.BugOrBroken(
"Expecting a list here, not " + layer
)
}
// all later objects will be hidden so we can say we won't find
// the key
return null
} else {
// non-object, but resolved, like an integer or something.
// has no children so the one we're after won't be in it.
// we would only have this in the stack in case something
// else "looks back" to it due to a cycle.
// anyway at this point we know we can't find the key anymore.
if (!layer.ignoresFallbacks) {
throw new ConfigException.BugOrBroken(
"resolved non-object should ignore fallbacks"
)
case head :: tl =>
head match {
case layer: AbstractConfigObject => {
layer.attemptPeekWithPartialResolve(key) match {
case v if v != null =>
if (v.ignoresFallbacks) {
// we know we won't need to merge anything in to this value
Right(v)
} else {
// we can't return this value because we know there are
// unmergeable values later in the stack that may
// contain values that need to be merged with this
// value. we'll throw the exception when we get to those
// unmergeable values, so continue here.
loop(tl)
}
case _: Unmergeable =>
// an unmergeable object (which would be another
// ConfigDelayedMergeObject) can't know that a key is
// missing, so it can't return null; it can only return a
// value or throw NotPossibleToResolve
throw new ConfigException.BugOrBroken(
"should not be reached: unmergeable object returned null value"
)
case _ =>
// a non-unmergeable AbstractConfigObject that returned null
// for the key in question is not relevant, we can keep
// looking for a value.
loop(tl)
}
}
case _: Unmergeable =>
throw new ConfigException.NotResolved(
s"Key '$key' is not available at '${origin.description}' because value at '${head.origin.description}'" +
s" has not been resolved and may turn out to contain or hide '$key'." +
" Be sure to Config#resolve() before using a config object."
)
case layer if (layer.resolveStatus eq ResolveStatus.UNRESOLVED) =>
// if the layer is not an object, and not a substitution or merge,
// then it's something that's unresolved because it _contains_
// an unresolved object... i.e. it's an array
if (!layer.isInstanceOf[ConfigList]) {
throw new ConfigException.BugOrBroken(
"Expecting a list here, not " + layer
)
}
// all later objects will be hidden so we can say we won't find
// the key
Right(null) // can I get null from this??
case _ =>
// non-object, but resolved, like an integer or something.
// has no children so the one we're after won't be in it.
// we would only have this in the stack in case something
// else "looks back" to it due to a cycle.
// anyway at this point we know we can't find the key anymore.
if (!head.ignoresFallbacks) {
throw new ConfigException.BugOrBroken(
"resolved non-object should ignore fallbacks"
)
}
Right(null)
}
return null
}
}
// run the logic
loop(stack.asScala.toList) match {
case Right(res) =>
res
case Left(ex) =>
throw ex
}
// If we get here, then we never found anything unresolved which means
// the ConfigDelayedMergeObject should not have existed. some
// invariant was violated.
throw new ConfigException.BugOrBroken(
"Delayed merge stack does not contain any unmergeable values"
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ final class ConfigNodeField(_children: ju.Collection[AbstractConfigNode])
}
tokens
}

def replaceValue(newValue: AbstractConfigNodeValue): ConfigNodeField = {
val childrenCopy =
new ju.ArrayList[AbstractConfigNode](children)
Expand All @@ -31,6 +32,7 @@ final class ConfigNodeField(_children: ju.Collection[AbstractConfigNode])
}
throw new ConfigException.BugOrBroken("Field node doesn't have a value")
}

def value: AbstractConfigNodeValue = {
var i = 0
while (i < children.size) {
Expand All @@ -40,6 +42,7 @@ final class ConfigNodeField(_children: ju.Collection[AbstractConfigNode])
}
throw new ConfigException.BugOrBroken("Field node doesn't have a value")
}

def path: ConfigNodePath = {
var i = 0
while (i < children.size) {
Expand All @@ -49,16 +52,16 @@ final class ConfigNodeField(_children: ju.Collection[AbstractConfigNode])
}
throw new ConfigException.BugOrBroken("Field node doesn't have a path")
}
private[impl] def separator: Token = {
for (child <- children.asScala) {
if (child.isInstanceOf[ConfigNodeSingleToken]) {
val t = child.asInstanceOf[ConfigNodeSingleToken].token
if ((t eq Tokens.PLUS_EQUALS) || (t eq Tokens.COLON) || (t eq Tokens.EQUALS))
return t

private[impl] def separator: Token =
children.asScala.iterator
.filter(_.isInstanceOf[ConfigNodeSingleToken])
.map(_.asInstanceOf[ConfigNodeSingleToken].token)
.find { t =>
(t eq Tokens.PLUS_EQUALS) || (t eq Tokens.COLON) || (t eq Tokens.EQUALS)
}
}
null
}
.getOrElse(null)

private[impl] def comments: ju.List[String] = {
val comments = new ju.ArrayList[String]
for (child <- children.asScala) {
Expand Down
Loading

0 comments on commit 1232911

Please sign in to comment.