Skip to content
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

fix typesafeconfig HOCON include #131

Merged
merged 2 commits into from
May 31, 2021
Merged

fix typesafeconfig HOCON include #131

merged 2 commits into from
May 31, 2021

Conversation

bjaglin
Copy link
Member

@bjaglin bjaglin commented May 26, 2021

Originally reported in scalacenter/scalafix#1414. I was surprised it was not first reported through Scalafmt, but it looks like the bump there was recent, so no release was cut with the regression.

Since df7731d TypesafeConfig2Class was always looking up positions from the main input, causing incorrect mappings at best, or parsing failures when a key was defined in an included file larger than the main one (1) or when a key was defined across files (2).

(1)

java.lang.IllegalArgumentException: 8 is not a valid line number, allowed [0..1]
 at metaconfig.Input.lineToOffset(Input.scala:36)
 at metaconfig.typesafeconfig.Typesafe...(TypesafeConfig2Class.scala:85)

(2)

java.lang.IllegalArgumentException: -2 is not a valid line number, allowed [0..3]
 at metaconfig.Input.lineToOffset(Input.scala:36)
 at metaconfig.typesafeconfig.Typesafe...(TypesafeConfig2Class.scala:85)

@bjaglin bjaglin changed the title fix typesafeconfig HOCON "include" fix typesafeconfig HOCON include May 26, 2021
input <- cache.updateWith(OriginId(origin)) { maybeCurrent =>
maybeCurrent
.orElse(
Option(origin.url)
Copy link
Member Author

@bjaglin bjaglin May 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this allows to short-circuit the for comprehension when we don't have a URL for the not-seen-before origin, which is the case when a key is defined across files like a in the test

Since df7731d TypesafeConfig2Class was always looking up positions from
the main input, causing incorrect mappings at best, or parsing failures
when a key was defined in an included file larger than the main one (1)
or when a key was defined across files (2).

(1) java.lang.IllegalArgumentException: 8 is not a valid line number,
allowed [0..1]
 at metaconfig.Input.lineToOffset(Input.scala:36)
 at metaconfig.typesafeconfig.Typesafe...(TypesafeConfig2Class.scala:85)

(2) java.lang.IllegalArgumentException: -2 is not a valid line number,
allowed [0..3]
 at metaconfig.Input.lineToOffset(Input.scala:36)
 at metaconfig.typesafeconfig.Typesafe...(TypesafeConfig2Class.scala:85)
.withLineNumber(-1) // strip potential line suffix
// use description instead of the nullable filename() or url(),
// so that we can have an identifier to map to the main input
// when using ConfigFactory.parseString(string)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the complexity of the new implementation comes from gimmeConfFromStringFilename, present in TypesafeConfig2Class but not in Sconfig2Class, and tested via

def checkError(
name: String,
input: String,
expectedError: String
)(implicit loc: munit.Location): Unit = {
test(name) {
val parsed = Hocon.parseFilename("hello.conf", input)(EchoOptions.decoder)
assert(clue(parsed).isNotOk)(munit.Location.generate)
assertNoDiff(
parsed.toEither.left.get.toString,
expectedError
)
}
}
checkError(
"basic",
"foo = bar",
"""|hello.conf:1:0 error: found option 'foo' which wasn't expected, or isn't valid in this context.
|foo = ba
|^
|""".stripMargin
)

@@ -32,7 +32,7 @@ object TypesafeConfig2Class {
input: Input,
config: () => Config
): Configured[Conf] = {
val cache = mutable.Map.empty[Input, Array[Int]]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is unused on main

Copy link
Member

@olafurpg olafurpg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM nice fix! A lot of pain could have been saved if Scalafmt would have used HOCON idiomatically from the start by supporting include. I resisted supporting include because I had (unfound) concerns it wouldn’t work on JS. In hindsight, it would have been easier to embrace all HOCON features, instead of only a subset.

maybeCurrent
.orElse(
Option(origin.url)
.map(url => Input.File(new java.io.File(url.toURI)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s best to use Input.VirtualFile to avoid hashCode/equals issues when comparing inputs. Input.File should never be used because it has the same hashCode even if the underlying file contents change.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! I guess in that case it's not a big deal as the Input instances are not used as keys and don't escape private methods? I don't see any factory method for creating a Input.VirtualFile out of a Input.File so I wonder if the extra steps are really worth it here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, then it should be fine to use Input.File here. I think it would make sense to deprecate Input.File at some point since it's easy to screw up these things resulting in surprising behavior

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants