Skip to content

Commit

Permalink
#110 API cleanup, ready for 1.1.0-RC0 (#118)
Browse files Browse the repository at this point in the history
* fix a compilation warning

* large scale refactoring of PluginSettings to minimise boilerplate

Aggregate reduction & position into TypeDetail setting

position format has been simplified

scalafix defined & applied
  • Loading branch information
tribbloid committed Nov 5, 2023
1 parent 7fe1ec1 commit 17a9f8d
Show file tree
Hide file tree
Showing 19 changed files with 359 additions and 172 deletions.
25 changes: 25 additions & 0 deletions .scalafix.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
runner.dialect = scala213

// Built in rules
rules = [
NoAutoTupling // Rewrite that inserts explicit tuples for adapted argument lists for compatibility with -Yno-adapted-args
NoValInForComprehension
ProcedureSyntax
RemoveUnused
LeakingImplicitClassVal
// TODO: unrealiable due to lack of speculative modification, disabled
// ExplicitResultTypes
]

RemoveUnused {
imports = true
privates = true
locals = true
patternvars = true
params = true
}

ExplicitResultTypes {
rewriteStructuralTypesToNamedSubclass = false
skipSimpleDefinitions = false
}
9 changes: 6 additions & 3 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
version = "3.7.14"
runner.dialect = scala213


maxColumn = 120
lineEndings = unix

