diff --git a/src/QsCompiler/Tests.Compiler/ClassicalControlTests.fs b/src/QsCompiler/Tests.Compiler/ClassicalControlTests.fs index f4a82d0eef..d5f6ec5a25 100644 --- a/src/QsCompiler/Tests.Compiler/ClassicalControlTests.fs +++ b/src/QsCompiler/Tests.Compiler/ClassicalControlTests.fs @@ -1265,4 +1265,84 @@ type ClassicalControlTests () = IsApplyIfElseArgsMatch args "[r1], [r2]" Bar NoOp |> (fun (x, _, _, _, _) -> Assert.True(x, "ApplyConditionally did not have the correct arguments")) - Assert.True(IsTypeArgsMatch targs "Result, Unit", "ApplyConditionally did not have the correct type arguments") \ No newline at end of file + Assert.True(IsTypeArgsMatch targs "Result, Unit", "ApplyConditionally did not have the correct type arguments") + + [] + [] + member this.``Inequality with ApplyConditionally`` () = + let result = CompileClassicalControlTest 31 + + let original = GetCallableWithName result Signatures.ClassicalControlNs "Foo" |> GetBodyFromCallable + let lines = original |> GetLinesFromSpecialization + + Assert.True(3 = Seq.length lines, sprintf "Callable %O(%A) did not have the expected number of statements" original.Parent original.Kind) + + let (success, targs, args) = CheckIfLineIsCall BuiltIn.ApplyConditionally.Namespace.Value BuiltIn.ApplyConditionally.Name.Value lines.[2] + Assert.True(success, sprintf "Callable %O(%A) did not have expected content" original.Parent original.Kind) + + let Bar = {Namespace = NonNullable<_>.New Signatures.ClassicalControlNs; Name = NonNullable<_>.New "Bar"} + let SubOp1 = {Namespace = NonNullable<_>.New "SubOps"; Name = NonNullable<_>.New "SubOp1"} + + IsApplyIfElseArgsMatch args "[r1], [r2]" SubOp1 Bar + |> (fun (x, _, _, _, _) -> Assert.True(x, "ApplyConditionally did not have the correct arguments")) + + Assert.True(IsTypeArgsMatch targs "Unit, Result", "ApplyConditionally did not have the correct type arguments") + + [] + [] + member this.``Inequality with Apply If One Else Zero`` () = + let (targs, args) = CompileClassicalControlTest 32 |> ApplyIfElseTest + + let Bar = {Namespace = NonNullable<_>.New Signatures.ClassicalControlNs; Name = NonNullable<_>.New "Bar"} + let SubOp1 = {Namespace = NonNullable<_>.New "SubOps"; Name = NonNullable<_>.New "SubOp1"} + + IsApplyIfElseArgsMatch args "r" SubOp1 Bar + |> (fun (x, _, _, _, _) -> Assert.True(x, "ApplyIfElse did not have the correct arguments")) + + Assert.True(IsTypeArgsMatch targs "Unit, Result", "ApplyIfElse did not have the correct type arguments") + + [] + [] + member this.``Inequality with Apply If Zero Else One`` () = + let (targs, args) = CompileClassicalControlTest 33 |> ApplyIfElseTest + + let Bar = {Namespace = NonNullable<_>.New Signatures.ClassicalControlNs; Name = NonNullable<_>.New "Bar"} + let SubOp1 = {Namespace = NonNullable<_>.New "SubOps"; Name = NonNullable<_>.New "SubOp1"} + + IsApplyIfElseArgsMatch args "r" Bar SubOp1 + |> (fun (x, _, _, _, _) -> Assert.True(x, "ApplyIfElse did not have the correct arguments")) + + Assert.True(IsTypeArgsMatch targs "Result, Unit", "ApplyIfElse did not have the correct type arguments") + + [] + [] + member this.``Inequality with ApplyIfOne`` () = + let result = CompileClassicalControlTest 34 + + let originalOp = GetCallableWithName result Signatures.ClassicalControlNs "Foo" |> GetBodyFromCallable + + [ (1, BuiltIn.ApplyIfOne) ] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls originalOp + + [] + [] + member this.``Inequality with ApplyIfZero`` () = + let result = CompileClassicalControlTest 35 + + let originalOp = GetCallableWithName result Signatures.ClassicalControlNs "Foo" |> GetBodyFromCallable + + [ (1, BuiltIn.ApplyIfZero) ] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls originalOp + + [] + [] + member this.``Literal on the Left`` () = + let result = CompileClassicalControlTest 36 + + let originalOp = GetCallableWithName result Signatures.ClassicalControlNs "Foo" |> GetBodyFromCallable + + [(1, BuiltIn.ApplyIfZero)] + |> Seq.map ExpandBuiltInQualifiedSymbol + |> AssertSpecializationHasCalls originalOp \ No newline at end of file diff --git a/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/ClassicalControl.qs b/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/ClassicalControl.qs index 09086d6b29..fcfcfd5db8 100644 --- a/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/ClassicalControl.qs +++ b/src/QsCompiler/Tests.Compiler/TestCases/LinkingTests/ClassicalControl.qs @@ -909,4 +909,110 @@ namespace Microsoft.Quantum.Testing.ClassicalControl { Bar(r1); } } +} + +// ================================= + +// Inequality with ApplyConditionally +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Bar(r : Result) : Unit { } + + operation Foo() : Unit { + let r1 = Zero; + let r2 = Zero; + + if (r1 != r2) { + Bar(r1); + } + else { + SubOp1(); + } + } +} + +// ================================= + +// Inequality with Apply If One Else Zero +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Bar(r : Result) : Unit { } + + operation Foo() : Unit { + let r = Zero; + + if (r != Zero) { + Bar(r); + } + else { + SubOp1(); + } + } +} + +// ================================= + +// Inequality with Apply If Zero Else One +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Bar(r : Result) : Unit { } + + operation Foo() : Unit { + let r = Zero; + + if (r != One) { + Bar(r); + } + else { + SubOp1(); + } + } +} + +// ================================= + +// Inequality with ApplyIfOne +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo() : Unit { + let r = Zero; + + if (r != Zero) { + SubOp1(); + } + } +} + +// ================================= + +// Inequality with ApplyIfZero +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo() : Unit { + let r = Zero; + + if (r != One) { + SubOp1(); + } + } +} + +// ================================= + +// Literal on the Left +namespace Microsoft.Quantum.Testing.ClassicalControl { + open SubOps; + + operation Foo() : Unit { + let r = Zero; + + if (Zero == r) { + SubOp1(); + } + } } \ No newline at end of file diff --git a/src/QsCompiler/Tests.Compiler/TestUtils/Signatures.fs b/src/QsCompiler/Tests.Compiler/TestUtils/Signatures.fs index 6931520202..c4d7f6efed 100644 --- a/src/QsCompiler/Tests.Compiler/TestUtils/Signatures.fs +++ b/src/QsCompiler/Tests.Compiler/TestUtils/Signatures.fs @@ -393,5 +393,26 @@ let public ClassicalControlSignatures = ClassicalControlNs, "Bar", [|"Result"|], "Unit" ClassicalControlNs, "Foo", [||], "Unit" |]) + (_DefaultTypes, [| // Inequality with ApplyConditionally + ClassicalControlNs, "Bar", [|"Result"|], "Unit" + ClassicalControlNs, "Foo", [||], "Unit" + |]) + (_DefaultTypes, [| // Inequality with Apply If One Else Zero + ClassicalControlNs, "Bar", [|"Result"|], "Unit" + ClassicalControlNs, "Foo", [||], "Unit" + |]) + (_DefaultTypes, [| // Inequality with Apply If Zero Else One + ClassicalControlNs, "Bar", [|"Result"|], "Unit" + ClassicalControlNs, "Foo", [||], "Unit" + |]) + (_DefaultTypes, [| // Inequality with ApplyIfOne + ClassicalControlNs, "Foo", [||], "Unit" + |]) + (_DefaultTypes, [| // Inequality with ApplyIfZero + ClassicalControlNs, "Foo", [||], "Unit" + |]) + (_DefaultTypes, [| // Literal on the Left + ClassicalControlNs, "Foo", [||], "Unit" + |]) |] |> _MakeSignatures \ No newline at end of file diff --git a/src/QsCompiler/Transformations/ClassicallyControlled.cs b/src/QsCompiler/Transformations/ClassicallyControlled.cs index 850b2f12db..b8d47bc3d1 100644 --- a/src/QsCompiler/Transformations/ClassicallyControlled.cs +++ b/src/QsCompiler/Transformations/ClassicallyControlled.cs @@ -170,33 +170,40 @@ x.Resolution is ResolvedTypeKind.TupleType tup /// /// Creates an operation call from the conditional control API for non-literal Result comparisons. + /// The equalityScope and inequalityScope cannot both be null. /// - private TypedExpression CreateApplyConditionallyExpression(TypedExpression conditionExpr1, TypedExpression conditionExpr2, QsScope conditionScope, QsScope defaultScope) + private TypedExpression CreateApplyConditionallyExpression(TypedExpression conditionExpr1, TypedExpression conditionExpr2, QsScope equalityScope, QsScope inequalityScope) { - var (isConditionValid, conditionId, conditionArgs) = IsValidScope(conditionScope); - var (isDefaultValid, defaultId, defaultArgs) = IsValidScope(defaultScope); + QsCompilerError.Verify(equalityScope != null || inequalityScope != null, $"Cannot have null for both equality and inequality scopes when creating ApplyConditionally expressions."); + + var (isEqualityValid, equalityId, equalityArgs) = IsValidScope(equalityScope); + var (isInequaltiyValid, inequalityId, inequalityArgs) = IsValidScope(inequalityScope); - if (!isConditionValid) + if (!isEqualityValid && equalityScope != null) { - return null; // ToDo: Diagnostic message - condition block not valid + return null; // ToDo: Diagnostic message - equality block exists, but is not valid } - if (!isDefaultValid && defaultScope != null) + if (!isInequaltiyValid && inequalityScope != null) { - return null; // ToDo: Diagnostic message - default block exists, but is not valid + return null; // ToDo: Diagnostic message - inequality block exists, but is not valid } - if (defaultScope == null) + if (equalityScope == null) { - (defaultId, defaultArgs) = Utils.GetNoOp(); + (equalityId, equalityArgs) = Utils.GetNoOp(); + } + else if (inequalityScope == null) + { + (inequalityId, inequalityArgs) = Utils.GetNoOp(); } // Get characteristic properties from global id var props = ImmutableHashSet.Empty; - if (conditionId.ResolvedType.Resolution is ResolvedTypeKind.Operation op) + if (equalityId.ResolvedType.Resolution is ResolvedTypeKind.Operation op) { props = op.Item2.Characteristics.GetProperties(); - if (defaultId != null && defaultId.ResolvedType.Resolution is ResolvedTypeKind.Operation defaultOp) + if (inequalityId != null && inequalityId.ResolvedType.Resolution is ResolvedTypeKind.Operation defaultOp) { props = props.Intersect(defaultOp.Item2.Characteristics.GetProperties()); } @@ -221,10 +228,10 @@ private TypedExpression CreateApplyConditionallyExpression(TypedExpression condi controlOpInfo = BuiltIn.ApplyConditionally; } - var equality = Utils.CreateValueTupleExpression(conditionId, conditionArgs); - var inequality = Utils.CreateValueTupleExpression(defaultId, defaultArgs); + var equality = Utils.CreateValueTupleExpression(equalityId, equalityArgs); + var inequality = Utils.CreateValueTupleExpression(inequalityId, inequalityArgs); var controlArgs = Utils.CreateValueTupleExpression(Utils.CreateValueArray(conditionExpr1), Utils.CreateValueArray(conditionExpr2), equality, inequality); - var targetArgsTypes = ImmutableArray.Create(conditionArgs.ResolvedType, defaultArgs.ResolvedType); + var targetArgsTypes = ImmutableArray.Create(equalityArgs.ResolvedType, inequalityArgs.ResolvedType); return CreateControlCall(controlOpInfo, props, controlArgs, targetArgsTypes); } @@ -367,24 +374,22 @@ private QsStatement ConvertConditionalToControlCall(QsStatement statement) if (isCondition) { - var (isCompareLiteral, literal, nonLiteral) = IsConditionedOnResultLiteralExpression(condition); - if (isCompareLiteral) + if (IsConditionedOnResultLiteralExpression(condition, out var literal, out var conditionExpression)) { - return CreateControlStatement(statement, CreateApplyIfExpression(literal, nonLiteral, conditionScope, defaultScope)); + return CreateControlStatement(statement, CreateApplyIfExpression(literal, conditionExpression, conditionScope, defaultScope)); } - else + else if (IsConditionedOnResultEqualityExpression(condition, out var lhsConditionExpression, out var rhsConditionExpression)) { - var (isCompareNonLiteral, conditionExpr1, conditionExpr2) = IsConditionedOnResultEqualityExpression(condition); - if (isCompareNonLiteral) - { - return CreateControlStatement(statement, CreateApplyConditionallyExpression(conditionExpr1, conditionExpr2, conditionScope, defaultScope)); - } - else - { - // ToDo: Diagnostic message - return statement; // The condition does not fit a supported format. - } + return CreateControlStatement(statement, CreateApplyConditionallyExpression(lhsConditionExpression, rhsConditionExpression, conditionScope, defaultScope)); + } + else if (IsConditionedOnResultInequalityExpression(condition, out lhsConditionExpression, out rhsConditionExpression)) + { + // The scope arguments are reversed to account for the negation of the NEQ + return CreateControlStatement(statement, CreateApplyConditionallyExpression(lhsConditionExpression, rhsConditionExpression, defaultScope, conditionScope)); } + + // ToDo: Diagnostic message + return statement; // The condition does not fit a supported format. } else { @@ -413,41 +418,92 @@ private QsStatement ConvertConditionalToControlCall(QsStatement statement) } /// - /// Checks if the expression is an equality comparison where one side is a Result literal. - /// If it is, returns true along with the Result literal and the other expression in the - /// equality, otherwise returns false with nulls. + /// Checks if the expression is an equality or inequality comparison where one side is a + /// Result literal. If it is, returns true along with the Result literal and the other + /// expression in the (in)equality, otherwise returns false with nulls. If it is an + /// inequality, the returned result value will be the opposite of the result literal found. /// - private (bool, QsResult, TypedExpression) IsConditionedOnResultLiteralExpression(TypedExpression expression) + private bool IsConditionedOnResultLiteralExpression(TypedExpression expression, out QsResult literal, out TypedExpression conditionExpression) { + literal = null; + conditionExpression = null; + if (expression.Expression is ExpressionKind.EQ eq) { - if (eq.Item1.Expression is ExpressionKind.ResultLiteral exp1) + if (eq.Item1.Expression is ExpressionKind.ResultLiteral literal1) { - return (true, exp1.Item, eq.Item2); + literal = literal1.Item; + conditionExpression = eq.Item2; + return true; } - else if (eq.Item2.Expression is ExpressionKind.ResultLiteral exp2) + else if (eq.Item2.Expression is ExpressionKind.ResultLiteral literal2) { - return (true, exp2.Item, eq.Item1); + literal = literal2.Item; + conditionExpression = eq.Item1; + return true; } } + else if (expression.Expression is ExpressionKind.NEQ neq) + { + QsResult FlipResult(QsResult result) => result.IsZero ? QsResult.One : QsResult.Zero; - return (false, null, null); + if (neq.Item1.Expression is ExpressionKind.ResultLiteral literal1) + { + literal = FlipResult(literal1.Item); + conditionExpression = neq.Item2; + return true; + } + else if (neq.Item2.Expression is ExpressionKind.ResultLiteral literal2) + { + literal = FlipResult(literal2.Item); + conditionExpression = neq.Item1; + return true; + } + } + + return false; } /// /// Checks if the expression is an equality comparison between two Result-typed expressions. /// If it is, returns true along with the two expressions, otherwise returns false with nulls. /// - private (bool, TypedExpression, TypedExpression) IsConditionedOnResultEqualityExpression(TypedExpression expression) + private bool IsConditionedOnResultEqualityExpression(TypedExpression expression, out TypedExpression lhs, out TypedExpression rhs) { + lhs = null; + rhs = null; + if (expression.Expression is ExpressionKind.EQ eq && eq.Item1.ResolvedType.Resolution == ResolvedTypeKind.Result && eq.Item2.ResolvedType.Resolution == ResolvedTypeKind.Result) { - return (true, eq.Item1, eq.Item2); + lhs = eq.Item1; + rhs = eq.Item2; + return true; } - return (false, null, null); + return false; + } + + /// + /// Checks if the expression is an inequality comparison between two Result-typed expressions. + /// If it is, returns true along with the two expressions, otherwise returns false with nulls. + /// + private bool IsConditionedOnResultInequalityExpression(TypedExpression expression, out TypedExpression lhs, out TypedExpression rhs) + { + lhs = null; + rhs = null; + + if (expression.Expression is ExpressionKind.NEQ neq + && neq.Item1.ResolvedType.Resolution == ResolvedTypeKind.Result + && neq.Item2.ResolvedType.Resolution == ResolvedTypeKind.Result) + { + lhs = neq.Item1; + rhs = neq.Item2; + return true; + } + + return false; } #endregion