From 142cda7418cea80ad8ca2ecec3eaa2acc1beb63a Mon Sep 17 00:00:00 2001 From: MikePopoloski Date: Sun, 17 Nov 2024 11:51:39 -0500 Subject: [PATCH] Make sequence no-match error be a warning instead --- include/slang/ast/expressions/AssertionExpr.h | 74 +++++--- scripts/diagnostics.txt | 6 +- scripts/warning_docs.txt | 8 + source/ast/expressions/AssertionExpr.cpp | 163 ++++++++++++------ source/ast/expressions/MiscExpressions.cpp | 2 +- 5 files changed, 175 insertions(+), 78 deletions(-) diff --git a/include/slang/ast/expressions/AssertionExpr.h b/include/slang/ast/expressions/AssertionExpr.h index d690d0093..d8e69fc39 100644 --- a/include/slang/ast/expressions/AssertionExpr.h +++ b/include/slang/ast/expressions/AssertionExpr.h @@ -127,13 +127,6 @@ class SLANG_EXPORT AssertionExpr { /// Indicates whether the expression is invalid. bool bad() const { return kind == AssertionExprKind::Invalid; } - /// Checks whether this assertion expression is nondegenerate or whether it has - /// properties of degeneracy (admitting empty matches or no matches at all). - bitmask checkNondegeneracy() const; - - /// Computes possible clock ticks (delay) length of sequence under assertion expression. - std::optional computeSequenceLength() const; - /// Specifies binding behavior of property expressions as /// it pertains to nondegeneracy checking. enum class NondegeneracyRequirement { @@ -151,6 +144,41 @@ class SLANG_EXPORT AssertionExpr { NonOverlapOp, }; + /// A result structure for checking nondegeneracy. + struct NondegeneracyCheckResult { + /// The nondegeneracy status of the expression. + bitmask status; + + /// The range of the expression that caused status + /// to contain NondegeneracyStatus::AdmitsNoMatch. + SourceRange noMatchRange; + + /// Set to true if the expression is known to be always false. + bool isAlwaysFalse = false; + + NondegeneracyCheckResult& operator|=(const NondegeneracyCheckResult& rhs) { + status |= rhs.status; + if (!noMatchRange.start()) { + noMatchRange = rhs.noMatchRange; + isAlwaysFalse = rhs.isAlwaysFalse; + } + return *this; + } + + NondegeneracyCheckResult operator|(const NondegeneracyCheckResult& rhs) const { + auto result = *this; + result |= rhs; + return result; + } + }; + + /// Checks whether this assertion expression is nondegenerate or whether it has + /// properties of degeneracy (admitting empty matches or no matches at all). + NondegeneracyCheckResult checkNondegeneracy() const; + + /// Computes possible clock ticks (delay) length of sequence under assertion expression. + std::optional computeSequenceLength() const; + static const AssertionExpr& bind(const syntax::SequenceExprSyntax& syntax, const ASTContext& context, bool allowDisable = false); @@ -230,9 +258,7 @@ class SLANG_EXPORT InvalidAssertionExpr : public AssertionExpr { explicit InvalidAssertionExpr(const AssertionExpr* child) : AssertionExpr(AssertionExprKind::Invalid), child(child) {} - bitmask checkNondegeneracyImpl() const { - return NondegeneracyStatus::None; - } + NondegeneracyCheckResult checkNondegeneracyImpl() const { return {}; } std::optional computeSequenceLengthImpl() const { return {}; } @@ -263,7 +289,7 @@ struct SequenceRepetition { /// Checks whether this assertion expression is nondegenerate or whether it has /// properties of degeneracy (admitting empty matches or no matches at all). - bitmask checkNondegeneracy() const; + AssertionExpr::NondegeneracyCheckResult checkNondegeneracy() const; /// Applies the repetition to the given range, scaling it and returning the result. SequenceRange applyTo(SequenceRange other) const; @@ -289,7 +315,7 @@ class SLANG_EXPORT SimpleAssertionExpr : public AssertionExpr { isNullExpr(isNullExpr) {} void requireSequence(const ASTContext& context, DiagCode code) const; - bitmask checkNondegeneracyImpl() const; + NondegeneracyCheckResult checkNondegeneracyImpl() const; std::optional computeSequenceLengthImpl() const; static AssertionExpr& fromSyntax(const syntax::SimpleSequenceExprSyntax& syntax, @@ -323,7 +349,7 @@ class SLANG_EXPORT SequenceConcatExpr : public AssertionExpr { explicit SequenceConcatExpr(std::span elements) : AssertionExpr(AssertionExprKind::SequenceConcat), elements(elements) {} - bitmask checkNondegeneracyImpl() const; + NondegeneracyCheckResult checkNondegeneracyImpl() const; std::optional computeSequenceLengthImpl() const; static AssertionExpr& fromSyntax(const syntax::DelayedSequenceExprSyntax& syntax, @@ -358,7 +384,7 @@ class SLANG_EXPORT SequenceWithMatchExpr : public AssertionExpr { AssertionExpr(AssertionExprKind::SequenceWithMatch), expr(expr), repetition(repetition), matchItems(matchItems) {} - bitmask checkNondegeneracyImpl() const; + NondegeneracyCheckResult checkNondegeneracyImpl() const; std::optional computeSequenceLengthImpl() const; static AssertionExpr& fromSyntax(const syntax::ParenthesizedSequenceExprSyntax& syntax, @@ -394,7 +420,7 @@ class SLANG_EXPORT UnaryAssertionExpr : public AssertionExpr { std::optional range) : AssertionExpr(AssertionExprKind::Unary), op(op), expr(expr), range(range) {} - bitmask checkNondegeneracyImpl() const { return {}; } + NondegeneracyCheckResult checkNondegeneracyImpl() const { return {}; } std::optional computeSequenceLengthImpl() const { return {}; } static AssertionExpr& fromSyntax(const syntax::UnaryPropertyExprSyntax& syntax, @@ -431,7 +457,7 @@ class SLANG_EXPORT BinaryAssertionExpr : public AssertionExpr { void requireSequence(const ASTContext& context, DiagCode code) const; - bitmask checkNondegeneracyImpl() const; + NondegeneracyCheckResult checkNondegeneracyImpl() const; std::optional computeSequenceLengthImpl() const; static AssertionExpr& fromSyntax(const syntax::BinarySequenceExprSyntax& syntax, @@ -464,7 +490,7 @@ class SLANG_EXPORT FirstMatchAssertionExpr : public AssertionExpr { std::span matchItems) : AssertionExpr(AssertionExprKind::FirstMatch), seq(seq), matchItems(matchItems) {} - bitmask checkNondegeneracyImpl() const; + NondegeneracyCheckResult checkNondegeneracyImpl() const; std::optional computeSequenceLengthImpl() const { return seq.computeSequenceLength(); @@ -497,9 +523,7 @@ class SLANG_EXPORT ClockingAssertionExpr : public AssertionExpr { ClockingAssertionExpr(const TimingControl& clocking, const AssertionExpr& expr) : AssertionExpr(AssertionExprKind::Clocking), clocking(clocking), expr(expr) {} - bitmask checkNondegeneracyImpl() const { - return expr.checkNondegeneracy(); - } + NondegeneracyCheckResult checkNondegeneracyImpl() const { return expr.checkNondegeneracy(); } std::optional computeSequenceLengthImpl() const { return expr.computeSequenceLength(); @@ -540,7 +564,7 @@ class SLANG_EXPORT StrongWeakAssertionExpr : public AssertionExpr { StrongWeakAssertionExpr(const AssertionExpr& expr, Strength strength) : AssertionExpr(AssertionExprKind::StrongWeak), expr(expr), strength(strength) {} - bitmask checkNondegeneracyImpl() const { return {}; } + NondegeneracyCheckResult checkNondegeneracyImpl() const { return {}; } std::optional computeSequenceLengthImpl() const { return {}; } static AssertionExpr& fromSyntax(const syntax::StrongWeakPropertyExprSyntax& syntax, @@ -576,7 +600,7 @@ class SLANG_EXPORT AbortAssertionExpr : public AssertionExpr { AssertionExpr(AssertionExprKind::Abort), condition(condition), expr(expr), action(action), isSync(isSync) {} - bitmask checkNondegeneracyImpl() const { return {}; } + NondegeneracyCheckResult checkNondegeneracyImpl() const { return {}; } std::optional computeSequenceLengthImpl() const { return {}; } static AssertionExpr& fromSyntax(const syntax::AcceptOnPropertyExprSyntax& syntax, @@ -610,7 +634,7 @@ class SLANG_EXPORT ConditionalAssertionExpr : public AssertionExpr { AssertionExpr(AssertionExprKind::Conditional), condition(condition), ifExpr(ifExpr), elseExpr(elseExpr) {} - bitmask checkNondegeneracyImpl() const { return {}; } + NondegeneracyCheckResult checkNondegeneracyImpl() const { return {}; } std::optional computeSequenceLengthImpl() const { return {}; } static AssertionExpr& fromSyntax(const syntax::ConditionalPropertyExprSyntax& syntax, @@ -655,7 +679,7 @@ class SLANG_EXPORT CaseAssertionExpr : public AssertionExpr { AssertionExpr(AssertionExprKind::Case), expr(expr), items(items), defaultCase(defaultCase) { } - bitmask checkNondegeneracyImpl() const { return {}; } + NondegeneracyCheckResult checkNondegeneracyImpl() const { return {}; } std::optional computeSequenceLengthImpl() const { return {}; } static AssertionExpr& fromSyntax(const syntax::CasePropertyExprSyntax& syntax, @@ -691,7 +715,7 @@ class SLANG_EXPORT DisableIffAssertionExpr : public AssertionExpr { DisableIffAssertionExpr(const Expression& condition, const AssertionExpr& expr) : AssertionExpr(AssertionExprKind::DisableIff), condition(condition), expr(expr) {} - bitmask checkNondegeneracyImpl() const { return {}; } + NondegeneracyCheckResult checkNondegeneracyImpl() const { return {}; } std::optional computeSequenceLengthImpl() const { return {}; } static AssertionExpr& fromSyntax(const syntax::DisableIffSyntax& syntax, diff --git a/scripts/diagnostics.txt b/scripts/diagnostics.txt index 1c81b776b..508137a9b 100644 --- a/scripts/diagnostics.txt +++ b/scripts/diagnostics.txt @@ -870,8 +870,8 @@ error ForkJoinAlwaysComb "fork-join is not allowed in {} procedure" error BlockingInAlwaysFF "always_ff procedures cannot have blocking timing controls" error AlwaysFFEventControl "always_ff procedure must have one and only one event control" error SeqEmptyMatch "sequence must not admit an empty match" -error SeqNoMatch "sequence can never be matched" error SeqOnlyEmpty "sequence admits only empty matches" +warning seq-no-match SeqNoMatch "sequence can never be matched" warning event-const EventExpressionConstant "edge expression is constant" warning empty-stmt EmptyStatement "extra ';' has no effect" warning pointless-void-cast PointlessVoidCast "cast to void for void-returning function '{}' has no effect" @@ -880,6 +880,7 @@ warning bad-procedural-force BadProceduralForce "lvalue of force/release must be warning multibit-edge MultiBitEdge "edge of expression of type {} will only trigger on changes to the first bit" note NoteWhileExpanding "while expanding {} '{}'" note NoteExpandedHere "expanded here" +note NoteAlwaysFalse "expression is always false" subsystem Types error InvalidEnumBase "invalid enum base type {} (must be a single dimensional integer type)" @@ -1160,7 +1161,8 @@ group default = { real-underflow real-overflow vector-overflow int-overflow unco raw-protect-eof protected-envelope specify-param dup-timing-path invalid-pulsestyle negative-timing-limit bad-procedural-force duplicate-defparam implicit-port-type-mismatch split-distweight-op dpi-pure-task multibit-edge unknown-sys-name unknown-library - dup-config-rule unused-config-cell unused-config-instance specify-condition-expr } + dup-config-rule unused-config-cell unused-config-instance specify-condition-expr + seq-no-match } group extra = { empty-member empty-stmt dup-import pointless-void-cast case-gen-none case-gen-dup unused-result format-real ignored-slice task-ignored width-trunc dup-attr event-const diff --git a/scripts/warning_docs.txt b/scripts/warning_docs.txt index 2cd76c5d9..a87b2e4fc 100644 --- a/scripts/warning_docs.txt +++ b/scripts/warning_docs.txt @@ -1478,3 +1478,11 @@ module m; end endmodule ``` + +-Wseq-no-match +Warns about a sequence expression that can never be matched. +``` +module top; + assert property ((1'b1 ##1 1'b0) intersect (1'b1[*0] ##2 1'b1)); +endmodule +``` diff --git a/source/ast/expressions/AssertionExpr.cpp b/source/ast/expressions/AssertionExpr.cpp index 09343cba5..857d238d9 100644 --- a/source/ast/expressions/AssertionExpr.cpp +++ b/source/ast/expressions/AssertionExpr.cpp @@ -29,9 +29,9 @@ using namespace slang::ast; struct NondegeneracyCheckVisitor { template - slang::bitmask visit(const T& expr) { + AssertionExpr::NondegeneracyCheckResult visit(const T& expr) { if (expr.bad()) - return NondegeneracyStatus::None; + return {}; return expr.checkNondegeneracyImpl(); } @@ -124,9 +124,20 @@ static void enforceNondegeneracy(const AssertionExpr& expr, const ASTContext& co AssertionExpr::NondegeneracyRequirement nondegRequirement, const SyntaxNode& syntax) { using NR = AssertionExpr::NondegeneracyRequirement; - const auto seqNondegenSt = expr.checkNondegeneracy(); + const auto seqNondegen = expr.checkNondegeneracy(); + const auto seqNondegenSt = seqNondegen.status; if (seqNondegenSt.has(NondegeneracyStatus::AdmitsNoMatch)) { - context.addDiag(diag::SeqNoMatch, syntax.sourceRange()); + auto range = seqNondegen.noMatchRange; + if (!range.start()) + range = syntax.sourceRange(); + + if (seqNondegen.isAlwaysFalse) { + auto& diag = context.addDiag(diag::SeqNoMatch, syntax.sourceRange()); + diag.addNote(diag::NoteAlwaysFalse, range); + } + else { + context.addDiag(diag::SeqNoMatch, range); + } } else if (nondegRequirement == NR::OverlapOp) { if (seqNondegenSt.has(NondegeneracyStatus::AcceptsOnlyEmpty)) @@ -268,7 +279,7 @@ void AssertionExpr::requireSequence(const ASTContext& context, DiagCode code) co SLANG_UNREACHABLE; } -bitmask AssertionExpr::checkNondegeneracy() const { +AssertionExpr::NondegeneracyCheckResult AssertionExpr::checkNondegeneracy() const { NondegeneracyCheckVisitor visitor; return visit(visitor); } @@ -485,14 +496,14 @@ SequenceRepetition::SequenceRepetition(const SequenceRepetitionSyntax& syntax, range = SequenceRange::fromSyntax(*syntax.selector, context, /* allowUnbounded */ true); } -bitmask SequenceRepetition::checkNondegeneracy() const { +AssertionExpr::NondegeneracyCheckResult SequenceRepetition::checkNondegeneracy() const { bitmask res; if (range.min == 0u) { res = NondegeneracyStatus::AdmitsEmpty; if (range.max == 0u) res |= NondegeneracyStatus::AcceptsOnlyEmpty; } - return res; + return {res, {}, false}; } SequenceRange SequenceRepetition::applyTo(SequenceRange res) const { @@ -574,22 +585,27 @@ void SimpleAssertionExpr::requireSequence(const ASTContext& context, DiagCode co } } -bitmask SimpleAssertionExpr::checkNondegeneracyImpl() const { +AssertionExpr::NondegeneracyCheckResult SimpleAssertionExpr::checkNondegeneracyImpl() const { // If simple sequence expression can be evaluated to false // then it admits no possible match. - bitmask res = NondegeneracyStatus::None; - if (isNullExpr) - res |= NondegeneracyStatus::AdmitsNoMatch; + NondegeneracyCheckResult result; + if (isNullExpr) { + result.status |= NondegeneracyStatus::AdmitsNoMatch; + if (syntax) { + result.noMatchRange = syntax->sourceRange(); + result.isAlwaysFalse = true; + } + } if (repetition) - res |= repetition->checkNondegeneracy(); + result |= repetition->checkNondegeneracy(); if (expr.kind == ExpressionKind::AssertionInstance) { auto& aie = expr.as(); if (aie.type->isSequenceType()) - res |= aie.body.checkNondegeneracy(); + result |= aie.body.checkNondegeneracy(); } - return res; + return result; } std::optional SimpleAssertionExpr::computeSequenceLengthImpl() const { @@ -663,18 +679,23 @@ AssertionExpr& SequenceConcatExpr::fromSyntax(const DelayedSequenceExprSyntax& s return *result; } -bitmask SequenceConcatExpr::checkNondegeneracyImpl() const { +AssertionExpr::NondegeneracyCheckResult SequenceConcatExpr::checkNondegeneracyImpl() const { + NondegeneracyCheckResult result; bool admitsEmpty = true; bool admitsNoMatch = false; auto left = elements.begin(); SLANG_ASSERT(left != elements.end()); - auto leftNondegenSt = left->sequence->checkNondegeneracy(); + auto leftNondegen = left->sequence->checkNondegeneracy(); + auto leftNondegenSt = leftNondegen.status; if (left->delay.min != 0 || !leftNondegenSt.has(NondegeneracyStatus::AdmitsEmpty)) admitsEmpty = false; - if (leftNondegenSt.has(NondegeneracyStatus::AdmitsNoMatch)) + if (leftNondegenSt.has(NondegeneracyStatus::AdmitsNoMatch)) { admitsNoMatch = true; + result.noMatchRange = leftNondegen.noMatchRange; + result.isAlwaysFalse = leftNondegen.isAlwaysFalse; + } // See SystemVerilog LRM sections 16.9.2.1 and F.3.4.2.2 for details while (admitsEmpty || !admitsNoMatch) { @@ -683,9 +704,13 @@ bitmask SequenceConcatExpr::checkNondegeneracyImpl() const break; // If any element of concat sequence is no match then all concat sequence is no match. - const auto rightNondegenSt = right->sequence->checkNondegeneracy(); - if (rightNondegenSt.has(NondegeneracyStatus::AdmitsNoMatch)) + const auto rightNondegen = right->sequence->checkNondegeneracy(); + const auto rightNondegenSt = rightNondegen.status; + if (rightNondegenSt.has(NondegeneracyStatus::AdmitsNoMatch)) { admitsNoMatch = true; + result.noMatchRange = rightNondegen.noMatchRange; + result.isAlwaysFalse = rightNondegen.isAlwaysFalse; + } if (!rightNondegenSt.has(NondegeneracyStatus::AdmitsEmpty)) admitsEmpty = false; @@ -693,17 +718,29 @@ bitmask SequenceConcatExpr::checkNondegeneracyImpl() const if (right->delay.min == 0u && right->delay.max == 0u) { admitsEmpty = false; + auto setNoMatchRange = [&] { + auto ls = left->sequence->syntax; + auto rs = right->sequence->syntax; + if (ls && rs) { + result.noMatchRange = {ls->getFirstToken().location(), rs->sourceRange().end()}; + result.isAlwaysFalse = false; + } + }; + // (empty ##0 seq) does not result in a match // We need to check that case if and only if we are at the start of concat sequence // because empty parts in the middle of concat sequence are processed by the next `if` - if (left == elements.begin() && + if (left == elements.begin() && !admitsNoMatch && leftNondegenSt.has(NondegeneracyStatus::AcceptsOnlyEmpty)) { admitsNoMatch = true; + setNoMatchRange(); } // (seq ##0 empty) does not result in a match. - if (rightNondegenSt.has(NondegeneracyStatus::AcceptsOnlyEmpty)) + if (rightNondegenSt.has(NondegeneracyStatus::AcceptsOnlyEmpty) && !admitsNoMatch) { admitsNoMatch = true; + setNoMatchRange(); + } } if (right->delay.min > 1) @@ -713,19 +750,18 @@ bitmask SequenceConcatExpr::checkNondegeneracyImpl() const leftNondegenSt = rightNondegenSt; } - bitmask res; if (admitsEmpty) - res |= NondegeneracyStatus::AdmitsEmpty; + result.status |= NondegeneracyStatus::AdmitsEmpty; if (admitsNoMatch) - res |= NondegeneracyStatus::AdmitsNoMatch; - return res; + result.status |= NondegeneracyStatus::AdmitsNoMatch; + return result; } std::optional SequenceConcatExpr::computeSequenceLengthImpl() const { uint32_t delayMin = 0; uint32_t delayMax = 0; for (auto it = elements.begin(); it != elements.end(); it++) { - const bool acceptsOnlyEmpty = it->sequence->checkNondegeneracy().has( + const bool acceptsOnlyEmpty = it->sequence->checkNondegeneracy().status.has( NondegeneracyStatus::AcceptsOnlyEmpty); // Default delay length for concat sequence is 1 if it admits a non-empty match. @@ -828,7 +864,7 @@ static std::span bindMatchItems(const SequenceMatchList } } - if (sequence.checkNondegeneracy().has(NondegeneracyStatus::AdmitsEmpty)) { + if (sequence.checkNondegeneracy().status.has(NondegeneracyStatus::AdmitsEmpty)) { auto& diag = context.addDiag(diag::MatchItemsAdmitEmpty, syntax.items.sourceRange()); if (sequence.syntax) diag << sequence.syntax->sourceRange(); @@ -856,12 +892,12 @@ AssertionExpr& SequenceWithMatchExpr::fromSyntax(const ParenthesizedSequenceExpr return *context.getCompilation().emplace(expr, repetition, matchItems); } -bitmask SequenceWithMatchExpr::checkNondegeneracyImpl() const { - bitmask res; +AssertionExpr::NondegeneracyCheckResult SequenceWithMatchExpr::checkNondegeneracyImpl() const { + NondegeneracyCheckResult result; if (repetition) - res = repetition->checkNondegeneracy(); + result = repetition->checkNondegeneracy(); - return res | expr.checkNondegeneracy(); + return result | expr.checkNondegeneracy(); } std::optional SequenceWithMatchExpr::computeSequenceLengthImpl() const { @@ -1089,37 +1125,59 @@ void BinaryAssertionExpr::requireSequence(const ASTContext& context, DiagCode co SLANG_UNREACHABLE; } -bitmask BinaryAssertionExpr::checkNondegeneracyImpl() const { - const auto leftNondegenSt = left.checkNondegeneracy(); - const auto rightNondegenSt = right.checkNondegeneracy(); +AssertionExpr::NondegeneracyCheckResult BinaryAssertionExpr::checkNondegeneracyImpl() const { + const auto leftNondegen = left.checkNondegeneracy(); + const auto rightNondegen = right.checkNondegeneracy(); + const auto leftNondegenSt = leftNondegen.status; + const auto rightNondegenSt = rightNondegen.status; const bool leftAdmitsEmpty = leftNondegenSt.has(NondegeneracyStatus::AdmitsEmpty); const bool rightAdmitsEmpty = rightNondegenSt.has(NondegeneracyStatus::AdmitsEmpty); const bool leftAdmitsNoMatch = leftNondegenSt.has(NondegeneracyStatus::AdmitsNoMatch); const bool rightAdmitsNoMatch = rightNondegenSt.has(NondegeneracyStatus::AdmitsNoMatch); - bitmask res; + NondegeneracyCheckResult res; + auto joinNoMatchReasons = [&] { + if (leftAdmitsNoMatch) { + res.noMatchRange = leftNondegen.noMatchRange; + res.isAlwaysFalse = leftNondegen.isAlwaysFalse; + } + else if (rightAdmitsNoMatch) { + res.noMatchRange = rightNondegen.noMatchRange; + res.isAlwaysFalse = rightNondegen.isAlwaysFalse; + } + else if (left.syntax && right.syntax) { + res.noMatchRange = {left.syntax->getFirstToken().location(), + right.syntax->sourceRange().end()}; + res.isAlwaysFalse = false; + } + }; + switch (op) { case BinaryAssertionOperator::Or: { if (leftAdmitsEmpty || rightAdmitsEmpty) - res |= NondegeneracyStatus::AdmitsEmpty; + res.status |= NondegeneracyStatus::AdmitsEmpty; // In case of `or` full sequence is no match if and only if both parts are no match. - if (leftAdmitsNoMatch && rightAdmitsNoMatch) - res |= NondegeneracyStatus::AdmitsNoMatch; + if (leftAdmitsNoMatch && rightAdmitsNoMatch) { + res.status |= NondegeneracyStatus::AdmitsNoMatch; + joinNoMatchReasons(); + } break; } case BinaryAssertionOperator::And: { if (leftAdmitsEmpty && rightAdmitsEmpty) - res |= NondegeneracyStatus::AdmitsEmpty; + res.status |= NondegeneracyStatus::AdmitsEmpty; // In case of `and` full sequence is no match if and only if any part is no match. - if (leftAdmitsNoMatch || rightAdmitsNoMatch) - res |= NondegeneracyStatus::AdmitsNoMatch; + if (leftAdmitsNoMatch || rightAdmitsNoMatch) { + res.status |= NondegeneracyStatus::AdmitsNoMatch; + joinNoMatchReasons(); + } break; } case BinaryAssertionOperator::Intersect: { if (leftAdmitsEmpty && rightAdmitsEmpty) - res |= NondegeneracyStatus::AdmitsEmpty; + res.status |= NondegeneracyStatus::AdmitsEmpty; // In case of `intersect` full sequence is no match if both parts don't have any // possible overlap in their length ranges. @@ -1127,13 +1185,14 @@ bitmask BinaryAssertionExpr::checkNondegeneracyImpl() const const auto rightLen = right.computeSequenceLength(); if (leftAdmitsNoMatch || rightAdmitsNoMatch || (leftLen && rightLen && !leftLen->canIntersect(*rightLen))) { - res |= NondegeneracyStatus::AdmitsNoMatch; + res.status |= NondegeneracyStatus::AdmitsNoMatch; + joinNoMatchReasons(); } break; } case BinaryAssertionOperator::Within: { if (leftAdmitsEmpty && rightAdmitsEmpty) - res |= NondegeneracyStatus::AdmitsEmpty; + res.status |= NondegeneracyStatus::AdmitsEmpty; // In case of `within` full sequence is no match if left side delay range is within // right side delay range. @@ -1141,17 +1200,21 @@ bitmask BinaryAssertionExpr::checkNondegeneracyImpl() const const auto rightLen = right.computeSequenceLength(); if (leftAdmitsNoMatch || rightAdmitsNoMatch || (leftLen && rightLen && !leftLen->canBeWithin(*rightLen))) { - res |= NondegeneracyStatus::AdmitsNoMatch; + res.status |= NondegeneracyStatus::AdmitsNoMatch; + joinNoMatchReasons(); } break; } case BinaryAssertionOperator::Throughout: { if (rightAdmitsEmpty) - res |= NondegeneracyStatus::AdmitsEmpty; + res.status |= NondegeneracyStatus::AdmitsEmpty; // In case of `throughout` full sequence is no match if right part is no match - if (rightAdmitsNoMatch) - res |= NondegeneracyStatus::AdmitsNoMatch; + if (rightAdmitsNoMatch) { + res.status |= NondegeneracyStatus::AdmitsNoMatch; + res.noMatchRange = rightNondegen.noMatchRange; + res.isAlwaysFalse = rightNondegen.isAlwaysFalse; + } break; } default: @@ -1225,10 +1288,10 @@ AssertionExpr& FirstMatchAssertionExpr::fromSyntax(const FirstMatchSequenceExprS return *comp.emplace(seq, matchItems); } -bitmask FirstMatchAssertionExpr::checkNondegeneracyImpl() const { +AssertionExpr::NondegeneracyCheckResult FirstMatchAssertionExpr::checkNondegeneracyImpl() const { auto res = seq.checkNondegeneracy(); if (!matchItems.empty()) - res &= ~(NondegeneracyStatus::AdmitsEmpty | NondegeneracyStatus::AcceptsOnlyEmpty); + res.status &= ~(NondegeneracyStatus::AdmitsEmpty | NondegeneracyStatus::AcceptsOnlyEmpty); return res; } diff --git a/source/ast/expressions/MiscExpressions.cpp b/source/ast/expressions/MiscExpressions.cpp index 2dfbced06..fe5831282 100644 --- a/source/ast/expressions/MiscExpressions.cpp +++ b/source/ast/expressions/MiscExpressions.cpp @@ -772,7 +772,7 @@ static const AssertionExpr& bindAssertionBody(const Symbol& symbol, const Syntax result.requireSequence(context); if (outputLocalVarArgLoc && - result.checkNondegeneracy().has(NondegeneracyStatus::AdmitsEmpty)) { + result.checkNondegeneracy().status.has(NondegeneracyStatus::AdmitsEmpty)) { auto& diag = context.addDiag(diag::LocalVarOutputEmptyMatch, sds.seqExpr->sourceRange()); diag << symbol.name;