Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 64 additions & 9 deletions src/compiler/default_compiler_draft3.h
Original file line number Diff line number Diff line change
Expand Up @@ -856,15 +856,56 @@ auto compiler_draft3_applicator_properties_with_options(
}
}

if (fusion_possible && substeps.size() >= 2 &&
std::ranges::any_of(substeps, [](const auto &step) -> auto {
return step.type ==
InstructionIndex::AssertionObjectPropertiesSimple;
})) {
std::erase_if(substeps, [](const auto &step) -> auto {
// A fused check rejects a non-object and enforces the presence of the
// properties it marks as required, but only those, and only at its own
// instance location. This list is flattened and may also hold checks
// merged in from elsewhere, as every `allOf` branch contributes to it,
// so we may only drop what the fusion provably covers
std::vector<
std::pair<sourcemeta::core::Pointer, std::unordered_set<std::string>>>
fusions;
if (fusion_possible && substeps.size() >= 2) {
for (const auto &step : substeps) {
if (step.type != InstructionIndex::AssertionObjectPropertiesSimple) {
continue;
}

auto match{
std::ranges::find_if(fusions, [&step](const auto &entry) -> bool {
return entry.first == step.relative_instance_location;
})};
if (match == fusions.end()) {
fusions.emplace_back(step.relative_instance_location,
std::unordered_set<std::string>{});
match = std::prev(fusions.end());
}

for (const auto &entry :
std::get<ValueObjectProperties>(step.value)) {
if (std::get<2>(entry)) {
match->second.insert(std::get<0>(entry));
}
}
}
}

if (!fusions.empty()) {
std::erase_if(substeps, [&fusions](const auto &step) -> auto {
const auto fusion{
std::ranges::find_if(fusions, [&step](const auto &entry) -> bool {
return entry.first == step.relative_instance_location;
})};
if (fusion == fusions.cend()) {
return false;
}

if (step.type == InstructionIndex::AssertionDefinesAllStrict ||
step.type == InstructionIndex::AssertionDefinesAll) {
return true;
const auto &value{std::get<ValueStringSet>(step.value)};
return std::ranges::all_of(
value, [&fusion](const auto &property) -> auto {
return fusion->second.contains(property.first);
});
}

if ((step.type == InstructionIndex::AssertionTypeStrict ||
Expand All @@ -878,9 +919,23 @@ auto compiler_draft3_applicator_properties_with_options(
});
}

if (fusion_possible && substeps.size() == 1 &&
// Fusion re-applies the single check to every property of the enclosing
// loop with its instance location cleared, so a check whose meaning
// depends on its own instance location cannot be relocated this way. A
// jump into a separate target, or a check that navigates into or gates
// on a nested location such as the one a `properties` conditional
// produces, therefore falls back to unfused emission
const bool fusable_single{
substeps.size() == 1 &&
substeps.front().type != InstructionIndex::ControlJump &&
substeps.front().type != InstructionIndex::ControlDynamicAnchorJump) {
substeps.front().type != InstructionIndex::ControlDynamicAnchorJump &&
substeps.front().type != InstructionIndex::ControlGroup &&
substeps.front().type != InstructionIndex::ControlGroupWhenDefines &&
substeps.front().type !=
InstructionIndex::ControlGroupWhenDefinesDirect &&
substeps.front().type != InstructionIndex::ControlGroupWhenType &&
substeps.front().type != InstructionIndex::ControlEvaluate};
Comment thread
jviotti marked this conversation as resolved.
if (fusion_possible && fusable_single) {
const auto is_required{assume_object && required.contains(name)};
auto prop{make_property(name)};
auto fusion_child{substeps.front()};
Expand Down
126 changes: 126 additions & 0 deletions test/evaluator/evaluator_2019_09.json
Original file line number Diff line number Diff line change
Expand Up @@ -6710,5 +6710,131 @@
"The value was expected to be of type object"
]
}
},
{
"description": "nested_properties_fusion_valid",
"schema": {
"$schema": "https://json-schema.org/draft/2019-09/schema",
"type": "object",
"properties": { "a": { "properties": { "c": { "type": "string", "minLength": 2 } } } }
},
"instance": { "a": { "c": "xy" } },
"valid": true,
"fast": {
"pre": [
[ "AssertionTypeStringBounded", "/properties/a/properties/c/type", "#/properties/a/properties/c/type", "/a/c" ],
[ "AssertionTypeStrict", "/type", "#/type", "" ]
],
"post": [
[ true, "AssertionTypeStringBounded", "/properties/a/properties/c/type", "#/properties/a/properties/c/type", "/a/c" ],
[ true, "AssertionTypeStrict", "/type", "#/type", "" ]
],
"descriptions": [
"The value was expected to consist of a string of at least 2 characters",
"The value was expected to be of type object"
]
},
"exhaustive": {
"pre": [
[ "LogicalWhenType", "/properties", "#/properties", "" ],
[ "LogicalWhenType", "/properties/a/properties", "#/properties/a/properties", "/a" ],
[ "AssertionStringSizeGreater", "/properties/a/properties/c/minLength", "#/properties/a/properties/c/minLength", "/a/c" ],
[ "AssertionTypeStrict", "/properties/a/properties/c/type", "#/properties/a/properties/c/type", "/a/c" ],
[ "Annotation", "/properties/a/properties", "#/properties/a/properties", "/a" ],
[ "Annotation", "/properties", "#/properties", "" ],
[ "AssertionTypeStrict", "/type", "#/type", "" ]
],
"post": [
[ true, "AssertionStringSizeGreater", "/properties/a/properties/c/minLength", "#/properties/a/properties/c/minLength", "/a/c" ],
[ true, "AssertionTypeStrict", "/properties/a/properties/c/type", "#/properties/a/properties/c/type", "/a/c" ],
[ true, "Annotation", "/properties/a/properties", "#/properties/a/properties", "/a", "c" ],
[ true, "LogicalWhenType", "/properties/a/properties", "#/properties/a/properties", "/a" ],
[ true, "Annotation", "/properties", "#/properties", "", "a" ],
[ true, "LogicalWhenType", "/properties", "#/properties", "" ],
[ true, "AssertionTypeStrict", "/type", "#/type", "" ]
],
"descriptions": [
"The string value \"xy\" was expected to consist of at least 2 characters and it consisted of 2 characters",
"The value was expected to be of type string",
"The object property \"c\" successfully validated against its property subschema",
"The object value was expected to validate against the single defined property subschema",
"The object property \"a\" successfully validated against its property subschema",
"The object value was expected to validate against the single defined property subschema",
"The value was expected to be of type object"
]
}
},
{
"description": "nested_properties_fusion_tooshort",
"schema": {
"$schema": "https://json-schema.org/draft/2019-09/schema",
"type": "object",
"properties": { "a": { "properties": { "c": { "type": "string", "minLength": 2 } } } }
},
"instance": { "a": { "c": "x" } },
"valid": false,
"fast": {
"pre": [
[ "AssertionTypeStringBounded", "/properties/a/properties/c/type", "#/properties/a/properties/c/type", "/a/c" ]
],
"post": [
[ false, "AssertionTypeStringBounded", "/properties/a/properties/c/type", "#/properties/a/properties/c/type", "/a/c" ]
],
"descriptions": [
"The value was expected to consist of a string of at least 2 characters"
]
},
"exhaustive": {
"pre": [
[ "LogicalWhenType", "/properties", "#/properties", "" ],
[ "LogicalWhenType", "/properties/a/properties", "#/properties/a/properties", "/a" ],
[ "AssertionStringSizeGreater", "/properties/a/properties/c/minLength", "#/properties/a/properties/c/minLength", "/a/c" ]
],
"post": [
[ false, "AssertionStringSizeGreater", "/properties/a/properties/c/minLength", "#/properties/a/properties/c/minLength", "/a/c" ],
[ false, "LogicalWhenType", "/properties/a/properties", "#/properties/a/properties", "/a" ],
[ false, "LogicalWhenType", "/properties", "#/properties", "" ]
],
"descriptions": [
"The string value \"x\" was expected to consist of at least 2 characters but it consisted of 1 character",
"The object value was expected to validate against the single defined property subschema",
"The object value was expected to validate against the single defined property subschema"
]
}
},
{
"description": "nested_properties_fusion_absent",
"schema": {
"$schema": "https://json-schema.org/draft/2019-09/schema",
"type": "object",
"properties": { "a": { "properties": { "c": { "type": "string", "minLength": 2 } } } }
},
"instance": { "other": 1 },
"valid": true,
"fast": {
"pre": [
[ "AssertionTypeStrict", "/type", "#/type", "" ]
],
"post": [
[ true, "AssertionTypeStrict", "/type", "#/type", "" ]
],
"descriptions": [
"The value was expected to be of type object"
]
},
"exhaustive": {
"pre": [
[ "LogicalWhenType", "/properties", "#/properties", "" ],
[ "AssertionTypeStrict", "/type", "#/type", "" ]
],
"post": [
[ true, "LogicalWhenType", "/properties", "#/properties", "" ],
[ true, "AssertionTypeStrict", "/type", "#/type", "" ]
],
"descriptions": [
"The object value was expected to validate against the single defined property subschema",
"The value was expected to be of type object"
]
}
}
]
Loading
Loading