# Only format files tracked by git.
project.git = true
Expand All @@ -28,11 +28,14 @@ newlines {
rewrite {
rules = [
# // AvoidInfix,
Imports,
RedundantBraces,
RedundantParens,
SortModifiers,
SortImports
SortModifiers
]
imports {
sort=original
}
redundantBraces {
generalExpressions = false
methodBodies = false
Expand Down
35 changes: 31 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ The plugin can be configured via compiler Options with the format:
`param` can be one of the following:

| v0.x | built-in, v1.x | default value |
| ----------------- | ----------------------------------------- | ---------------- |
| ----------------- |-------------------------------------------|------------------|
| `all` | (dropped) | |
| `infix` | (dropped) | |
| `foundreq` | `Vtype-diffs` | |
Expand All @@ -131,6 +131,9 @@ The plugin can be configured via compiler Options with the format:
| `keepmodules` | (dropped) | 0 |
| (N/A) | `P:splain:Vimplicits-diverging` | false |
| (N/A) | `P:splain:Vimplicits-diverging-max-depth` | 100 |
| (N/A) | `P:splain:Vtype-detail` | 1 |
| (N/A) | `P:splain:Vtype-diffs-detail` | 1 |


`value` can either be `true` or `false`. If omitted, the default is `true` for
both value and parameter.
Expand Down Expand Up @@ -283,9 +286,9 @@ So with `-P:splain:keepmodules:2`, the qualified type `cats.free.FreeT.Suspend`
`free.FreeT.Suspend`, keeping the two segments `free.FreeT` before the type name.
The default is `0`, so only the type name itself will be displayed

# diverging implicit errors (experimental)
# expanding diverging implicit errors (experimental)

This error may be thrown by the Scala compiler if it cannot decide if an implicit search can terminate in polynomial time (e.g. if the search algorithm encounter a loop or infinite expansion). In most cases, such error will cause the entire search to fail immediately, but there are few exceptions to this rule, for which the search can backtrack and try an alternative path to fulfil the implicit argument. Either way, the Scala compiler error is only capable of showing the entry point of such loop or infinite expansion:
A `diverging implicit error` is thrown by compiler if it cannot decide if an implicit search can terminate in polynomial time (e.g. if the search algorithm encounter a loop or infinite expansion). In most cases, such error will cause the entire search to fail immediately, but there are few exceptions to this rule, for which the search can backtrack and try an alternative path to fulfil the implicit argument. Either way, the Scala compiler error is only capable of showing the entry point of such loop or infinite expansion:

```
diverging implicit expansion for type splain.DivergingImplicits.C
Expand All @@ -307,7 +310,31 @@ starting with method f in object Endo
starting with method f in object Endo
```

**WARNING!** This feature is marked "experimental" as sometimes it may cause failed implicit resolution to succeed, due to the delay in throwing the diverging implicit error. It may also increase compilation time slightly. If your build has been broken by this feature, please consider simplifying your code base to create a minimal reproducible test case, and submit it with a pull request.
**EXPERIMENTAL!** sometimes this feature may cause failed implicit resolution to succeed, due to the delay in throwing the diverging implicit error. It may also increase compilation time slightly. If your build has been broken by this feature, please consider simplifying your code base to create a minimal reproducible test case, and submit it with a pull request.

# type detail (experimental)

The option `-P:splain:Vtype-detail:X` can take an integer from 1 to 6 to attach different kinds of details to type information in any error message.

- `1` (DEFAULT) : type info in short form, by using toString (same as pre-1.1.0)
- `2` = **long** : type info in long form, by using toLongString
- `3` = `2` + (**existential** : existential context)
- `4` = `3` + (**reduction** : explain type reduction process)
- `5` = `4` + (**position** : type definition position in code)
- `6` = `5` + (**alias** : explain type aliases, this generally contains duplicate information with `3`, it is only included for completeness)

In addition, multiple names of the detail kind (denoted by bold text in the above list) can be appended to the option value to enable it, e.g. `-P:splain:Vtype-detail:1,reduction,position` can attach type reduction process & type definition position while bypassing **long** and **existential**.

# type diffs detail (experimental)

The option `-P:splain:Vtype-diffs-detail:X` can take an integer from 1 to 4 to augment type diff errors with different kinds of details.

- `1` (DEFAULT) : no augmentation (same as pre-1.1.0)
- `2` = **disambiguation** : augment type info with disambiguation for both sides in `<found>|<required>` and infix types (e.g. `A =:= B`, `A <:< B`) in error message
- `3` = `2` + (**builtIn** : attach built-in found/required errors emitted by Scala compiler IF AND ONLY IF both sides of the error message are identical)
- `4` = `3` + (**builtInAlways** : ALWAYS attach original found/required error info, even if both sides of the error message are different)

In addition, multiple names of the detail kind (denoted by bold text in the above list) can be appended to the option value to enable it, e.g. `-P:splain:Vtype-diffs-detail:1,builtIn` can attach built-in errors while bypassing **disambiguation**.

# Development

Expand Down
58 changes: 32 additions & 26 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ plugins {
id("io.github.gradle-nexus.publish-plugin") version "1.3.0"

id("com.github.ben-manes.versions") version "0.49.0"

id("io.github.cosmicsilence.scalafix") version "0.1.14"
}

val sonatypeApiUser = providers.gradleProperty("sonatypeApiUser")
Expand Down Expand Up @@ -127,6 +129,26 @@ allprojects {
}
}

dependencies {

// see https://github.com/gradle/gradle/issues/13067
fun bothImpl(constraintNotation: Any) {
implementation(constraintNotation)
testFixturesImplementation(constraintNotation)
}

constraints {}

bothImpl("${vs.scalaGroup}:scala-compiler:${vs.scalaV}")
bothImpl("${vs.scalaGroup}:scala-library:${vs.scalaV}")

val scalaTestV = "3.2.11"
testFixturesApi("org.scalatest:scalatest_${vs.scalaBinaryV}:${scalaTestV}")
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")

testRuntimeOnly("co.helmethair:scalatest-junit-runner:0.2.0")
}

task("dependencyTree") {

dependsOn("dependencies")
Expand All @@ -136,6 +158,9 @@ allprojects {

java {

withSourcesJar()
withJavadocJar()

sourceCompatibility = jvmTarget
targetCompatibility = jvmTarget
}
Expand Down Expand Up @@ -210,9 +235,10 @@ allprojects {
}
}

java {
withSourcesJar()
withJavadocJar()
apply(plugin = "io.github.cosmicsilence.scalafix")
scalafix {
semanticdb.autoConfigure.set(true)
semanticdb.version.set("4.8.11")
}

idea {
Expand Down Expand Up @@ -245,26 +271,6 @@ allprojects {

subprojects {

dependencies {

// see https://github.com/gradle/gradle/issues/13067
fun bothImpl(constraintNotation: Any) {
implementation(constraintNotation)
testFixturesImplementation(constraintNotation)
}

constraints {}

bothImpl("${vs.scalaGroup}:scala-compiler:${vs.scalaV}")
bothImpl("${vs.scalaGroup}:scala-library:${vs.scalaV}")

val scalaTestV = "3.2.11"
testFixturesApi("org.scalatest:scalatest_${vs.scalaBinaryV}:${scalaTestV}")
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")

testRuntimeOnly("co.helmethair:scalatest-junit-runner:0.2.0")
}

// https://stackoverflow.com/a/66352905/1772342

// val signingKeyID = providers.gradleProperty("signing.gnupg.keyID")
Expand Down Expand Up @@ -349,9 +355,9 @@ idea {
module {

excludeDirs = excludeDirs + files(
(".gradle"),
("gradle"),
("spike"),
".gradle",
"gradle",
"spike",
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,11 @@ trait ImplicitsExtension extends TyperCompatViews with typechecker.Implicits {
): SearchResult = {

import ImplicitsHistory._
import PluginSettings.Keys._

def getResult = super.inferImplicit(tree, pt, reportAmbiguous, isView, context, saveAmbiguousDivergent, pos)

if (settings.Vimplicits.value && pluginSettings.implicitDiverging) {
if (settings.Vimplicits.value && `Vimplicits-diverging`.isEnabled) {

val posII = PositionIndex(
tree.pos
Expand All @@ -80,7 +81,7 @@ trait ImplicitsExtension extends TyperCompatViews with typechecker.Implicits {
}

val previousSimilarErrorsN = previousSimilarErrors.size
if (previousSimilarErrorsN >= pluginSettings.implicitDivergingMaxDepth) {
if (previousSimilarErrorsN >= `Vimplicits-diverging-max-depth`.get) {

local.DivergingImplicitErrors.logs +=
s"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ class SplainAnalyzer(val global: Global, val pluginSettings: PluginSettings)
extends typechecker.Analyzer
with SplainFormattingExtension
with ImplicitsExtension
with SplainAnalyzerShim {
with SplainAnalyzerShim
with PluginSettings.Implicits {

override val specialFormatters: List[SpecialFormatter] =
List(
Expand All @@ -24,7 +25,7 @@ class SplainAnalyzer(val global: Global, val pluginSettings: PluginSettings)

val extra = mutable.Buffer.empty[String]

if (pluginSettings.debug) {
if (PluginSettings.Keys.debug.isEnabled) {

extra += "===[ ORIGINAL ERROR ]===" +
builtinFoundReqMsg(found, req) +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ trait SplainFormattingExtension extends typechecker.splain.SplainFormatting with

import SplainFormattingExtension._
import global._
import PluginSettings._

case class SplainImplicitErrorLink(
fromTree: ImplicitError,
Expand Down Expand Up @@ -396,9 +397,6 @@ trait SplainFormattingExtension extends typechecker.splain.SplainFormatting with
else {

val result = tpe.dealias
// if (pluginSettings.typeReduction) {
// TyperHistory.currentGlobal.TypeReductionCache.pushReduction(tpe)
// }
result
}
}
Expand All @@ -425,7 +423,8 @@ trait SplainFormattingExtension extends typechecker.splain.SplainFormatting with
lazy val flat: String = {
val (header, body) = formattedHeader_Body(false)

s"$header { ${body.map(v => v.flat).mkString(";")} }"
if (body.isEmpty) header
else s"$header { ${body.map(v => v.flat).mkString(";")} }"
}

lazy val broken: Seq[String] = {
Expand Down Expand Up @@ -456,8 +455,9 @@ trait SplainFormattingExtension extends typechecker.splain.SplainFormatting with

def index(): Unit = {

if (pluginSettings.showTypeReduction)
if (TypeDetail.reduction.isEnabled) {
Based += FormattedIndex(element) -> this
}
}

override protected def formattedHeader_Body(break: Boolean): (String, Seq[TypeRepr]) = {
Expand All @@ -474,7 +474,7 @@ trait SplainFormattingExtension extends typechecker.splain.SplainFormatting with

def index(): Unit = {

if (pluginSettings.TypeDiffsDetail.builtInMsg)
if (TypeDiffsDetail.`builtin-msg`.isEnabled)
Based += FormattedIndex(element) -> this
}

Expand All @@ -495,18 +495,19 @@ trait SplainFormattingExtension extends typechecker.splain.SplainFormatting with

case class DefPosition(
element: Formatted,
msg: String
srcInfo: String,
quotes: Seq[String] = Nil
) extends Based {

def index(): Unit = {

if (pluginSettings.showTypeDefPosition)
if (TypeDetail.position.isEnabled)
Based += FormattedIndex(element) -> this
}

override protected def formattedHeader_Body(break: Boolean): (String, Seq[TypeRepr]) = {

s"(defined at)" -> Seq(BrokenType(List(msg)))
s"(defined at $srcInfo)" -> quotes.map(quote => BrokenType(List(quote)))
}
}

Expand All @@ -519,7 +520,7 @@ trait SplainFormattingExtension extends typechecker.splain.SplainFormatting with
tpe.typeArgs match {
case List(t1, t2) =>
val result =
if (pluginSettings.TypeDiffsDetail.disambiguation) {
if (TypeDiffsDetail.disambiguation.isEnabled) {

withDisambiguation(Nil, t1, t2) {
formatTypeImplNoDisambiguation(tpe, top)
Expand All @@ -533,7 +534,7 @@ trait SplainFormattingExtension extends typechecker.splain.SplainFormatting with
case Infix(ii, left, right, _) =>
val noApparentDiff = (left == right) && (t1 != t2)

if (noApparentDiff || pluginSettings.TypeDiffsDetail.builtInMsgAlways) {
if (noApparentDiff || TypeDiffsDetail.`builtin-msg-always`.isEnabled) {

BuiltInDiffMsg(
result,
Expand Down Expand Up @@ -578,7 +579,7 @@ trait SplainFormattingExtension extends typechecker.splain.SplainFormatting with

override def formatDiffImpl(found: Type, req: Type, top: Boolean): Formatted = {

if (pluginSettings.TypeDiffsDetail.disambiguation) {
if (TypeDiffsDetail.disambiguation.isEnabled) {

val result = withDisambiguation(Nil, found, req) {
formatDiffImplNoDisambiguation(found, req, top)
Expand All @@ -588,7 +589,7 @@ trait SplainFormattingExtension extends typechecker.splain.SplainFormatting with
case diff: Diff =>
val noApparentDiff = (diff.left == diff.right) && (found != req)

if (noApparentDiff || pluginSettings.TypeDiffsDetail.builtInMsgAlways) {
if (noApparentDiff || TypeDiffsDetail.`builtin-msg-always`.isEnabled) {

BuiltInDiffMsg(
diff,
Expand Down
12 changes: 7 additions & 5 deletions core/src/main/scala-2.13.7+/latest/splain/SplainPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package splain
import scala.tools.nsc._
import scala.tools.nsc.typechecker.{Analyzer, MacroAnnotationNamers}

class SplainPlugin(val global: Global) extends SplainPluginLike {
class SplainPlugin(val global: Global) extends SplainPluginLike with PluginSettings.Implicits {

lazy val pluginSettings: PluginSettings = PluginSettings(this.opts)
override lazy val pluginSettings: PluginSettings = PluginSettings(this.opts)

lazy val splainAnalyzer: SplainAnalyzer =
if (global.settings.YmacroAnnotations.value)
Expand Down Expand Up @@ -47,8 +47,10 @@ class SplainPlugin(val global: Global) extends SplainPluginLike {
}

override def init(options: List[String], error: String => Unit): Boolean = {
def invalid(opt: String) = error(s"splain: invalid option `$opt`")
def setOpt(key: String, value: String) =
def invalid(opt: String): Unit = error(
s"splain: invalid option `$opt`, supported options are ${PluginSettings.nameToKey.map(kv => "`" + kv._1 + "`").mkString(", ")}"
)
def setOpt(key: String, value: String): Unit =
if (opts.contains(key))
opts.update(key, value)
else
Expand All @@ -63,6 +65,6 @@ class SplainPlugin(val global: Global) extends SplainPluginLike {
invalid(opt)
}
}
pluginSettings.enabled
PluginSettings.Keys.enabled.get
}
}
Loading

0 comments on commit 17a9f8d

Please sign in to comment.