diff --git a/src/QsCompiler/CompilationManager/DataStructures.cs b/src/QsCompiler/CompilationManager/DataStructures.cs index 868f958532..1f4964b3e9 100644 --- a/src/QsCompiler/CompilationManager/DataStructures.cs +++ b/src/QsCompiler/CompilationManager/DataStructures.cs @@ -326,7 +326,12 @@ internal struct TreeNode private readonly Position rootPosition; public readonly CodeFragment Fragment; public readonly IReadOnlyList Children; + + /// + /// Returns the position of the root node that all child node positions are relative to. + /// public Position GetRootPosition() => rootPosition.Copy(); + public Position GetPositionRelativeToRoot() => relPosition.Copy(); /// diff --git a/src/QsCompiler/CompilationManager/DiagnosticTools.cs b/src/QsCompiler/CompilationManager/DiagnosticTools.cs index 3144a3a7ec..b76aef1769 100644 --- a/src/QsCompiler/CompilationManager/DiagnosticTools.cs +++ b/src/QsCompiler/CompilationManager/DiagnosticTools.cs @@ -53,6 +53,20 @@ internal static Position GetAbsolutePosition(Position offset, QsPositionInfo rel return absPos; } + /// + /// Creates an absolute position by adding the zero-based offset to the relative position. + /// + /// The position. + /// A zero-based line and column offset. + /// Thrown if the position line or column is negative. + /// Thrown if offset line or column is negative. + /// The absolute position. + internal static Position GetAbsolutePosition(Position position, Tuple offset) + { + var (line, column) = offset; + return GetAbsolutePosition(position, new QsPositionInfo(line + 1, column + 1)); + } + /// /// Given the starting position, convertes the relative range returned by the Q# compiler w.r.t. that starting position into an absolute range as expected by VS. /// IMPORTANT: The position returned by the Q# Compiler is (assumed to be) one-based, whereas the given and returned absolute positions are (assumed to be) zero-based! diff --git a/src/QsCompiler/CompilationManager/TypeChecking.cs b/src/QsCompiler/CompilationManager/TypeChecking.cs index 24fc2df6af..2ed32be877 100644 --- a/src/QsCompiler/CompilationManager/TypeChecking.cs +++ b/src/QsCompiler/CompilationManager/TypeChecking.cs @@ -742,10 +742,12 @@ private static bool TryBuildIfStatement(IEnumerator nodes if (nodes.Current.Fragment.Kind is QsFragmentKind.IfClause ifCond) { + var rootPosition = nodes.Current.GetRootPosition(); + // if block var buildClause = BuildStatement(nodes.Current, (relPos, ctx) => Statements.NewConditionalBlock(nodes.Current.Fragment.Comments, relPos, ctx, ifCond.Item), - context, diagnostics); + context.WithinIfCondition, diagnostics); var ifBlock = buildClause(BuildScope(nodes.Current.Children, context, diagnostics)); // elif blocks @@ -755,7 +757,7 @@ private static bool TryBuildIfStatement(IEnumerator nodes { buildClause = BuildStatement(nodes.Current, (relPos, ctx) => Statements.NewConditionalBlock(nodes.Current.Fragment.Comments, relPos, ctx, elifCond.Item), - context, diagnostics); + context.WithinIfCondition, diagnostics); elifBlocks.Add(buildClause(BuildScope(nodes.Current.Children, context, diagnostics))); proceed = nodes.MoveNext(); } @@ -771,7 +773,15 @@ private static bool TryBuildIfStatement(IEnumerator nodes proceed = nodes.MoveNext(); } - statement = Statements.NewIfStatement(ifBlock, elifBlocks, elseBlock); + var (ifStatement, ifDiagnostics) = + Statements.NewIfStatement(context, ifBlock.Item1, ifBlock.Item2, elifBlocks, elseBlock); + statement = ifStatement; + diagnostics.AddRange(ifDiagnostics.Select(item => + { + var (relativeOffset, diagnostic) = item; + var offset = DiagnosticTools.GetAbsolutePosition(rootPosition, relativeOffset); + return Diagnostics.Generate(context.Symbols.SourceFile.Value, diagnostic, offset); + })); return true; } (statement, proceed) = (null, true); @@ -816,7 +826,12 @@ QsNullable RelativeLocation(FragmentTree.TreeNode node) => var innerTransformation = BuildScope(nodes.Current.Children, context, diagnostics); var inner = new QsPositionedBlock(innerTransformation, RelativeLocation(nodes.Current), nodes.Current.Fragment.Comments); var built = Statements.NewConjugation(outer, inner); - diagnostics.AddRange(built.Item2.Select(msg => Diagnostics.Generate(context.Symbols.SourceFile.Value, msg.Item2, nodes.Current.GetRootPosition()))); + diagnostics.AddRange(built.Item2.Select(item => + { + var (relativeOffset, diagnostic) = item; + var offset = DiagnosticTools.GetAbsolutePosition(nodes.Current.GetRootPosition(), relativeOffset); + return Diagnostics.Generate(context.Symbols.SourceFile.Value, diagnostic, offset); + })); statement = built.Item1; proceed = nodes.MoveNext(); diff --git a/src/QsCompiler/Core/SyntaxGenerator.fs b/src/QsCompiler/Core/SyntaxGenerator.fs index c33e86a772..93aef3a27a 100644 --- a/src/QsCompiler/Core/SyntaxGenerator.fs +++ b/src/QsCompiler/Core/SyntaxGenerator.fs @@ -76,6 +76,11 @@ module SyntaxGenerator = let UnitValue = AutoGeneratedExpression UnitValue QsTypeKind.UnitType false + /// Creates a typed expression that corresponds to a Bool literal with the given value. + /// Sets the range information for the built expression to Null. + let BoolLiteral value = + AutoGeneratedExpression (BoolLiteral value) QsTypeKind.Bool false + /// Creates a typed expression that corresponds to an Int literal with the given value. /// Sets the range information for the built expression to Null. let IntLiteral v = diff --git a/src/QsCompiler/DataStructures/Diagnostics.fs b/src/QsCompiler/DataStructures/Diagnostics.fs index 6e85f086be..10138d5c01 100644 --- a/src/QsCompiler/DataStructures/Diagnostics.fs +++ b/src/QsCompiler/DataStructures/Diagnostics.fs @@ -167,6 +167,9 @@ type ErrorCode = | ExpectingCallableExpr = 5021 | UnknownIdentifier = 5022 | UnsupportedResultComparison = 5023 + | ResultComparisonNotInOperationIf = 5024 + | ReturnInResultConditionedBlock = 5025 + | SetInResultConditionedBlock = 5026 | CallableRedefinition = 6001 | CallableOverlapWithTypeConstructor = 6002 @@ -544,8 +547,19 @@ type DiagnosticItem = | ErrorCode.ExpectingIterableExpr -> "The type {0} does not support iteration. Expecting an expression of array type or of type Range." | ErrorCode.ExpectingCallableExpr -> "The type of the expression must be a function or operation type. The given expression is of type {0}." | ErrorCode.UnknownIdentifier -> "No identifier with the name \"{0}\" exists." - // TODO: When the names of the runtime capabilities are finalized, they can be included in the error message. + // TODO: When the names of the runtime capabilities are finalized, they can be included in the result + // comparison error messages. | ErrorCode.UnsupportedResultComparison -> "The target {0} does not support comparing measurement results." + | ErrorCode.ResultComparisonNotInOperationIf -> + "Measurement results cannot be compared here. " + + "The execution target {0} only supports comparing measurement results as part of the condition of an if- or elif-statement in an operation." + | ErrorCode.ReturnInResultConditionedBlock -> + "A return statement cannot be used here. " + + "The execution target {0} does not support return statements in conditional blocks that depend on a measurement result." + | ErrorCode.SetInResultConditionedBlock -> + "The variable \"{0}\" cannot be reassigned here. " + + "In conditional blocks that depend on a measurement result, the execution target {1} only supports reassigning variables that were declared within the block." + | ErrorCode.CallableRedefinition -> "Invalid callable declaration. A function or operation with the name \"{0}\" already exists." | ErrorCode.CallableOverlapWithTypeConstructor -> "Invalid callable declaration. A type constructor with the name \"{0}\" already exists." | ErrorCode.TypeRedefinition -> "Invalid type declaration. A type with the name \"{0}\" already exists." diff --git a/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs b/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs index 8b89e63e2e..87b0073160 100644 --- a/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs @@ -63,7 +63,7 @@ let internal toString (t : ResolvedType) = SyntaxTreeToQsharp.Default.ToCode t /// This subtyping carries over to tuple types containing operations, and callable types containing operations as within their in- and/or output type. /// However, arrays in particular are treated as invariant; /// i.e. an array of operations of type t1 are *not* a subtype of arrays of operations of type t2 even if t1 is a subtype of t2. -let private CommonBaseType addError mismatchErr parent (lhsType : ResolvedType, lhsRange) (rhsType : ResolvedType, rhsRange) : ResolvedType = +let private CommonBaseType addError mismatchErr parent (lhsType : ResolvedType, lhsRange) (rhsType : ResolvedType, rhsRange) : ResolvedType = let raiseError errCode (lhsCond, rhsCond) = if lhsCond then lhsRange |> addError errCode if rhsCond then rhsRange |> addError errCode @@ -251,9 +251,16 @@ let private VerifyEqualityComparison context addError (lhsType, lhsRange) (rhsTy // comparison for any derived type). let argumentError = ErrorCode.ArgumentMismatchInBinaryOp, [toString lhsType; toString rhsType] let baseType = CommonBaseType addError argumentError context.Symbols.Parent (lhsType, lhsRange) (rhsType, rhsRange) + + // This assumes that: + // - Result has no derived types that support equality comparisons. + // - Compound types containing Result (e.g., tuples or arrays of results) do not support equality comparison. match baseType.Resolution with | Result when context.Capabilities = RuntimeCapabilities.QPRGen0 -> addError (ErrorCode.UnsupportedResultComparison, [context.ProcessorArchitecture.Value]) rhsRange + | Result when context.Capabilities = RuntimeCapabilities.QPRGen1 && + not (context.IsInOperation && context.IsInIfCondition) -> + addError (ErrorCode.ResultComparisonNotInOperationIf, [context.ProcessorArchitecture.Value]) rhsRange | _ -> let unsupportedError = ErrorCode.InvalidTypeInEqualityComparison, [toString baseType] VerifyIsOneOf (fun t -> t.supportsEqualityComparison) unsupportedError addError (baseType, rhsRange) |> ignore diff --git a/src/QsCompiler/SyntaxProcessor/ScopeTools.fs b/src/QsCompiler/SyntaxProcessor/ScopeTools.fs index 849252e9d7..931161b264 100644 --- a/src/QsCompiler/SyntaxProcessor/ScopeTools.fs +++ b/src/QsCompiler/SyntaxProcessor/ScopeTools.fs @@ -286,6 +286,8 @@ type ScopeContext<'a> = Symbols : SymbolTracker<'a> /// True if the parent callable for the current scope is an operation. IsInOperation : bool + /// True if the current expression is contained within the condition of an if- or elif-statement. + IsInIfCondition : bool /// The return type of the parent callable for the current scope. ReturnType : ResolvedType /// The runtime capabilities for the compilation unit. @@ -313,7 +315,12 @@ type ScopeContext<'a> = | Found declaration -> { Symbols = SymbolTracker<'a> (nsManager, spec.SourceFile, spec.Parent) IsInOperation = declaration.Kind = Operation + IsInIfCondition = false ReturnType = StripPositionInfo.Apply declaration.Signature.ReturnType Capabilities = capabilities ProcessorArchitecture = processorArchitecture } | _ -> raise <| ArgumentException "The specialization's parent callable does not exist." + + /// Returns a new scope context for an expression that is contained within the condition of an if- or + /// elif-statement. + member this.WithinIfCondition = { this with IsInIfCondition = true } diff --git a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs index 569e285e2e..d27e87572c 100644 --- a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs @@ -9,6 +9,7 @@ open System.Collections.Immutable open Microsoft.Quantum.QsCompiler open Microsoft.Quantum.QsCompiler.DataTypes open Microsoft.Quantum.QsCompiler.Diagnostics +open Microsoft.Quantum.QsCompiler.ReservedKeywords.AssemblyConstants open Microsoft.Quantum.QsCompiler.SyntaxExtensions open Microsoft.Quantum.QsCompiler.SyntaxProcessing open Microsoft.Quantum.QsCompiler.SyntaxProcessing.Expressions @@ -51,6 +52,75 @@ let private onAutoInvertGenerateError (errCode, range) (symbols : SymbolTracker< if not (symbols.RequiredFunctorSupport.Contains QsFunctor.Adjoint) then [||] else [| range |> QsCompilerDiagnostic.Error errCode |] +/// +/// Returns true if the expression is an equality or inequality comparison between two expressions of type Result. +/// +/// The name of the callable in which the expression occurs. +/// The expression. +let private isResultComparison ({ Expression = expression } : TypedExpression) = + let validType = function + | InvalidType -> None + | kind -> Some kind + let binaryType lhs rhs = validType lhs.ResolvedType.Resolution |> Option.defaultValue rhs.ResolvedType.Resolution + + // This assumes that: + // - Result has no derived types that support equality comparisons. + // - Compound types containing Result (e.g., tuples or arrays of results) do not support equality comparison. + match expression with + | EQ (lhs, rhs) -> binaryType lhs rhs = Result + | NEQ (lhs, rhs) -> binaryType lhs rhs = Result + | _ -> false + +/// Finds the locations where a mutable variable, which was not declared locally in the given scope, is reassigned. The +/// symbol tracker is not modified. Returns the name of the variable and the location of the reassignment. +let private nonLocalUpdates (symbols : SymbolTracker<_>) (scope : QsScope) = + // This assumes that a variable with the same name cannot be re-declared in an inner scope (i.e., shadowing is not + // allowed). + let identifierExists (name, location : QsLocation) = + let symbol = { Symbol = Symbol name; Range = Value location.Range } + match (symbols.ResolveIdentifier ignore symbol |> fst).VariableName with + | InvalidIdentifier -> false + | _ -> true + let accumulator = AccumulateIdentifiers() + accumulator.Statements.OnScope scope |> ignore + accumulator.SharedState.ReassignedVariables + |> Seq.collect (fun grouping -> Seq.map (fun location -> grouping.Key, location) grouping) + |> Seq.filter identifierExists + +/// Verifies that any conditional blocks which depend on a measurement result do not use any language constructs that +/// are not supported by the runtime capabilities. Returns the diagnostics for the blocks. +let private verifyResultConditionalBlocks context condBlocks elseBlock = + // Diagnostics for return statements. + let returnStatements (statement : QsStatement) = statement.ExtractAll <| fun s -> + match s.Statement with + | QsReturnStatement _ -> [s] + | _ -> [] + let returnError (statement : QsStatement) = + let error = ErrorCode.ReturnInResultConditionedBlock, [context.ProcessorArchitecture.Value] + let location = statement.Location.ValueOr { Offset = 0, 0; Range = QsCompilerDiagnostic.DefaultRange } + location.Offset, QsCompilerDiagnostic.Error error location.Range + let returnErrors (block : QsPositionedBlock) = + block.Body.Statements |> Seq.collect returnStatements |> Seq.map returnError + + // Diagnostics for variable reassignments. + let setError (name : NonNullable, location : QsLocation) = + let error = ErrorCode.SetInResultConditionedBlock, [name.Value; context.ProcessorArchitecture.Value] + location.Offset, QsCompilerDiagnostic.Error error location.Range + let setErrors (block : QsPositionedBlock) = nonLocalUpdates context.Symbols block.Body |> Seq.map setError + + let blocks = + elseBlock + |> QsNullable<_>.Map (fun block -> SyntaxGenerator.BoolLiteral true, block) + |> QsNullable<_>.Fold (fun acc x -> x :: acc) [] + |> Seq.append condBlocks + let foldErrors (dependsOnResult, diagnostics) (condition : TypedExpression, block) = + if dependsOnResult || condition.Exists isResultComparison + then true, Seq.concat [returnErrors block; setErrors block; diagnostics] + else false, diagnostics + if context.Capabilities = RuntimeCapabilities.QPRGen1 + then Seq.fold foldErrors (false, Seq.empty) blocks |> snd + else Seq.empty + // utils for building QsStatements from QsFragmentKinds @@ -250,15 +320,21 @@ let NewConditionalBlock comments location context (qsExpr : QsExpression) = let block body = condition, QsPositionedBlock.New comments (Value location) body new BlockStatement<_>(block), Array.concat [errs; autoGenErrs] -/// Given a conditional block for the if-clause of a Q# if-statement, a sequence of conditional blocks for the elif-clauses, -/// as well as optionally a positioned block of Q# statements and its location for the else-clause, builds and returns the complete if-statement. -/// Throws an ArgumentException if the given if-block contains no location information. -let NewIfStatement (ifBlock : TypedExpression * QsPositionedBlock, elifBlocks, elseBlock : QsNullable) = - let location = (snd ifBlock).Location |> function - | Null -> ArgumentException "no location is set for the given if-block" |> raise - | Value loc -> loc - let condBlocks = seq { yield ifBlock; yield! elifBlocks; } - QsConditionalStatement.New (condBlocks, elseBlock) |> QsConditionalStatement |> asStatement QsComments.Empty location LocalDeclarations.Empty +/// Given a conditional block for the if-clause of a Q# if-statement, a sequence of conditional blocks for the elif-clauses, +/// as well as optionally a positioned block of Q# statements and its location for the else-clause, builds and returns the complete if-statement. +/// Throws an ArgumentException if the given if-block contains no location information. +let NewIfStatement context (ifBlock : TypedExpression * QsPositionedBlock) elifBlocks elseBlock = + let location = + match (snd ifBlock).Location with + | Null -> ArgumentException "No location is set for the given if-block." |> raise + | Value location -> location + let condBlocks = Seq.append (Seq.singleton ifBlock) elifBlocks + let statement = + QsConditionalStatement.New (condBlocks, elseBlock) + |> QsConditionalStatement + |> asStatement QsComments.Empty location LocalDeclarations.Empty + let diagnostics = verifyResultConditionalBlocks context condBlocks elseBlock + statement, diagnostics /// Given a positioned block of Q# statements for the repeat-block of a Q# RUS-statement, a typed expression containing the success condition, /// as well as a positioned block of Q# statements for the fixup-block, builds the complete RUS-statement at the given location and returns it. diff --git a/src/QsCompiler/SyntaxProcessor/VerificationTools.fs b/src/QsCompiler/SyntaxProcessor/VerificationTools.fs index 4407a9f73e..ce20143c41 100644 --- a/src/QsCompiler/SyntaxProcessor/VerificationTools.fs +++ b/src/QsCompiler/SyntaxProcessor/VerificationTools.fs @@ -107,5 +107,3 @@ type ResolvedType with | Range -> Some (Int |> ResolvedType.New) | ArrayType bt -> Some bt | _ -> None - - diff --git a/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs b/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs index e2c5c20ca8..bb9820c18c 100644 --- a/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs +++ b/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs @@ -28,57 +28,110 @@ let private testName name = QsQualifiedName.New (NonNullable<_>.New "Microsoft.Quantum.Testing.CapabilityVerification", NonNullable<_>.New name) -/// Asserts that the tester allows the given test case. -let private allows (tester : CompilerTests) name = tester.Verify (testName name, List.empty) +/// Asserts that the tester produces the expected error codes for the test case with the given name. +let private expect (tester : CompilerTests) errorCodes name = tester.Verify (testName name, Seq.map Error errorCodes) -/// Asserts that the tester disallows the given test case. -let private restricts (tester : CompilerTests) name = - tester.Verify (testName name, [Error ErrorCode.UnsupportedResultComparison]) - -/// The names of all test cases. -let private all = +/// The names of all "simple" test cases: test cases that have exactly one unsupported result comparison error in +/// QPRGen0, and no errors in Unknown. +let private simpleTests = [ "ResultAsBool" "ResultAsBoolNeq" "ResultAsBoolOp" "ResultAsBoolNeqOp" "ResultAsBoolOpReturnIf" "ResultAsBoolNeqOpReturnIf" + "ResultAsBoolOpReturnIfNested" "ResultAsBoolOpSetIf" "ResultAsBoolNeqOpSetIf" + "ResultAsBoolOpElseSet" + "ElifSet" + "ElifElifSet" + "ElifElseSet" + "SetLocal" + "SetTuple" "EmptyIf" "EmptyIfNeq" + "EmptyIfOp" + "EmptyIfNeqOp" "Reset" "ResetNeq" ] -let [] ``Unknown allows all Result comparison`` () = List.iter (allows unknown) all -let [] ``QPRGen0 restricts all Result comparison`` () = List.iter (restricts gen0) all +[] +let ``Unknown allows all Result comparison`` () = + List.iter (expect unknown []) simpleTests + "SetReusedName" |> expect unknown [ErrorCode.LocalVariableAlreadyExists] + [ "ResultTuple" + "ResultArray" ] + |> List.iter (expect unknown [ErrorCode.InvalidTypeInEqualityComparison]) -[] +[] +let ``QPRGen0 restricts all Result comparison`` () = + List.iter (expect gen0 [ErrorCode.UnsupportedResultComparison]) simpleTests + "SetReusedName" |> expect gen0 [ErrorCode.LocalVariableAlreadyExists; ErrorCode.UnsupportedResultComparison] + [ "ResultTuple" + "ResultArray" ] + |> List.iter (expect unknown [ErrorCode.InvalidTypeInEqualityComparison]) + +[] let ``QPRGen1 restricts Result comparison in functions`` () = - restricts gen1 "ResultAsBool" - restricts gen1 "ResultAsBoolNeq" + [ "ResultAsBool" + "ResultAsBoolNeq" ] + |> List.iter (expect gen1 [ErrorCode.ResultComparisonNotInOperationIf]) + [ "ResultTuple" + "ResultArray" ] + |> List.iter (expect unknown [ErrorCode.InvalidTypeInEqualityComparison]) -[] +[] let ``QPRGen1 restricts non-if Result comparison in operations`` () = - restricts gen1 "ResultAsBoolOp" - restricts gen1 "ResultAsBoolNeqOp" + [ "ResultAsBoolOp" + "ResultAsBoolNeqOp" ] + |> List.iter (expect gen1 [ErrorCode.ResultComparisonNotInOperationIf]) -[] +[] let ``QPRGen1 restricts return from Result if`` () = - restricts gen1 "ResultAsBoolOpReturnIf" - restricts gen1 "ResultAsBoolNeqOpReturnIf" + [ "ResultAsBoolOpReturnIf" + "ResultAsBoolOpReturnIfNested" + "ResultAsBoolNeqOpReturnIf" ] + |> List.iter (expect gen1 <| Seq.replicate 2 ErrorCode.ReturnInResultConditionedBlock) -[] -let ``QPRGen1 restricts mutable set from Result if`` () = - restricts gen1 "ResultAsBoolOpSetIf" - restricts gen1 "ResultAsBoolNeqOpSetIf" +[] +let ``QPRGen1 allows local mutable set from Result if`` () = "SetLocal" |> expect gen1 [] [] -let ``QPRGen1 allows empty Result if`` () = - allows gen1 "EmptyIf" - allows gen1 "EmptyIfNeq" +let ``QPRGen1 restricts non-local mutable set from Result if`` () = + [ "ResultAsBoolOpSetIf" + "ResultAsBoolNeqOpSetIf" + "SetTuple" ] + |> List.iter (expect gen1 [ErrorCode.SetInResultConditionedBlock]) + "SetReusedName" + |> expect gen1 (ErrorCode.LocalVariableAlreadyExists :: List.replicate 2 ErrorCode.SetInResultConditionedBlock) + +[] +let ``QPRGen1 restricts non-local mutable set from Result elif`` () = + [ "ElifSet" + "ElifElifSet" ] + |> List.iter (expect gen1 [ErrorCode.SetInResultConditionedBlock]) + +[] +let ``QPRGen1 restricts non-local mutable set from Result else`` () = + [ "ResultAsBoolOpElseSet" + "ElifElseSet" ] + |> List.iter (expect gen1 [ErrorCode.SetInResultConditionedBlock]) + +[] +let ``QPRGen1 restricts empty Result if function`` () = + [ "EmptyIf" + "EmptyIfNeq" ] + |> List.iter (expect gen1 [ErrorCode.ResultComparisonNotInOperationIf]) + +[] +let ``QPRGen1 allows empty Result if operation`` () = + [ "EmptyIfOp" + "EmptyIfNeqOp" ] + |> List.iter (expect gen1 []) [] let ``QPRGen1 allows operation call from Result if`` () = - allows gen1 "Reset" - allows gen1 "ResetNeq" + [ "Reset" + "ResetNeq" ] + |> List.iter (expect gen1 []) diff --git a/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs b/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs index 000161c30a..ee8fc25291 100644 --- a/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs +++ b/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs @@ -11,10 +11,12 @@ namespace Microsoft.Quantum.Testing.CapabilityVerification { body intrinsic; } + internal operation NoOp() : Unit { } + function ResultAsBool(result : Result) : Bool { return result == Zero ? false | true; } - + function ResultAsBoolNeq(result : Result) : Bool { return result != One ? false | true; } @@ -22,7 +24,7 @@ namespace Microsoft.Quantum.Testing.CapabilityVerification { operation ResultAsBoolOp(result : Result) : Bool { return result == Zero ? false | true; } - + function ResultAsBoolNeqOp(result : Result) : Bool { return result != One ? false | true; } @@ -34,7 +36,7 @@ namespace Microsoft.Quantum.Testing.CapabilityVerification { return true; } } - + operation ResultAsBoolNeqOpReturnIf(result : Result) : Bool { if (result != One) { return false; @@ -43,6 +45,24 @@ namespace Microsoft.Quantum.Testing.CapabilityVerification { } } + operation ResultAsBoolOpReturnIfNested(result : Result) : Bool { + if (result == Zero) { + let x = 5; + if (x == 5) { + return false; + } else { + fail "error"; + } + } else { + let x = 7; + if (x == 7) { + return true; + } else { + fail "error"; + } + } + } + operation ResultAsBoolOpSetIf(result : Result) : Bool { mutable b = false; if (result == One) { @@ -50,7 +70,7 @@ namespace Microsoft.Quantum.Testing.CapabilityVerification { } return b; } - + operation ResultAsBoolNeqOpSetIf(result : Result) : Bool { mutable b = false; if (result != Zero) { @@ -59,11 +79,93 @@ namespace Microsoft.Quantum.Testing.CapabilityVerification { return b; } - operation EmptyIf(result : Result) : Unit { + operation ResultAsBoolOpElseSet(result : Result) : Bool { + mutable b = false; + if (result == Zero) { + NoOp(); + } else { + set b = true; + } + return b; + } + + operation ElifSet(result : Result, flag : Bool) : Bool { + mutable b = false; + if (flag) { + set b = true; + } elif (result != Zero) { + set b = true; + } + return b; + } + + operation ElifElifSet(result : Result, flag : Bool) : Bool { + mutable b = false; + if (flag) { + set b = true; + } elif (flag) { + set b = true; + } elif (result != Zero) { + set b = true; + } else { + NoOp(); + } + return b; + } + + operation ElifElseSet(result : Result, flag : Bool) : Bool { + mutable b = false; + if (flag) { + set b = true; + } elif (result == Zero) { + NoOp(); + } else { + set b = true; + } + return b; + } + + operation SetLocal(result : Result) : Unit { + if (result == One) { + mutable b = false; + set b = true; + } + } + + operation SetReusedName(result : Result) : Unit { + mutable b = false; + if (result == One) { + if (true) { + // Re-declaring b is an error, but it shouldn't affect the invalid sets below. + mutable b = false; + set b = true; + } + set b = true; + } + } + + operation SetTuple(result : Result) : Unit { + mutable a = false; + if (result == One) { + mutable b = 0; + mutable c = 0.0; + set (c, (b, a)) = (1.0, (1, true)); + } + } + + function EmptyIf(result : Result) : Unit { if (result == Zero) { } } - operation EmptyIfNeq(result : Result) : Unit { + function EmptyIfNeq(result : Result) : Unit { + if (result != Zero) { } + } + + operation EmptyIfOp(result : Result) : Unit { + if (result == Zero) { } + } + + operation EmptyIfNeqOp(result : Result) : Unit { if (result != Zero) { } } @@ -78,4 +180,15 @@ namespace Microsoft.Quantum.Testing.CapabilityVerification { X(q); } } + + // Tuples and arrays currently don't support equality comparison, but result comparison should still be prevented if + // they do. + + function ResultTuple(rr : (Result, Result)) : Bool { + return rr == (One, One) ? true | false; + } + + function ResultArray(rs : Result[]) : Bool { + return rs == [One] ? true | false; + } }