Skip to content

Commit d04f511

Browse files
committed
a couple of more test cases for variants of similar cases, and a fix for those
1 parent e7d669a commit d04f511

2 files changed

Lines changed: 51 additions & 0 deletions

File tree

config/src/main/java/com/typesafe/config/impl/ConfigDelayedMerge.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,18 @@ static ResolveResult<? extends AbstractConfigValue> resolveSubstitutions(Replace
8484
int count = 0;
8585
AbstractConfigValue merged = null;
8686
for (AbstractConfigValue end : stack) {
87+
// Per the HOCON spec, a substitution hidden by a value that
88+
// cannot be merged with it is never evaluated. Once the
89+
// accumulated merged value ignores fallbacks, nothing below it
90+
// in the stack can contribute, so skip it to avoid evaluating
91+
// substitutions that cannot affect the result.
92+
if (merged != null && merged.ignoresFallbacks()) {
93+
if (ConfigImpl.traceSubstitutionsEnabled())
94+
ConfigImpl.trace(newContext.depth(),
95+
"merged ignores fallbacks, skipping remaining stack");
96+
break;
97+
}
98+
8799
// the end value may or may not be resolved already
88100

89101
ResolveSource sourceForEnd;

config/src/test/scala/com/typesafe/config/impl/ConfigSubstitutionTest.scala

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1304,4 +1304,43 @@ class ConfigSubstitutionTest extends TestUtils {
13041304
val resolvedViaSub = resolve(viaSub)
13051305
assertEquals(parseObject("""{ a : { x : 1 } }"""), resolvedViaSub.getConfig("p").root)
13061306
}
1307+
1308+
// Spec (HOCON.md): "If a substitution is hidden by a value that could not
1309+
// be merged with it (by a non-object value) then it is never evaluated and
1310+
// no error will be reported." The direct top-level case from the spec.
1311+
@Test
1312+
def substitutionHiddenByLiteralNonObjectIsNotEvaluated() {
1313+
val obj = parseObject("""
1314+
foo: ${does-not-exist}
1315+
foo: 42
1316+
""")
1317+
val resolved = resolve(obj)
1318+
assertEquals(42, resolved.getInt("foo"))
1319+
}
1320+
1321+
// Variant of #838: an object containing a substitution is hidden by a
1322+
// literal non-object. The object cannot merge with 42, so the enclosed
1323+
// substitution must not be evaluated.
1324+
@Test
1325+
def substitutionInsideObjectHiddenByLiteralNonObjectIsNotEvaluated() {
1326+
val obj = parseObject("""
1327+
p: { a : ${does-not-exist} }
1328+
p: 42
1329+
""")
1330+
val resolved = resolve(obj)
1331+
assertEquals(42, resolved.getInt("p"))
1332+
}
1333+
1334+
// Variant of #838: an object containing a substitution is hidden by a
1335+
// substitution that resolves to a non-object. Same spec intent applies.
1336+
@Test
1337+
def substitutionInsideObjectHiddenByNonObjectFromSubstitutionIsNotEvaluated() {
1338+
val obj = parseObject("""
1339+
p: { a : ${does-not-exist} }
1340+
p: ${z}
1341+
z: 42
1342+
""")
1343+
val resolved = resolve(obj)
1344+
assertEquals(42, resolved.getInt("p"))
1345+
}
13071346
}

0 commit comments

Comments
 (0)