diff --git a/Sources/SwiftOperators/OperatorTable+Semantics.swift b/Sources/SwiftOperators/OperatorTable+Semantics.swift index 6bf67ca9e61..07c095a0699 100644 --- a/Sources/SwiftOperators/OperatorTable+Semantics.swift +++ b/Sources/SwiftOperators/OperatorTable+Semantics.swift @@ -155,4 +155,33 @@ extension OperatorTable { try visitor.errors.forEach(errorHandler) self = visitor.opPrecedence } + + /// Determine the precedence of one precedence group relative to another. + /// + /// - Parameters: + /// - firstGroupName: The precedence group whose position is being queried. + /// - secondGroupName: The precedence group being compared against. + /// - syntax: A syntax node used as the reference location when reporting + /// errors encountered while walking the precedence-group relationships. + /// - errorHandler: A handler invoked when an inconsistency is found in + /// the precedence-group relationships. By default the underlying + /// `OperatorError` is re-thrown. + /// - Returns: ``Precedence/higherThan`` if `firstGroupName` has higher + /// precedence than `secondGroupName`, ``Precedence/lowerThan`` if it has + /// lower precedence, and ``Precedence/unrelated`` otherwise (including + /// when both names refer to the same precedence group). + public func precedence( + of firstGroupName: PrecedenceGroupName, + relativeTo secondGroupName: PrecedenceGroupName, + referencedFrom syntax: Syntax, + errorHandler: OperatorErrorHandler = { throw $0 } + ) rethrows -> Precedence { + return try precedenceGraph.precedence( + relating: firstGroupName, + to: secondGroupName, + startSyntax: syntax, + endSyntax: syntax, + errorHandler: errorHandler + ) + } } diff --git a/Sources/SwiftOperators/PrecedenceGraph.swift b/Sources/SwiftOperators/PrecedenceGraph.swift index 4e8ab7daf8c..dcfae1ce91e 100644 --- a/Sources/SwiftOperators/PrecedenceGraph.swift +++ b/Sources/SwiftOperators/PrecedenceGraph.swift @@ -17,13 +17,13 @@ import SwiftSyntax #endif /// Describes the relative precedence of two groups. -enum Precedence: Sendable { +public enum Precedence: Sendable { case unrelated case higherThan case lowerThan /// Flip the precedence order around. - var flipped: Precedence { + public var flipped: Precedence { switch self { case .unrelated: return .unrelated diff --git a/Tests/SwiftOperatorsTest/OperatorTableTests.swift b/Tests/SwiftOperatorsTest/OperatorTableTests.swift index 47e5bd2ad2f..ac5b293ada8 100644 --- a/Tests/SwiftOperatorsTest/OperatorTableTests.swift +++ b/Tests/SwiftOperatorsTest/OperatorTableTests.swift @@ -441,4 +441,59 @@ class OperatorPrecedenceTests: XCTestCase { let folded = try OperatorTable.standardOperators.foldAll(original) XCTAssertEqual(original.description, folded.description) } + + func testPrecedenceGroupComparison() throws { + let opPrecedence = OperatorTable.standardOperators + let syntax = Syntax(ExprSyntax("x")) + + // MultiplicationPrecedence has "higherThan: AdditionPrecedence" in the + // standard operators, so comparing the two groups directly should return + // the corresponding relationship in both directions. + XCTAssertEqual( + try opPrecedence.precedence( + of: "MultiplicationPrecedence", + relativeTo: "AdditionPrecedence", + referencedFrom: syntax + ), + .higherThan + ) + XCTAssertEqual( + try opPrecedence.precedence( + of: "AdditionPrecedence", + relativeTo: "MultiplicationPrecedence", + referencedFrom: syntax + ), + .lowerThan + ) + + // Comparing a group to itself should always be reported as unrelated. + XCTAssertEqual( + try opPrecedence.precedence( + of: "AdditionPrecedence", + relativeTo: "AdditionPrecedence", + referencedFrom: syntax + ), + .unrelated + ) + + // Two groups that both sit above `TernaryPrecedence` but have no direct + // or transitive relationship to each other should be reported as unrelated. + // In the standard operator table, `DefaultPrecedence` and + // `LogicalDisjunctionPrecedence` are both declared `higherThan: + // TernaryPrecedence` with no other links between them. + XCTAssertEqual( + try opPrecedence.precedence( + of: "DefaultPrecedence", + relativeTo: "LogicalDisjunctionPrecedence", + referencedFrom: syntax + ), + .unrelated + ) + } + + func testPrecedenceGroupFlipped() { + XCTAssertEqual(Precedence.higherThan.flipped, .lowerThan) + XCTAssertEqual(Precedence.lowerThan.flipped, .higherThan) + XCTAssertEqual(Precedence.unrelated.flipped, .unrelated) + } }