From ccc34b2863ea9e450eb06479e0ff67901fb3ad41 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Tue, 16 Jun 2020 13:36:58 -0700 Subject: [PATCH 01/39] Verify that result comparisons are in if condition --- src/QsCompiler/CompilationManager/TypeChecking.cs | 4 ++-- src/QsCompiler/DataStructures/Diagnostics.fs | 8 ++++++-- src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs | 2 ++ src/QsCompiler/SyntaxProcessor/ScopeTools.fs | 7 +++++++ 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/QsCompiler/CompilationManager/TypeChecking.cs b/src/QsCompiler/CompilationManager/TypeChecking.cs index 9caeeaf924..2675c28add 100644 --- a/src/QsCompiler/CompilationManager/TypeChecking.cs +++ b/src/QsCompiler/CompilationManager/TypeChecking.cs @@ -745,7 +745,7 @@ private static bool TryBuildIfStatement(IEnumerator nodes // 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 +755,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(); } diff --git a/src/QsCompiler/DataStructures/Diagnostics.fs b/src/QsCompiler/DataStructures/Diagnostics.fs index 49d67ef219..3b8347ef29 100644 --- a/src/QsCompiler/DataStructures/Diagnostics.fs +++ b/src/QsCompiler/DataStructures/Diagnostics.fs @@ -167,6 +167,7 @@ type ErrorCode = | ExpectingCallableExpr = 5021 | UnknownIdentifier = 5022 | UnsupportedResultComparison = 5023 + | ResultComparisonOutsideIf = 5024 | CallableRedefinition = 6001 | CallableOverlapWithTypeConstructor = 6002 @@ -544,11 +545,14 @@ 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 result + // comparison error messages. | ErrorCode.UnsupportedResultComparison -> - // TODO: When the names of the runtime capabilities are finalized, they can be included in the error - // message. "The execution target {0} does not support comparing measurement results. " + "Choose an execution target with additional capabilities or avoid result comparisons." + | ErrorCode.ResultComparisonOutsideIf -> + "The execution target {0} supports comparing measurement results only as part of the condition of an if- or elif-statement. " + + "Choose an execution target with additional capabilities or avoid result comparisons outside of if-conditions." | 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." diff --git a/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs b/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs index 4c62ae08f6..99182026ba 100644 --- a/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs @@ -254,6 +254,8 @@ let private VerifyEqualityComparison context addError (lhsType, lhsRange) (rhsTy match baseType.Resolution with | Result when context.Capabilities = RuntimeCapabilities.QPRGen0 -> addError (ErrorCode.UnsupportedResultComparison, [context.ExecutionTarget.Value]) rhsRange + | Result when context.Capabilities = RuntimeCapabilities.QPRGen1 && not context.IsInIfCondition -> + addError (ErrorCode.ResultComparisonOutsideIf, [context.ExecutionTarget.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 c838e10fa4..c8135adc0e 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 ExecutionTarget = executionTarget } | _ -> 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 } From 9190fbd0f34856f385ca318d9d1c1fc78cea203e Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Mon, 22 Jun 2020 16:15:43 -0700 Subject: [PATCH 02/39] Add test cases for elif/else Result comparisons --- .../CapabilityVerificationTests.fs | 24 ++++++++-- .../TestCases/CapabilityVerification.qs | 48 +++++++++++++++++++ 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs b/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs index e2c5c20ca8..5bb951cc20 100644 --- a/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs +++ b/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs @@ -45,6 +45,10 @@ let private all = "ResultAsBoolNeqOpReturnIf" "ResultAsBoolOpSetIf" "ResultAsBoolNeqOpSetIf" + "ResultAsBoolOpElseSet" + "ElifSet" + "ElifElifSet" + "ElifElseSet" "EmptyIf" "EmptyIfNeq" "Reset" @@ -53,25 +57,35 @@ let private all = let [] ``Unknown allows all Result comparison`` () = List.iter (allows unknown) all let [] ``QPRGen0 restricts all Result comparison`` () = List.iter (restricts gen0) all -[] +[] let ``QPRGen1 restricts Result comparison in functions`` () = restricts gen1 "ResultAsBool" restricts gen1 "ResultAsBoolNeq" -[] +[] let ``QPRGen1 restricts non-if Result comparison in operations`` () = restricts gen1 "ResultAsBoolOp" restricts gen1 "ResultAsBoolNeqOp" -[] +[] let ``QPRGen1 restricts return from Result if`` () = restricts gen1 "ResultAsBoolOpReturnIf" restricts gen1 "ResultAsBoolNeqOpReturnIf" -[] +[] let ``QPRGen1 restricts mutable set from Result if`` () = restricts gen1 "ResultAsBoolOpSetIf" - restricts gen1 "ResultAsBoolNeqOpSetIf" + restricts gen1 "ResultAsBoolNeqOpSetIf" + +[] +let ``QPRGen1 restricts mutable set from Result elif`` () = + restricts gen1 "ElifSet" + restricts gen1 "ElifElifSet" + +[] +let ``QPRGen1 restricts mutable set from Result else`` () = + restricts gen1 "ResultAsBoolOpElseSet" + restricts gen1 "ElifElseSet" [] let ``QPRGen1 allows empty Result if`` () = diff --git a/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs b/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs index 000161c30a..5e84d26ae8 100644 --- a/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs +++ b/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs @@ -11,6 +11,8 @@ namespace Microsoft.Quantum.Testing.CapabilityVerification { body intrinsic; } + internal operation NoOp() : Unit { } + function ResultAsBool(result : Result) : Bool { return result == Zero ? false | true; } @@ -59,6 +61,52 @@ namespace Microsoft.Quantum.Testing.CapabilityVerification { return b; } + 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 EmptyIf(result : Result) : Unit { if (result == Zero) { } } From 73d94bc41be8f35b06e5c34f3c3925387b48a48a Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Mon, 22 Jun 2020 17:59:31 -0700 Subject: [PATCH 03/39] Gen 1 result ifs can only be in operations --- src/QsCompiler/DataStructures/Diagnostics.fs | 9 ++-- .../SyntaxProcessor/ExpressionVerification.fs | 5 +- .../CapabilityVerificationTests.fs | 47 +++++++++++-------- .../TestCases/CapabilityVerification.qs | 18 +++++-- 4 files changed, 49 insertions(+), 30 deletions(-) diff --git a/src/QsCompiler/DataStructures/Diagnostics.fs b/src/QsCompiler/DataStructures/Diagnostics.fs index 3b8347ef29..1a63484dd8 100644 --- a/src/QsCompiler/DataStructures/Diagnostics.fs +++ b/src/QsCompiler/DataStructures/Diagnostics.fs @@ -167,7 +167,7 @@ type ErrorCode = | ExpectingCallableExpr = 5021 | UnknownIdentifier = 5022 | UnsupportedResultComparison = 5023 - | ResultComparisonOutsideIf = 5024 + | ResultComparisonNotInOperationIf = 5024 | CallableRedefinition = 6001 | CallableOverlapWithTypeConstructor = 6002 @@ -550,9 +550,10 @@ type DiagnosticItem = | ErrorCode.UnsupportedResultComparison -> "The execution target {0} does not support comparing measurement results. " + "Choose an execution target with additional capabilities or avoid result comparisons." - | ErrorCode.ResultComparisonOutsideIf -> - "The execution target {0} supports comparing measurement results only as part of the condition of an if- or elif-statement. " + - "Choose an execution target with additional capabilities or avoid result comparisons outside of if-conditions." + | ErrorCode.ResultComparisonNotInOperationIf -> + "The execution target {0} supports comparing measurement results only as part of the condition of an " + + "if- or elif-statement in an operation. " + + "Choose an execution target with additional capabilities or avoid unsupported result comparisons." | 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." diff --git a/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs b/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs index 99182026ba..df7a88aa4d 100644 --- a/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs @@ -254,8 +254,9 @@ let private VerifyEqualityComparison context addError (lhsType, lhsRange) (rhsTy match baseType.Resolution with | Result when context.Capabilities = RuntimeCapabilities.QPRGen0 -> addError (ErrorCode.UnsupportedResultComparison, [context.ExecutionTarget.Value]) rhsRange - | Result when context.Capabilities = RuntimeCapabilities.QPRGen1 && not context.IsInIfCondition -> - addError (ErrorCode.ResultComparisonOutsideIf, [context.ExecutionTarget.Value]) rhsRange + | Result when context.Capabilities = RuntimeCapabilities.QPRGen1 && + not (context.IsInOperation && context.IsInIfCondition) -> + addError (ErrorCode.ResultComparisonNotInOperationIf, [context.ExecutionTarget.Value]) rhsRange | _ -> let unsupportedError = ErrorCode.InvalidTypeInEqualityComparison, [toString baseType] VerifyIsOneOf (fun t -> t.supportsEqualityComparison) unsupportedError addError (baseType, rhsRange) |> ignore diff --git a/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs b/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs index 5bb951cc20..17541f41a6 100644 --- a/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs +++ b/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs @@ -31,9 +31,8 @@ let private testName 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 disallows the given test case. -let private restricts (tester : CompilerTests) name = - tester.Verify (testName name, [Error ErrorCode.UnsupportedResultComparison]) +/// Asserts that the tester does not allow the given test case. +let private restricts (tester : CompilerTests) errorCode name = tester.Verify (testName name, [Error errorCode]) /// The names of all test cases. let private all = @@ -51,46 +50,56 @@ let private all = "ElifElseSet" "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 ``QPRGen0 restricts all Result comparison`` () = + List.iter (restricts gen0 ErrorCode.UnsupportedResultComparison) all [] let ``QPRGen1 restricts Result comparison in functions`` () = - restricts gen1 "ResultAsBool" - restricts gen1 "ResultAsBoolNeq" + restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ResultAsBool" + restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ResultAsBoolNeq" [] let ``QPRGen1 restricts non-if Result comparison in operations`` () = - restricts gen1 "ResultAsBoolOp" - restricts gen1 "ResultAsBoolNeqOp" + restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ResultAsBoolOp" + restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ResultAsBoolNeqOp" [] let ``QPRGen1 restricts return from Result if`` () = - restricts gen1 "ResultAsBoolOpReturnIf" - restricts gen1 "ResultAsBoolNeqOpReturnIf" + restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ResultAsBoolOpReturnIf" + restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ResultAsBoolNeqOpReturnIf" [] let ``QPRGen1 restricts mutable set from Result if`` () = - restricts gen1 "ResultAsBoolOpSetIf" - restricts gen1 "ResultAsBoolNeqOpSetIf" + restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ResultAsBoolOpSetIf" + restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ResultAsBoolNeqOpSetIf" [] let ``QPRGen1 restricts mutable set from Result elif`` () = - restricts gen1 "ElifSet" - restricts gen1 "ElifElifSet" + restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ElifSet" + restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ElifElifSet" [] let ``QPRGen1 restricts mutable set from Result else`` () = - restricts gen1 "ResultAsBoolOpElseSet" - restricts gen1 "ElifElseSet" + restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ResultAsBoolOpElseSet" + restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ElifElseSet" + +[] +let ``QPRGen1 restricts empty Result if function`` () = + restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "EmptyIf" + restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "EmptyIfNeq" [] -let ``QPRGen1 allows empty Result if`` () = - allows gen1 "EmptyIf" - allows gen1 "EmptyIfNeq" +let ``QPRGen1 allows empty Result if operation`` () = + allows gen1 "EmptyIfOp" + allows gen1 "EmptyIfNeqOp" [] let ``QPRGen1 allows operation call from Result if`` () = diff --git a/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs b/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs index 5e84d26ae8..77a7b4fb31 100644 --- a/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs +++ b/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs @@ -16,7 +16,7 @@ namespace Microsoft.Quantum.Testing.CapabilityVerification { function ResultAsBool(result : Result) : Bool { return result == Zero ? false | true; } - + function ResultAsBoolNeq(result : Result) : Bool { return result != One ? false | true; } @@ -36,7 +36,7 @@ namespace Microsoft.Quantum.Testing.CapabilityVerification { return true; } } - + operation ResultAsBoolNeqOpReturnIf(result : Result) : Bool { if (result != One) { return false; @@ -52,7 +52,7 @@ namespace Microsoft.Quantum.Testing.CapabilityVerification { } return b; } - + operation ResultAsBoolNeqOpSetIf(result : Result) : Bool { mutable b = false; if (result != Zero) { @@ -107,11 +107,19 @@ namespace Microsoft.Quantum.Testing.CapabilityVerification { return b; } - operation EmptyIf(result : Result) : Unit { + function EmptyIf(result : Result) : Unit { + if (result == Zero) { } + } + + function EmptyIfNeq(result : Result) : Unit { + if (result != Zero) { } + } + + operation EmptyIfOp(result : Result) : Unit { if (result == Zero) { } } - operation EmptyIfNeq(result : Result) : Unit { + operation EmptyIfNeqOp(result : Result) : Unit { if (result != Zero) { } } From 17da9e8009f5e0ccf834eab0ac46862ee3914883 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Tue, 23 Jun 2020 11:24:03 -0700 Subject: [PATCH 04/39] Un-skip QPRGen1 tests --- .../Tests.Compiler/CapabilityVerificationTests.fs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs b/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs index 17541f41a6..14e06cc0fb 100644 --- a/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs +++ b/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs @@ -61,32 +61,32 @@ let [] ``Unknown allows all Result comparison`` () = List.iter (allows unk let ``QPRGen0 restricts all Result comparison`` () = List.iter (restricts gen0 ErrorCode.UnsupportedResultComparison) all -[] +[] let ``QPRGen1 restricts Result comparison in functions`` () = restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ResultAsBool" restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ResultAsBoolNeq" -[] +[] let ``QPRGen1 restricts non-if Result comparison in operations`` () = restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ResultAsBoolOp" restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ResultAsBoolNeqOp" -[] +[] let ``QPRGen1 restricts return from Result if`` () = restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ResultAsBoolOpReturnIf" restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ResultAsBoolNeqOpReturnIf" -[] +[] let ``QPRGen1 restricts mutable set from Result if`` () = restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ResultAsBoolOpSetIf" restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ResultAsBoolNeqOpSetIf" -[] +[] let ``QPRGen1 restricts mutable set from Result elif`` () = restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ElifSet" restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ElifElifSet" -[] +[] let ``QPRGen1 restricts mutable set from Result else`` () = restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ResultAsBoolOpElseSet" restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ElifElseSet" From 029f1f6a073e2e40e3cfdf6d95bce5f518a9b409 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Tue, 23 Jun 2020 18:38:41 -0700 Subject: [PATCH 05/39] Add new diagnostics --- src/QsCompiler/DataStructures/Diagnostics.fs | 4 ++++ .../CapabilityVerificationTests.fs | 16 ++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/QsCompiler/DataStructures/Diagnostics.fs b/src/QsCompiler/DataStructures/Diagnostics.fs index 1a63484dd8..0807002368 100644 --- a/src/QsCompiler/DataStructures/Diagnostics.fs +++ b/src/QsCompiler/DataStructures/Diagnostics.fs @@ -168,6 +168,8 @@ type ErrorCode = | UnknownIdentifier = 5022 | UnsupportedResultComparison = 5023 | ResultComparisonNotInOperationIf = 5024 + | ReturnInResultConditionedBlock = 5025 + | SetInResultConditionedBlock = 5026 | CallableRedefinition = 6001 | CallableOverlapWithTypeConstructor = 6002 @@ -554,6 +556,8 @@ type DiagnosticItem = "The execution target {0} supports comparing measurement results only as part of the condition of an " + "if- or elif-statement in an operation. " + "Choose an execution target with additional capabilities or avoid unsupported result comparisons." + | ErrorCode.ReturnInResultConditionedBlock -> raise <| NotImplementedException() + | ErrorCode.SetInResultConditionedBlock -> raise <| NotImplementedException() | 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." diff --git a/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs b/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs index 14e06cc0fb..d5ef6ca0b3 100644 --- a/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs +++ b/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs @@ -73,23 +73,23 @@ let ``QPRGen1 restricts non-if Result comparison in operations`` () = [] let ``QPRGen1 restricts return from Result if`` () = - restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ResultAsBoolOpReturnIf" - restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ResultAsBoolNeqOpReturnIf" + restricts gen1 ErrorCode.ReturnInResultConditionedBlock "ResultAsBoolOpReturnIf" + restricts gen1 ErrorCode.ReturnInResultConditionedBlock "ResultAsBoolNeqOpReturnIf" [] let ``QPRGen1 restricts mutable set from Result if`` () = - restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ResultAsBoolOpSetIf" - restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ResultAsBoolNeqOpSetIf" + restricts gen1 ErrorCode.SetInResultConditionedBlock "ResultAsBoolOpSetIf" + restricts gen1 ErrorCode.SetInResultConditionedBlock "ResultAsBoolNeqOpSetIf" [] let ``QPRGen1 restricts mutable set from Result elif`` () = - restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ElifSet" - restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ElifElifSet" + restricts gen1 ErrorCode.SetInResultConditionedBlock "ElifSet" + restricts gen1 ErrorCode.SetInResultConditionedBlock "ElifElifSet" [] let ``QPRGen1 restricts mutable set from Result else`` () = - restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ResultAsBoolOpElseSet" - restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ElifElseSet" + restricts gen1 ErrorCode.SetInResultConditionedBlock "ResultAsBoolOpElseSet" + restricts gen1 ErrorCode.SetInResultConditionedBlock "ElifElseSet" [] let ``QPRGen1 restricts empty Result if function`` () = From 91d170ef812c69ba8d8c022a61e81bf066401899 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Tue, 23 Jun 2020 18:40:48 -0700 Subject: [PATCH 06/39] Messy, but most QPRGen1 tests passing --- .../CompilationManager/TypeChecking.cs | 6 +- src/QsCompiler/Core/SyntaxGenerator.fs | 2 + .../SyntaxProcessor/StatementVerification.fs | 68 ++++++++- src/QsCompiler/Transformations/NodeSearch.cs | 136 ++++++++++++++++++ 4 files changed, 206 insertions(+), 6 deletions(-) create mode 100644 src/QsCompiler/Transformations/NodeSearch.cs diff --git a/src/QsCompiler/CompilationManager/TypeChecking.cs b/src/QsCompiler/CompilationManager/TypeChecking.cs index 2675c28add..cc2ebf3dc6 100644 --- a/src/QsCompiler/CompilationManager/TypeChecking.cs +++ b/src/QsCompiler/CompilationManager/TypeChecking.cs @@ -771,7 +771,11 @@ private static bool TryBuildIfStatement(IEnumerator nodes proceed = nodes.MoveNext(); } - statement = Statements.NewIfStatement(ifBlock, elifBlocks, elseBlock); + var (ifStatement, ifDiagnostics) = Statements.NewIfStatement( + context, ifBlock.ToValueTuple(), elifBlocks, elseBlock); + statement = ifStatement; + diagnostics.AddRange(ifDiagnostics.Select(diagnostic => Diagnostics.Generate( + context.Symbols.SourceFile.Value, diagnostic, nodes.Current.GetRootPosition()))); return true; } (statement, proceed) = (null, true); diff --git a/src/QsCompiler/Core/SyntaxGenerator.fs b/src/QsCompiler/Core/SyntaxGenerator.fs index c33e86a772..caef691121 100644 --- a/src/QsCompiler/Core/SyntaxGenerator.fs +++ b/src/QsCompiler/Core/SyntaxGenerator.fs @@ -76,6 +76,8 @@ module SyntaxGenerator = let UnitValue = AutoGeneratedExpression UnitValue QsTypeKind.UnitType false + 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/SyntaxProcessor/StatementVerification.fs b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs index 569e285e2e..ee95808e62 100644 --- a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs @@ -9,12 +9,14 @@ 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 open Microsoft.Quantum.QsCompiler.SyntaxProcessing.VerificationTools open Microsoft.Quantum.QsCompiler.SyntaxTokens open Microsoft.Quantum.QsCompiler.SyntaxTree +open Microsoft.Quantum.QsCompiler.Transformations open Microsoft.Quantum.QsCompiler.Transformations.SearchAndReplace @@ -250,15 +252,71 @@ 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, +let private isResultComparison ({ Expression = expr } : TypedExpression) = + // TODO: Technically, it should be the common base type of LHS and RHS. + let bothResult lhs rhs = + match lhs.ResolvedType.Resolution, rhs.ResolvedType.Resolution with + | Result, Result -> true + | _ -> false + match expr with + | EQ (lhs, rhs) -> bothResult lhs rhs + | NEQ (lhs, rhs) -> bothResult lhs rhs + | _ -> false + +let private isReturnStatement { Statement = statement } = + match statement with + | QsReturnStatement -> true + | _ -> false + +let private verifyResultConditionalBlocks (blocks : (TypedExpression * QsPositionedBlock) seq) = + let returnDiagnostics (block : QsPositionedBlock) = + FindStatements.Apply (Func<_, _> isReturnStatement, block.Body) + |> Seq.map (fun statement -> + QsCompilerDiagnostic.Error + (ErrorCode.ReturnInResultConditionedBlock, []) + (statement.Location + |> QsNullable<_>.Map (fun location -> location.Range) + |> (fun nullable -> nullable.ValueOr QsCompilerDiagnostic.DefaultRange))) + + let setDiagnostics (block : QsPositionedBlock) = + UpdatedOutsideVariables.Apply block.Body + |> Seq.map (fun variable -> + QsCompilerDiagnostic.Error + (ErrorCode.SetInResultConditionedBlock, []) + (variable.Range.ValueOr QsCompilerDiagnostic.DefaultRange)) + + ((false, Seq.empty), blocks) + ||> Seq.fold (fun (foundResultEq, diagnostics) (condition, block) -> + let newFoundResultEq = foundResultEq || FindExpressions.Contains (Func<_, _> isResultComparison, condition) + let newDiagnostics = + if newFoundResultEq + then Seq.concat [returnDiagnostics block; setDiagnostics block; diagnostics] + else diagnostics + (newFoundResultEq, newDiagnostics)) + |> snd + +/// 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 +let NewIfStatement context (ifBlock : struct (TypedExpression * QsPositionedBlock)) elifBlocks elseBlock = + let location = + match (ifBlock.ToTuple() |> snd).Location with | 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 + let condBlocks = Seq.append (ifBlock.ToTuple() |> Seq.singleton) elifBlocks + let allBlocks = + match elseBlock with + | Value block -> Seq.append condBlocks (Seq.singleton (SyntaxGenerator.BoolLiteral true, block)) + | Null -> condBlocks + let diagnostics = + if context.Capabilities = RuntimeCapabilities.QPRGen1 + then verifyResultConditionalBlocks allBlocks + else Seq.empty + let statement = + QsConditionalStatement.New (condBlocks, elseBlock) + |> QsConditionalStatement + |> asStatement QsComments.Empty location LocalDeclarations.Empty + 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/Transformations/NodeSearch.cs b/src/QsCompiler/Transformations/NodeSearch.cs new file mode 100644 index 0000000000..3d55c43b36 --- /dev/null +++ b/src/QsCompiler/Transformations/NodeSearch.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Quantum.QsCompiler.DataTypes; +using Microsoft.Quantum.QsCompiler.SyntaxTokens; +using Microsoft.Quantum.QsCompiler.SyntaxTree; +using Microsoft.Quantum.QsCompiler.Transformations.Core; + +namespace Microsoft.Quantum.QsCompiler.Transformations +{ + using QsRangeInfo = QsNullable>; + using QsExpressionKind = QsExpressionKind; + + public static class FindExpressions + { + public static IList Find(Func predicate, TypedExpression expression) + { + var transformation = new Transformation(new State(predicate)); + transformation.OnTypedExpression(expression); + return transformation.SharedState.Expressions; + } + + public static bool Contains(Func predicate, TypedExpression expression) => + Find(predicate, expression).Any(); + + private sealed class State + { + internal Func Predicate { get; } + + internal IList Expressions { get; } = new List(); + + internal State(Func predicate) => Predicate = predicate; + } + + private sealed class Transformation : ExpressionTransformation + { + internal Transformation(State state) : base(state, TransformationOptions.NoRebuild) + { + } + + public override TypedExpression OnTypedExpression(TypedExpression expression) + { + if (SharedState.Predicate(expression)) + { + SharedState.Expressions.Add(expression); + } + return base.OnTypedExpression(expression); + } + } + } + + public static class FindStatements + { + public static IList Apply(Func predicate, QsScope scope) + { + var transformation = new Transformation(new State(predicate)); + transformation.OnScope(scope); + return transformation.SharedState.Statements; + } + + private sealed class State + { + internal Func Predicate { get; } + + internal IList Statements { get; } = new List(); + + internal State(Func predicate) => Predicate = predicate; + } + + private sealed class Transformation : StatementTransformation + { + internal Transformation(State state) : base(state, TransformationOptions.NoRebuild) + { + } + + public override QsStatement OnStatement(QsStatement statement) + { + if (SharedState.Predicate(statement)) + { + SharedState.Statements.Add(statement); + } + return base.OnStatement(statement); + } + } + } + + public static class UpdatedOutsideVariables + { + public static List Apply(QsScope scope) + { + var transformation = new SyntaxTreeTransformation(new State(), TransformationOptions.NoRebuild); + transformation.StatementKinds = new StatementKindTransformation(transformation); + transformation.Statements.OnScope(scope); + return transformation.SharedState.UpdatedOutsideVariables; + } + + private static IEnumerable Symbols(SymbolTuple tuple) => tuple switch + { + SymbolTuple.VariableName name => Enumerable.Repeat(name.Item.Value, 1), + SymbolTuple.VariableNameTuple names => names.Item.SelectMany(Symbols), + _ => Enumerable.Empty() + }; + + private sealed class State + { + internal List DeclaredVariables { get; } = new List(); + + internal List UpdatedOutsideVariables = new List(); + } + + private sealed class StatementKindTransformation : StatementKindTransformation + { + internal StatementKindTransformation(SyntaxTreeTransformation parent) : base(parent) + { + } + + public override QsStatementKind OnVariableDeclaration(QsBinding binding) + { + SharedState.DeclaredVariables.AddRange(Symbols(binding.Lhs)); + return base.OnVariableDeclaration(binding); + } + + public override QsStatementKind OnValueUpdate(QsValueUpdate update) + { + var variables = FindExpressions.Find( + expr => + expr.Expression is QsExpressionKind.Identifier id && + id.Item1 is Identifier.LocalVariable local && + !SharedState.DeclaredVariables.Contains(local.Item.Value), + update.Lhs); + SharedState.UpdatedOutsideVariables.AddRange(variables); + return base.OnValueUpdate(update); + } + } + } +} From e3ce63264747e449a8f4a136434164fc79716a77 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Wed, 24 Jun 2020 12:25:40 -0700 Subject: [PATCH 07/39] Fix diagnostic messages --- src/QsCompiler/DataStructures/Diagnostics.fs | 4 ++-- .../Tests.Compiler/TestCases/CapabilityVerification.qs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/QsCompiler/DataStructures/Diagnostics.fs b/src/QsCompiler/DataStructures/Diagnostics.fs index 0807002368..61312348db 100644 --- a/src/QsCompiler/DataStructures/Diagnostics.fs +++ b/src/QsCompiler/DataStructures/Diagnostics.fs @@ -556,8 +556,8 @@ type DiagnosticItem = "The execution target {0} supports comparing measurement results only as part of the condition of an " + "if- or elif-statement in an operation. " + "Choose an execution target with additional capabilities or avoid unsupported result comparisons." - | ErrorCode.ReturnInResultConditionedBlock -> raise <| NotImplementedException() - | ErrorCode.SetInResultConditionedBlock -> raise <| NotImplementedException() + | ErrorCode.ReturnInResultConditionedBlock -> "TODO" + | ErrorCode.SetInResultConditionedBlock -> "TODO" | 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." diff --git a/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs b/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs index 77a7b4fb31..733211ebf4 100644 --- a/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs +++ b/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs @@ -24,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; } From 028e627e5301af4689bb10f22568edb8c0ddf0fc Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Wed, 24 Jun 2020 13:45:30 -0700 Subject: [PATCH 08/39] Update diagnostic messages --- src/QsCompiler/DataStructures/Diagnostics.fs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/QsCompiler/DataStructures/Diagnostics.fs b/src/QsCompiler/DataStructures/Diagnostics.fs index 61312348db..207e926664 100644 --- a/src/QsCompiler/DataStructures/Diagnostics.fs +++ b/src/QsCompiler/DataStructures/Diagnostics.fs @@ -556,8 +556,8 @@ type DiagnosticItem = "The execution target {0} supports comparing measurement results only as part of the condition of an " + "if- or elif-statement in an operation. " + "Choose an execution target with additional capabilities or avoid unsupported result comparisons." - | ErrorCode.ReturnInResultConditionedBlock -> "TODO" - | ErrorCode.SetInResultConditionedBlock -> "TODO" + | ErrorCode.ReturnInResultConditionedBlock -> "TODO: ReturnInResultConditionedBlock" + | ErrorCode.SetInResultConditionedBlock -> "TODO: SetInResultConditionedBlock" | 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." From d2ac82f59ec8754389c50066c412189c476662be Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Wed, 24 Jun 2020 14:47:26 -0700 Subject: [PATCH 09/39] Workaround to get diagnostics closer to the right place --- .../CompilationManager/TypeChecking.cs | 5 +- .../CapabilityVerificationTests.fs | 49 +++++++++++-------- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/src/QsCompiler/CompilationManager/TypeChecking.cs b/src/QsCompiler/CompilationManager/TypeChecking.cs index cc2ebf3dc6..f29ce88d21 100644 --- a/src/QsCompiler/CompilationManager/TypeChecking.cs +++ b/src/QsCompiler/CompilationManager/TypeChecking.cs @@ -742,6 +742,9 @@ private static bool TryBuildIfStatement(IEnumerator nodes if (nodes.Current.Fragment.Kind is QsFragmentKind.IfClause ifCond) { + // TODO: This isn't the right way... + var startOffset = nodes.Current.GetRootPosition(); + // if block var buildClause = BuildStatement(nodes.Current, (relPos, ctx) => Statements.NewConditionalBlock(nodes.Current.Fragment.Comments, relPos, ctx, ifCond.Item), @@ -775,7 +778,7 @@ private static bool TryBuildIfStatement(IEnumerator nodes context, ifBlock.ToValueTuple(), elifBlocks, elseBlock); statement = ifStatement; diagnostics.AddRange(ifDiagnostics.Select(diagnostic => Diagnostics.Generate( - context.Symbols.SourceFile.Value, diagnostic, nodes.Current.GetRootPosition()))); + context.Symbols.SourceFile.Value, diagnostic, startOffset))); return true; } (statement, proceed) = (null, true); diff --git a/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs b/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs index d5ef6ca0b3..773c8a1468 100644 --- a/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs +++ b/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs @@ -32,7 +32,7 @@ let private testName name = let private allows (tester : CompilerTests) name = tester.Verify (testName name, List.empty) /// Asserts that the tester does not allow the given test case. -let private restricts (tester : CompilerTests) errorCode name = tester.Verify (testName name, [Error errorCode]) +let private restricts (tester : CompilerTests) errorCodes name = tester.Verify (testName name, Seq.map Error errorCodes) /// The names of all test cases. let private all = @@ -59,49 +59,58 @@ let [] ``Unknown allows all Result comparison`` () = List.iter (allows unk [] let ``QPRGen0 restricts all Result comparison`` () = - List.iter (restricts gen0 ErrorCode.UnsupportedResultComparison) all + List.iter (restricts gen0 [ErrorCode.UnsupportedResultComparison]) all [] let ``QPRGen1 restricts Result comparison in functions`` () = - restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ResultAsBool" - restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ResultAsBoolNeq" + [ "ResultAsBool" + "ResultAsBoolNeq" ] + |> List.iter (restricts gen1 [ErrorCode.ResultComparisonNotInOperationIf]) [] let ``QPRGen1 restricts non-if Result comparison in operations`` () = - restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ResultAsBoolOp" - restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "ResultAsBoolNeqOp" + [ "ResultAsBoolOp" + "ResultAsBoolNeqOp" ] + |> List.iter (restricts gen1 [ErrorCode.ResultComparisonNotInOperationIf]) [] let ``QPRGen1 restricts return from Result if`` () = - restricts gen1 ErrorCode.ReturnInResultConditionedBlock "ResultAsBoolOpReturnIf" - restricts gen1 ErrorCode.ReturnInResultConditionedBlock "ResultAsBoolNeqOpReturnIf" + [ "ResultAsBoolOpReturnIf" + "ResultAsBoolNeqOpReturnIf" ] + |> List.iter (restricts gen1 <| Seq.replicate 2 ErrorCode.ReturnInResultConditionedBlock) [] let ``QPRGen1 restricts mutable set from Result if`` () = - restricts gen1 ErrorCode.SetInResultConditionedBlock "ResultAsBoolOpSetIf" - restricts gen1 ErrorCode.SetInResultConditionedBlock "ResultAsBoolNeqOpSetIf" + [ "ResultAsBoolOpSetIf" + "ResultAsBoolNeqOpSetIf" ] + |> List.iter (restricts gen1 [ErrorCode.SetInResultConditionedBlock]) [] let ``QPRGen1 restricts mutable set from Result elif`` () = - restricts gen1 ErrorCode.SetInResultConditionedBlock "ElifSet" - restricts gen1 ErrorCode.SetInResultConditionedBlock "ElifElifSet" + [ "ElifSet" + "ElifElifSet" ] + |> List.iter (restricts gen1 [ErrorCode.SetInResultConditionedBlock]) [] let ``QPRGen1 restricts mutable set from Result else`` () = - restricts gen1 ErrorCode.SetInResultConditionedBlock "ResultAsBoolOpElseSet" - restricts gen1 ErrorCode.SetInResultConditionedBlock "ElifElseSet" + [ "ResultAsBoolOpElseSet" + "ElifElseSet" ] + |> List.iter (restricts gen1 [ErrorCode.SetInResultConditionedBlock]) [] let ``QPRGen1 restricts empty Result if function`` () = - restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "EmptyIf" - restricts gen1 ErrorCode.ResultComparisonNotInOperationIf "EmptyIfNeq" + [ "EmptyIf" + "EmptyIfNeq" ] + |> List.iter (restricts gen1 [ErrorCode.ResultComparisonNotInOperationIf]) [] let ``QPRGen1 allows empty Result if operation`` () = - allows gen1 "EmptyIfOp" - allows gen1 "EmptyIfNeqOp" + [ "EmptyIfOp" + "EmptyIfNeqOp" ] + |> List.iter (allows gen1) [] let ``QPRGen1 allows operation call from Result if`` () = - allows gen1 "Reset" - allows gen1 "ResetNeq" + [ "Reset" + "ResetNeq" ] + |> List.iter (allows gen1) From 8071f7ea2a1907a5bc0311a445216b288b87bfab Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Wed, 24 Jun 2020 16:25:56 -0700 Subject: [PATCH 10/39] Rename startOffset to rootPosition --- src/QsCompiler/CompilationManager/DataStructures.cs | 5 +++++ src/QsCompiler/CompilationManager/TypeChecking.cs | 5 ++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/QsCompiler/CompilationManager/DataStructures.cs b/src/QsCompiler/CompilationManager/DataStructures.cs index 868f958532..552bdf473f 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 nodes are relative to. + /// public Position GetRootPosition() => rootPosition.Copy(); + public Position GetPositionRelativeToRoot() => relPosition.Copy(); /// diff --git a/src/QsCompiler/CompilationManager/TypeChecking.cs b/src/QsCompiler/CompilationManager/TypeChecking.cs index f29ce88d21..25e6b2f1e8 100644 --- a/src/QsCompiler/CompilationManager/TypeChecking.cs +++ b/src/QsCompiler/CompilationManager/TypeChecking.cs @@ -742,8 +742,7 @@ private static bool TryBuildIfStatement(IEnumerator nodes if (nodes.Current.Fragment.Kind is QsFragmentKind.IfClause ifCond) { - // TODO: This isn't the right way... - var startOffset = nodes.Current.GetRootPosition(); + var rootPosition = nodes.Current.GetRootPosition(); // if block var buildClause = BuildStatement(nodes.Current, @@ -778,7 +777,7 @@ private static bool TryBuildIfStatement(IEnumerator nodes context, ifBlock.ToValueTuple(), elifBlocks, elseBlock); statement = ifStatement; diagnostics.AddRange(ifDiagnostics.Select(diagnostic => Diagnostics.Generate( - context.Symbols.SourceFile.Value, diagnostic, startOffset))); + context.Symbols.SourceFile.Value, diagnostic, rootPosition))); return true; } (statement, proceed) = (null, true); From 4f6789fea2513536796580bbbbc68c1c0612d4a7 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Wed, 24 Jun 2020 17:18:42 -0700 Subject: [PATCH 11/39] Add test for return in nested scope --- .../CapabilityVerificationTests.fs | 1 + .../TestCases/CapabilityVerification.qs | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs b/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs index 773c8a1468..9b8ad7fdc0 100644 --- a/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs +++ b/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs @@ -76,6 +76,7 @@ let ``QPRGen1 restricts non-if Result comparison in operations`` () = [] let ``QPRGen1 restricts return from Result if`` () = [ "ResultAsBoolOpReturnIf" + "ResultAsBoolOpReturnIfNested" "ResultAsBoolNeqOpReturnIf" ] |> List.iter (restricts gen1 <| Seq.replicate 2 ErrorCode.ReturnInResultConditionedBlock) diff --git a/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs b/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs index 733211ebf4..fa76053bd8 100644 --- a/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs +++ b/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs @@ -45,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) { From 0c9d67fdf033e922fbbd1ffa6913d173bbde7af2 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Wed, 24 Jun 2020 17:51:27 -0700 Subject: [PATCH 12/39] Replace FindStatements transformation with private function --- .../SyntaxProcessor/StatementVerification.fs | 28 ++++++++++----- src/QsCompiler/Transformations/NodeSearch.cs | 35 ------------------- 2 files changed, 19 insertions(+), 44 deletions(-) diff --git a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs index ee95808e62..9436ef1ea1 100644 --- a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs @@ -17,11 +17,25 @@ open Microsoft.Quantum.QsCompiler.SyntaxProcessing.VerificationTools open Microsoft.Quantum.QsCompiler.SyntaxTokens open Microsoft.Quantum.QsCompiler.SyntaxTree open Microsoft.Quantum.QsCompiler.Transformations +open Microsoft.Quantum.QsCompiler.Transformations.Core open Microsoft.Quantum.QsCompiler.Transformations.SearchAndReplace // some utils for type checking statements +/// Returns all statements matching the statement kind predicate. +let private findStatements predicate statements = + let mutable matches = [] + let transformation = + { new StatementTransformation(TransformationOptions.NoRebuild) with + override this.OnStatement statement = + if predicate statement.Statement then + matches <- statement :: matches + base.OnStatement statement + } + Seq.iter (transformation.OnStatement >> ignore) statements + matches + /// Given a verification function that takes an error logging function as well as an expression type and its range, /// first resolves the given expression, and then verifies its resolved type with the verification function. /// Returns the typed expression built upon resolution, the return value of the verification function, @@ -263,14 +277,10 @@ let private isResultComparison ({ Expression = expr } : TypedExpression) = | NEQ (lhs, rhs) -> bothResult lhs rhs | _ -> false -let private isReturnStatement { Statement = statement } = - match statement with - | QsReturnStatement -> true - | _ -> false - let private verifyResultConditionalBlocks (blocks : (TypedExpression * QsPositionedBlock) seq) = - let returnDiagnostics (block : QsPositionedBlock) = - FindStatements.Apply (Func<_, _> isReturnStatement, block.Body) + let returns (block : QsPositionedBlock) = + block.Body.Statements + |> findStatements (function | QsReturnStatement -> true | _ -> false) |> Seq.map (fun statement -> QsCompilerDiagnostic.Error (ErrorCode.ReturnInResultConditionedBlock, []) @@ -278,7 +288,7 @@ let private verifyResultConditionalBlocks (blocks : (TypedExpression * QsPositio |> QsNullable<_>.Map (fun location -> location.Range) |> (fun nullable -> nullable.ValueOr QsCompilerDiagnostic.DefaultRange))) - let setDiagnostics (block : QsPositionedBlock) = + let reassignments (block : QsPositionedBlock) = UpdatedOutsideVariables.Apply block.Body |> Seq.map (fun variable -> QsCompilerDiagnostic.Error @@ -290,7 +300,7 @@ let private verifyResultConditionalBlocks (blocks : (TypedExpression * QsPositio let newFoundResultEq = foundResultEq || FindExpressions.Contains (Func<_, _> isResultComparison, condition) let newDiagnostics = if newFoundResultEq - then Seq.concat [returnDiagnostics block; setDiagnostics block; diagnostics] + then Seq.concat [returns block; reassignments block; diagnostics] else diagnostics (newFoundResultEq, newDiagnostics)) |> snd diff --git a/src/QsCompiler/Transformations/NodeSearch.cs b/src/QsCompiler/Transformations/NodeSearch.cs index 3d55c43b36..7feb430380 100644 --- a/src/QsCompiler/Transformations/NodeSearch.cs +++ b/src/QsCompiler/Transformations/NodeSearch.cs @@ -49,41 +49,6 @@ public override TypedExpression OnTypedExpression(TypedExpression expression) } } - public static class FindStatements - { - public static IList Apply(Func predicate, QsScope scope) - { - var transformation = new Transformation(new State(predicate)); - transformation.OnScope(scope); - return transformation.SharedState.Statements; - } - - private sealed class State - { - internal Func Predicate { get; } - - internal IList Statements { get; } = new List(); - - internal State(Func predicate) => Predicate = predicate; - } - - private sealed class Transformation : StatementTransformation - { - internal Transformation(State state) : base(state, TransformationOptions.NoRebuild) - { - } - - public override QsStatement OnStatement(QsStatement statement) - { - if (SharedState.Predicate(statement)) - { - SharedState.Statements.Add(statement); - } - return base.OnStatement(statement); - } - } - } - public static class UpdatedOutsideVariables { public static List Apply(QsScope scope) From 85c630a045a23dbc0029c5702c0811d87c858cf8 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Wed, 24 Jun 2020 18:04:09 -0700 Subject: [PATCH 13/39] Combine Offset and Range for return statements --- src/QsCompiler/SyntaxProcessor/StatementVerification.fs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs index 9436ef1ea1..d683b2ef98 100644 --- a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs @@ -285,7 +285,13 @@ let private verifyResultConditionalBlocks (blocks : (TypedExpression * QsPositio QsCompilerDiagnostic.Error (ErrorCode.ReturnInResultConditionedBlock, []) (statement.Location - |> QsNullable<_>.Map (fun location -> location.Range) + |> QsNullable<_>.Map (fun location -> + let a, b = location.Range + let newA = { a with Line = a.Line + fst location.Offset + Column = a.Column + snd location.Offset } + let newB = { b with Line = b.Line + fst location.Offset + Column = b.Column + snd location.Offset } + newA, newB) |> (fun nullable -> nullable.ValueOr QsCompilerDiagnostic.DefaultRange))) let reassignments (block : QsPositionedBlock) = From 51be79a5d3704a93ecc429fa749f147bff1cd5e0 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Wed, 24 Jun 2020 19:59:33 -0700 Subject: [PATCH 14/39] Add QsStatement.RangeRelativeToRoot --- .../SyntaxProcessor/StatementVerification.fs | 33 +++++++------------ .../SyntaxProcessor/VerificationTools.fs | 16 ++++++++- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs index d683b2ef98..c8d4d0ccb0 100644 --- a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs @@ -278,35 +278,26 @@ let private isResultComparison ({ Expression = expr } : TypedExpression) = | _ -> false let private verifyResultConditionalBlocks (blocks : (TypedExpression * QsPositionedBlock) seq) = - let returns (block : QsPositionedBlock) = + let returnError (statement : QsStatement) = + QsCompilerDiagnostic.Error + (ErrorCode.ReturnInResultConditionedBlock, []) + (statement.RangeRelativeToRoot.ValueOr QsCompilerDiagnostic.DefaultRange) + let returnErrors (block : QsPositionedBlock) = block.Body.Statements |> findStatements (function | QsReturnStatement -> true | _ -> false) - |> Seq.map (fun statement -> - QsCompilerDiagnostic.Error - (ErrorCode.ReturnInResultConditionedBlock, []) - (statement.Location - |> QsNullable<_>.Map (fun location -> - let a, b = location.Range - let newA = { a with Line = a.Line + fst location.Offset - Column = a.Column + snd location.Offset } - let newB = { b with Line = b.Line + fst location.Offset - Column = b.Column + snd location.Offset } - newA, newB) - |> (fun nullable -> nullable.ValueOr QsCompilerDiagnostic.DefaultRange))) - - let reassignments (block : QsPositionedBlock) = - UpdatedOutsideVariables.Apply block.Body - |> Seq.map (fun variable -> - QsCompilerDiagnostic.Error - (ErrorCode.SetInResultConditionedBlock, []) - (variable.Range.ValueOr QsCompilerDiagnostic.DefaultRange)) + |> Seq.map returnError + let setError (variable : TypedExpression) = + QsCompilerDiagnostic.Error + (ErrorCode.SetInResultConditionedBlock, []) + (variable.Range.ValueOr QsCompilerDiagnostic.DefaultRange) + let setErrors (block : QsPositionedBlock) = Seq.map setError (UpdatedOutsideVariables.Apply block.Body) ((false, Seq.empty), blocks) ||> Seq.fold (fun (foundResultEq, diagnostics) (condition, block) -> let newFoundResultEq = foundResultEq || FindExpressions.Contains (Func<_, _> isResultComparison, condition) let newDiagnostics = if newFoundResultEq - then Seq.concat [returns block; reassignments block; diagnostics] + then Seq.concat [returnErrors block; setErrors block; diagnostics] else diagnostics (newFoundResultEq, newDiagnostics)) |> snd diff --git a/src/QsCompiler/SyntaxProcessor/VerificationTools.fs b/src/QsCompiler/SyntaxProcessor/VerificationTools.fs index 4407a9f73e..ea682c5096 100644 --- a/src/QsCompiler/SyntaxProcessor/VerificationTools.fs +++ b/src/QsCompiler/SyntaxProcessor/VerificationTools.fs @@ -108,4 +108,18 @@ type ResolvedType with | ArrayType bt -> Some bt | _ -> None - +/// Converts the range relative to the QsLocation's offset into a range relative to the root node. +let private rangeRelativeToRoot (location : QsLocation) = + let line, column = location.Offset + let start, finish = location.Range + let startFromRoot = + { Line = start.Line + line + Column = start.Column + column } + let finishFromRoot = + { Line = finish.Line + line + Column = if start.Line = finish.Line then finish.Column + column else finish.Column } + startFromRoot, finishFromRoot + +type QsStatement with + /// The range of this statement relative to its root node. + member this.RangeRelativeToRoot = QsNullable<_>.Map rangeRelativeToRoot this.Location From 59cbfc4c3cc26d39b13263376ad62c031e379a0d Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Wed, 24 Jun 2020 21:00:24 -0700 Subject: [PATCH 15/39] Clean up verification --- .../SyntaxProcessor/StatementVerification.fs | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs index c8d4d0ccb0..01edfb0fe1 100644 --- a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs @@ -277,30 +277,29 @@ let private isResultComparison ({ Expression = expr } : TypedExpression) = | NEQ (lhs, rhs) -> bothResult lhs rhs | _ -> false -let private verifyResultConditionalBlocks (blocks : (TypedExpression * QsPositionedBlock) seq) = +/// 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 capabilities (blocks : (TypedExpression * QsPositionedBlock) seq) = let returnError (statement : QsStatement) = QsCompilerDiagnostic.Error (ErrorCode.ReturnInResultConditionedBlock, []) (statement.RangeRelativeToRoot.ValueOr QsCompilerDiagnostic.DefaultRange) - let returnErrors (block : QsPositionedBlock) = - block.Body.Statements - |> findStatements (function | QsReturnStatement -> true | _ -> false) - |> Seq.map returnError let setError (variable : TypedExpression) = QsCompilerDiagnostic.Error (ErrorCode.SetInResultConditionedBlock, []) (variable.Range.ValueOr QsCompilerDiagnostic.DefaultRange) + let returnErrors (block : QsPositionedBlock) = + block.Body.Statements + |> findStatements (function | QsReturnStatement -> true | _ -> false) + |> Seq.map returnError let setErrors (block : QsPositionedBlock) = Seq.map setError (UpdatedOutsideVariables.Apply block.Body) - - ((false, Seq.empty), blocks) - ||> Seq.fold (fun (foundResultEq, diagnostics) (condition, block) -> - let newFoundResultEq = foundResultEq || FindExpressions.Contains (Func<_, _> isResultComparison, condition) - let newDiagnostics = - if newFoundResultEq - then Seq.concat [returnErrors block; setErrors block; diagnostics] - else diagnostics - (newFoundResultEq, newDiagnostics)) - |> snd + let folder (dependsOnResult, diagnostics) (condition, block) = + if dependsOnResult || FindExpressions.Contains (Func<_, _> isResultComparison, condition) + then true, Seq.concat [returnErrors block; setErrors block; diagnostics] + else false, diagnostics + if capabilities = RuntimeCapabilities.QPRGen1 + then Seq.fold folder (false, Seq.empty) blocks |> snd + else Seq.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. @@ -308,22 +307,24 @@ let private verifyResultConditionalBlocks (blocks : (TypedExpression * QsPositio let NewIfStatement context (ifBlock : struct (TypedExpression * QsPositionedBlock)) elifBlocks elseBlock = let location = match (ifBlock.ToTuple() |> snd).Location with - | Null -> ArgumentException "no location is set for the given if-block" |> raise - | Value loc -> loc + | Null -> ArgumentException "No location is set for the given if-block." |> raise + | Value location -> location + + // A sequence of the blocks that have a conditional expression. let condBlocks = Seq.append (ifBlock.ToTuple() |> Seq.singleton) elifBlocks + + // A sequence of all the blocks, with the final else-block treated as an elif-block with an always-true conditional + // expression. let allBlocks = match elseBlock with - | Value block -> Seq.append condBlocks (Seq.singleton (SyntaxGenerator.BoolLiteral true, block)) + | Value block -> (SyntaxGenerator.BoolLiteral true, block) |> Seq.singleton |> Seq.append condBlocks | Null -> condBlocks - let diagnostics = - if context.Capabilities = RuntimeCapabilities.QPRGen1 - then verifyResultConditionalBlocks allBlocks - else Seq.empty + let statement = QsConditionalStatement.New (condBlocks, elseBlock) |> QsConditionalStatement |> asStatement QsComments.Empty location LocalDeclarations.Empty - statement, diagnostics + statement, verifyResultConditionalBlocks context.Capabilities allBlocks /// 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. From 1834f33e74761f673aa17f0445cc6b3fbfcae73c Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Thu, 25 Jun 2020 22:01:32 -0700 Subject: [PATCH 16/39] Use _ for discarded case value --- src/QsCompiler/SyntaxProcessor/StatementVerification.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs index 01edfb0fe1..5ec97642c1 100644 --- a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs @@ -290,7 +290,7 @@ let private verifyResultConditionalBlocks capabilities (blocks : (TypedExpressio (variable.Range.ValueOr QsCompilerDiagnostic.DefaultRange) let returnErrors (block : QsPositionedBlock) = block.Body.Statements - |> findStatements (function | QsReturnStatement -> true | _ -> false) + |> findStatements (function | QsReturnStatement _ -> true | _ -> false) |> Seq.map returnError let setErrors (block : QsPositionedBlock) = Seq.map setError (UpdatedOutsideVariables.Apply block.Body) let folder (dependsOnResult, diagnostics) (condition, block) = From 568f7869bf56fd5bc70295649d6056430200d1cb Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Thu, 25 Jun 2020 22:46:10 -0700 Subject: [PATCH 17/39] Use QsStatement.ExtractAll --- .../SyntaxProcessor/StatementVerification.fs | 41 +++++++------------ 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs index 5ec97642c1..96feecd3a9 100644 --- a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs @@ -17,25 +17,11 @@ open Microsoft.Quantum.QsCompiler.SyntaxProcessing.VerificationTools open Microsoft.Quantum.QsCompiler.SyntaxTokens open Microsoft.Quantum.QsCompiler.SyntaxTree open Microsoft.Quantum.QsCompiler.Transformations -open Microsoft.Quantum.QsCompiler.Transformations.Core open Microsoft.Quantum.QsCompiler.Transformations.SearchAndReplace // some utils for type checking statements -/// Returns all statements matching the statement kind predicate. -let private findStatements predicate statements = - let mutable matches = [] - let transformation = - { new StatementTransformation(TransformationOptions.NoRebuild) with - override this.OnStatement statement = - if predicate statement.Statement then - matches <- statement :: matches - base.OnStatement statement - } - Seq.iter (transformation.OnStatement >> ignore) statements - matches - /// Given a verification function that takes an error logging function as well as an expression type and its range, /// first resolves the given expression, and then verifies its resolved type with the verification function. /// Returns the typed expression built upon resolution, the return value of the verification function, @@ -280,25 +266,28 @@ let private isResultComparison ({ Expression = expr } : TypedExpression) = /// 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 capabilities (blocks : (TypedExpression * QsPositionedBlock) seq) = + // Diagnostics for return statements. + let returnStatements (statement : QsStatement) = + statement.ExtractAll (fun s -> match s.Statement with QsReturnStatement _ -> [s] | _ -> []) let returnError (statement : QsStatement) = - QsCompilerDiagnostic.Error - (ErrorCode.ReturnInResultConditionedBlock, []) - (statement.RangeRelativeToRoot.ValueOr QsCompilerDiagnostic.DefaultRange) - let setError (variable : TypedExpression) = - QsCompilerDiagnostic.Error - (ErrorCode.SetInResultConditionedBlock, []) - (variable.Range.ValueOr QsCompilerDiagnostic.DefaultRange) + QsCompilerDiagnostic.Error (ErrorCode.ReturnInResultConditionedBlock, []) + (statement.RangeRelativeToRoot.ValueOr QsCompilerDiagnostic.DefaultRange) let returnErrors (block : QsPositionedBlock) = - block.Body.Statements - |> findStatements (function | QsReturnStatement _ -> true | _ -> false) - |> Seq.map returnError + block.Body.Statements |> Seq.collect returnStatements |> Seq.map returnError + + // Diagnostics for variable reassignments. + let setError (variable : TypedExpression) = + // TODO: Range information is wrong. + QsCompilerDiagnostic.Error (ErrorCode.SetInResultConditionedBlock, []) + (variable.Range.ValueOr QsCompilerDiagnostic.DefaultRange) let setErrors (block : QsPositionedBlock) = Seq.map setError (UpdatedOutsideVariables.Apply block.Body) - let folder (dependsOnResult, diagnostics) (condition, block) = + + let accumulateErrors (dependsOnResult, diagnostics) (condition, block) = if dependsOnResult || FindExpressions.Contains (Func<_, _> isResultComparison, condition) then true, Seq.concat [returnErrors block; setErrors block; diagnostics] else false, diagnostics if capabilities = RuntimeCapabilities.QPRGen1 - then Seq.fold folder (false, Seq.empty) blocks |> snd + then Seq.fold accumulateErrors (false, Seq.empty) blocks |> snd else Seq.empty /// Given a conditional block for the if-clause of a Q# if-statement, a sequence of conditional blocks for the elif-clauses, From 2ecba919295c6eaaafc06b027b6323263b2e7c19 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Thu, 25 Jun 2020 23:03:12 -0700 Subject: [PATCH 18/39] Use TypedExpression.Exists --- .../SyntaxProcessor/StatementVerification.fs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs index 96feecd3a9..cb2d4fac6b 100644 --- a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs @@ -267,8 +267,10 @@ let private isResultComparison ({ Expression = expr } : TypedExpression) = /// are not supported by the runtime capabilities. Returns the diagnostics for the blocks. let private verifyResultConditionalBlocks capabilities (blocks : (TypedExpression * QsPositionedBlock) seq) = // Diagnostics for return statements. - let returnStatements (statement : QsStatement) = - statement.ExtractAll (fun s -> match s.Statement with QsReturnStatement _ -> [s] | _ -> []) + let returnStatements (statement : QsStatement) = statement.ExtractAll <| fun s -> + match s.Statement with + | QsReturnStatement _ -> [s] + | _ -> [] let returnError (statement : QsStatement) = QsCompilerDiagnostic.Error (ErrorCode.ReturnInResultConditionedBlock, []) (statement.RangeRelativeToRoot.ValueOr QsCompilerDiagnostic.DefaultRange) @@ -282,8 +284,8 @@ let private verifyResultConditionalBlocks capabilities (blocks : (TypedExpressio (variable.Range.ValueOr QsCompilerDiagnostic.DefaultRange) let setErrors (block : QsPositionedBlock) = Seq.map setError (UpdatedOutsideVariables.Apply block.Body) - let accumulateErrors (dependsOnResult, diagnostics) (condition, block) = - if dependsOnResult || FindExpressions.Contains (Func<_, _> isResultComparison, condition) + let accumulateErrors (dependsOnResult, diagnostics) (condition : TypedExpression, block) = + if dependsOnResult || condition.Exists isResultComparison then true, Seq.concat [returnErrors block; setErrors block; diagnostics] else false, diagnostics if capabilities = RuntimeCapabilities.QPRGen1 From 92c63c87d07ef8e17275a6855c0bf56afdb5619e Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Fri, 26 Jun 2020 00:04:57 -0700 Subject: [PATCH 19/39] Add new test cases, currently failing --- .../CapabilityVerificationTests.fs | 11 +++++++--- .../TestCases/CapabilityVerification.qs | 21 +++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs b/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs index 9b8ad7fdc0..0e002e3599 100644 --- a/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs +++ b/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs @@ -81,19 +81,24 @@ let ``QPRGen1 restricts return from Result if`` () = |> List.iter (restricts gen1 <| Seq.replicate 2 ErrorCode.ReturnInResultConditionedBlock) [] -let ``QPRGen1 restricts mutable set from Result if`` () = +let ``QPRGen1 allows local mutable set from Result if`` () = + allows gen1 "SetLocal" + +[] +let ``QPRGen1 restricts non-local mutable set from Result if`` () = [ "ResultAsBoolOpSetIf" "ResultAsBoolNeqOpSetIf" ] |> List.iter (restricts gen1 [ErrorCode.SetInResultConditionedBlock]) + restricts gen1 [ErrorCode.LocalVariableAlreadyExists; ErrorCode.SetInResultConditionedBlock] "SetReusedName" [] -let ``QPRGen1 restricts mutable set from Result elif`` () = +let ``QPRGen1 restricts non-local mutable set from Result elif`` () = [ "ElifSet" "ElifElifSet" ] |> List.iter (restricts gen1 [ErrorCode.SetInResultConditionedBlock]) [] -let ``QPRGen1 restricts mutable set from Result else`` () = +let ``QPRGen1 restricts non-local mutable set from Result else`` () = [ "ResultAsBoolOpElseSet" "ElifElseSet" ] |> List.iter (restricts gen1 [ErrorCode.SetInResultConditionedBlock]) diff --git a/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs b/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs index fa76053bd8..eea282c7e9 100644 --- a/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs +++ b/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs @@ -125,6 +125,27 @@ namespace Microsoft.Quantum.Testing.CapabilityVerification { 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) { + // Redeclaring b is an error, but it should not mask the error below. + mutable b = false; + // This should not be a capability error. + set b = true; + } + // This should be a capability error. + set b = true; + } + } + function EmptyIf(result : Result) : Unit { if (result == Zero) { } } From 1807c594528b0a7e43118c11357d3dbacbb33440 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Fri, 26 Jun 2020 17:32:44 -0700 Subject: [PATCH 20/39] Update mutable set verification --- .../SyntaxProcessor/StatementVerification.fs | 31 ++++-- .../SyntaxProcessor/VerificationTools.fs | 2 +- .../CapabilityVerificationTests.fs | 3 +- .../TestCases/CapabilityVerification.qs | 3 - src/QsCompiler/Transformations/NodeSearch.cs | 101 ------------------ 5 files changed, 25 insertions(+), 115 deletions(-) delete mode 100644 src/QsCompiler/Transformations/NodeSearch.cs diff --git a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs index cb2d4fac6b..623bbcbbfa 100644 --- a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs @@ -16,7 +16,6 @@ open Microsoft.Quantum.QsCompiler.SyntaxProcessing.Expressions open Microsoft.Quantum.QsCompiler.SyntaxProcessing.VerificationTools open Microsoft.Quantum.QsCompiler.SyntaxTokens open Microsoft.Quantum.QsCompiler.SyntaxTree -open Microsoft.Quantum.QsCompiler.Transformations open Microsoft.Quantum.QsCompiler.Transformations.SearchAndReplace @@ -263,9 +262,25 @@ let private isResultComparison ({ Expression = expr } : TypedExpression) = | NEQ (lhs, rhs) -> bothResult lhs rhs | _ -> false +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 ignoreDiagnostics _ = () + let symbol = { Symbol = Symbol name + Range = Value location.Range } + match (symbols.ResolveIdentifier ignoreDiagnostics 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 capabilities (blocks : (TypedExpression * QsPositionedBlock) seq) = +let private verifyResultConditionalBlocks context (blocks : (TypedExpression * QsPositionedBlock) seq) = // Diagnostics for return statements. let returnStatements (statement : QsStatement) = statement.ExtractAll <| fun s -> match s.Statement with @@ -278,17 +293,15 @@ let private verifyResultConditionalBlocks capabilities (blocks : (TypedExpressio block.Body.Statements |> Seq.collect returnStatements |> Seq.map returnError // Diagnostics for variable reassignments. - let setError (variable : TypedExpression) = - // TODO: Range information is wrong. - QsCompilerDiagnostic.Error (ErrorCode.SetInResultConditionedBlock, []) - (variable.Range.ValueOr QsCompilerDiagnostic.DefaultRange) - let setErrors (block : QsPositionedBlock) = Seq.map setError (UpdatedOutsideVariables.Apply block.Body) + let setError (name, location) = + QsCompilerDiagnostic.Error (ErrorCode.SetInResultConditionedBlock, []) (rangeRelativeToRoot location) + let setErrors (block : QsPositionedBlock) = nonLocalUpdates context.Symbols block.Body |> Seq.map setError let accumulateErrors (dependsOnResult, diagnostics) (condition : TypedExpression, block) = if dependsOnResult || condition.Exists isResultComparison then true, Seq.concat [returnErrors block; setErrors block; diagnostics] else false, diagnostics - if capabilities = RuntimeCapabilities.QPRGen1 + if context.Capabilities = RuntimeCapabilities.QPRGen1 then Seq.fold accumulateErrors (false, Seq.empty) blocks |> snd else Seq.empty @@ -315,7 +328,7 @@ let NewIfStatement context (ifBlock : struct (TypedExpression * QsPositionedBloc QsConditionalStatement.New (condBlocks, elseBlock) |> QsConditionalStatement |> asStatement QsComments.Empty location LocalDeclarations.Empty - statement, verifyResultConditionalBlocks context.Capabilities allBlocks + statement, verifyResultConditionalBlocks context allBlocks /// 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 ea682c5096..631a489a73 100644 --- a/src/QsCompiler/SyntaxProcessor/VerificationTools.fs +++ b/src/QsCompiler/SyntaxProcessor/VerificationTools.fs @@ -109,7 +109,7 @@ type ResolvedType with | _ -> None /// Converts the range relative to the QsLocation's offset into a range relative to the root node. -let private rangeRelativeToRoot (location : QsLocation) = +let internal rangeRelativeToRoot (location : QsLocation) = let line, column = location.Offset let start, finish = location.Range let startFromRoot = diff --git a/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs b/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs index 0e002e3599..d6a9dfdd67 100644 --- a/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs +++ b/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs @@ -89,7 +89,8 @@ let ``QPRGen1 restricts non-local mutable set from Result if`` () = [ "ResultAsBoolOpSetIf" "ResultAsBoolNeqOpSetIf" ] |> List.iter (restricts gen1 [ErrorCode.SetInResultConditionedBlock]) - restricts gen1 [ErrorCode.LocalVariableAlreadyExists; ErrorCode.SetInResultConditionedBlock] "SetReusedName" + "SetReusedName" + |> restricts gen1 (ErrorCode.LocalVariableAlreadyExists :: List.replicate 2 ErrorCode.SetInResultConditionedBlock) [] let ``QPRGen1 restricts non-local mutable set from Result elif`` () = diff --git a/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs b/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs index eea282c7e9..88721e5b4a 100644 --- a/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs +++ b/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs @@ -136,12 +136,9 @@ namespace Microsoft.Quantum.Testing.CapabilityVerification { mutable b = false; if (result == One) { if (true) { - // Redeclaring b is an error, but it should not mask the error below. mutable b = false; - // This should not be a capability error. set b = true; } - // This should be a capability error. set b = true; } } diff --git a/src/QsCompiler/Transformations/NodeSearch.cs b/src/QsCompiler/Transformations/NodeSearch.cs deleted file mode 100644 index 7feb430380..0000000000 --- a/src/QsCompiler/Transformations/NodeSearch.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.Quantum.QsCompiler.DataTypes; -using Microsoft.Quantum.QsCompiler.SyntaxTokens; -using Microsoft.Quantum.QsCompiler.SyntaxTree; -using Microsoft.Quantum.QsCompiler.Transformations.Core; - -namespace Microsoft.Quantum.QsCompiler.Transformations -{ - using QsRangeInfo = QsNullable>; - using QsExpressionKind = QsExpressionKind; - - public static class FindExpressions - { - public static IList Find(Func predicate, TypedExpression expression) - { - var transformation = new Transformation(new State(predicate)); - transformation.OnTypedExpression(expression); - return transformation.SharedState.Expressions; - } - - public static bool Contains(Func predicate, TypedExpression expression) => - Find(predicate, expression).Any(); - - private sealed class State - { - internal Func Predicate { get; } - - internal IList Expressions { get; } = new List(); - - internal State(Func predicate) => Predicate = predicate; - } - - private sealed class Transformation : ExpressionTransformation - { - internal Transformation(State state) : base(state, TransformationOptions.NoRebuild) - { - } - - public override TypedExpression OnTypedExpression(TypedExpression expression) - { - if (SharedState.Predicate(expression)) - { - SharedState.Expressions.Add(expression); - } - return base.OnTypedExpression(expression); - } - } - } - - public static class UpdatedOutsideVariables - { - public static List Apply(QsScope scope) - { - var transformation = new SyntaxTreeTransformation(new State(), TransformationOptions.NoRebuild); - transformation.StatementKinds = new StatementKindTransformation(transformation); - transformation.Statements.OnScope(scope); - return transformation.SharedState.UpdatedOutsideVariables; - } - - private static IEnumerable Symbols(SymbolTuple tuple) => tuple switch - { - SymbolTuple.VariableName name => Enumerable.Repeat(name.Item.Value, 1), - SymbolTuple.VariableNameTuple names => names.Item.SelectMany(Symbols), - _ => Enumerable.Empty() - }; - - private sealed class State - { - internal List DeclaredVariables { get; } = new List(); - - internal List UpdatedOutsideVariables = new List(); - } - - private sealed class StatementKindTransformation : StatementKindTransformation - { - internal StatementKindTransformation(SyntaxTreeTransformation parent) : base(parent) - { - } - - public override QsStatementKind OnVariableDeclaration(QsBinding binding) - { - SharedState.DeclaredVariables.AddRange(Symbols(binding.Lhs)); - return base.OnVariableDeclaration(binding); - } - - public override QsStatementKind OnValueUpdate(QsValueUpdate update) - { - var variables = FindExpressions.Find( - expr => - expr.Expression is QsExpressionKind.Identifier id && - id.Item1 is Identifier.LocalVariable local && - !SharedState.DeclaredVariables.Contains(local.Item.Value), - update.Lhs); - SharedState.UpdatedOutsideVariables.AddRange(variables); - return base.OnValueUpdate(update); - } - } - } -} From 5a1930a15ea635a607f75dc88a564a52576b3df2 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Fri, 26 Jun 2020 17:38:47 -0700 Subject: [PATCH 21/39] Update doc comment for TreeNode.GetRootPosition --- src/QsCompiler/CompilationManager/DataStructures.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/QsCompiler/CompilationManager/DataStructures.cs b/src/QsCompiler/CompilationManager/DataStructures.cs index 552bdf473f..1f4964b3e9 100644 --- a/src/QsCompiler/CompilationManager/DataStructures.cs +++ b/src/QsCompiler/CompilationManager/DataStructures.cs @@ -328,7 +328,7 @@ internal struct TreeNode public readonly IReadOnlyList Children; /// - /// Returns the position of the root node that all child nodes are relative to. + /// Returns the position of the root node that all child node positions are relative to. /// public Position GetRootPosition() => rootPosition.Copy(); From 4922c33c1a86a04315ae4baad21a9091562041fd Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Fri, 26 Jun 2020 21:06:48 -0700 Subject: [PATCH 22/39] Move verification functions to the top --- .../SyntaxProcessor/StatementVerification.fs | 111 +++++++++--------- 1 file changed, 56 insertions(+), 55 deletions(-) diff --git a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs index 623bbcbbfa..1b561897e0 100644 --- a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs @@ -52,6 +52,60 @@ let private onAutoInvertGenerateError (errCode, range) (symbols : SymbolTracker< if not (symbols.RequiredFunctorSupport.Contains QsFunctor.Adjoint) then [||] else [| range |> QsCompilerDiagnostic.Error errCode |] +let private isResultComparison ({ Expression = expr } : TypedExpression) = + // TODO: Technically, it should be the common base type of LHS and RHS. + let bothResult lhs rhs = + match lhs.ResolvedType.Resolution, rhs.ResolvedType.Resolution with + | Result, Result -> true + | _ -> false + match expr with + | EQ (lhs, rhs) -> bothResult lhs rhs + | NEQ (lhs, rhs) -> bothResult lhs rhs + | _ -> 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 (blocks : (TypedExpression * QsPositionedBlock) seq) = + // Diagnostics for return statements. + let returnStatements (statement : QsStatement) = statement.ExtractAll <| fun s -> + match s.Statement with + | QsReturnStatement _ -> [s] + | _ -> [] + let returnError (statement : QsStatement) = + QsCompilerDiagnostic.Error (ErrorCode.ReturnInResultConditionedBlock, []) + (statement.RangeRelativeToRoot.ValueOr QsCompilerDiagnostic.DefaultRange) + let returnErrors (block : QsPositionedBlock) = + block.Body.Statements |> Seq.collect returnStatements |> Seq.map returnError + + // Diagnostics for variable reassignments. + let setError (name, location) = + QsCompilerDiagnostic.Error (ErrorCode.SetInResultConditionedBlock, []) (rangeRelativeToRoot location) + let setErrors (block : QsPositionedBlock) = nonLocalUpdates context.Symbols block.Body |> Seq.map setError + + let accumulateErrors (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 accumulateErrors (false, Seq.empty) blocks |> snd + else Seq.empty + // utils for building QsStatements from QsFragmentKinds @@ -251,60 +305,6 @@ let NewConditionalBlock comments location context (qsExpr : QsExpression) = let block body = condition, QsPositionedBlock.New comments (Value location) body new BlockStatement<_>(block), Array.concat [errs; autoGenErrs] -let private isResultComparison ({ Expression = expr } : TypedExpression) = - // TODO: Technically, it should be the common base type of LHS and RHS. - let bothResult lhs rhs = - match lhs.ResolvedType.Resolution, rhs.ResolvedType.Resolution with - | Result, Result -> true - | _ -> false - match expr with - | EQ (lhs, rhs) -> bothResult lhs rhs - | NEQ (lhs, rhs) -> bothResult lhs rhs - | _ -> false - -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 ignoreDiagnostics _ = () - let symbol = { Symbol = Symbol name - Range = Value location.Range } - match (symbols.ResolveIdentifier ignoreDiagnostics 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 (blocks : (TypedExpression * QsPositionedBlock) seq) = - // Diagnostics for return statements. - let returnStatements (statement : QsStatement) = statement.ExtractAll <| fun s -> - match s.Statement with - | QsReturnStatement _ -> [s] - | _ -> [] - let returnError (statement : QsStatement) = - QsCompilerDiagnostic.Error (ErrorCode.ReturnInResultConditionedBlock, []) - (statement.RangeRelativeToRoot.ValueOr QsCompilerDiagnostic.DefaultRange) - let returnErrors (block : QsPositionedBlock) = - block.Body.Statements |> Seq.collect returnStatements |> Seq.map returnError - - // Diagnostics for variable reassignments. - let setError (name, location) = - QsCompilerDiagnostic.Error (ErrorCode.SetInResultConditionedBlock, []) (rangeRelativeToRoot location) - let setErrors (block : QsPositionedBlock) = nonLocalUpdates context.Symbols block.Body |> Seq.map setError - - let accumulateErrors (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 accumulateErrors (false, Seq.empty) blocks |> snd - else Seq.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. @@ -328,7 +328,8 @@ let NewIfStatement context (ifBlock : struct (TypedExpression * QsPositionedBloc QsConditionalStatement.New (condBlocks, elseBlock) |> QsConditionalStatement |> asStatement QsComments.Empty location LocalDeclarations.Empty - statement, verifyResultConditionalBlocks context allBlocks + let diagnostics = verifyResultConditionalBlocks context allBlocks + 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. From 5a89f1d58ece0a94f02e48bb4c4ddbfd18946b4f Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Fri, 26 Jun 2020 21:21:52 -0700 Subject: [PATCH 23/39] Move else-block normalization to verify function --- src/QsCompiler/Core/SyntaxGenerator.fs | 2 ++ src/QsCompiler/DataStructures/DataTypes.fs | 6 ++++++ .../SyntaxProcessor/StatementVerification.fs | 19 +++++++------------ 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/QsCompiler/Core/SyntaxGenerator.fs b/src/QsCompiler/Core/SyntaxGenerator.fs index caef691121..e4d19a0070 100644 --- a/src/QsCompiler/Core/SyntaxGenerator.fs +++ b/src/QsCompiler/Core/SyntaxGenerator.fs @@ -76,6 +76,8 @@ module SyntaxGenerator = let UnitValue = AutoGeneratedExpression UnitValue QsTypeKind.UnitType false + /// Creates a typed expression that corresponds to an 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. diff --git a/src/QsCompiler/DataStructures/DataTypes.fs b/src/QsCompiler/DataStructures/DataTypes.fs index fee665728f..aac127a43c 100644 --- a/src/QsCompiler/DataStructures/DataTypes.fs +++ b/src/QsCompiler/DataStructures/DataTypes.fs @@ -53,6 +53,12 @@ type QsNullable<'T> = // to avoid having to include the F# core in the C# part o | Some v -> Value v | None -> Null + /// Converts the QsNullable to an F# option. + member this.ToOption = + match this with + | Value value -> Some value + | Null -> None + [] type NonNullable<'T> = private Item of 'T with diff --git a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs index 1b561897e0..5d85eb1812 100644 --- a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs @@ -81,7 +81,7 @@ let private nonLocalUpdates (symbols : SymbolTracker<_>) (scope : QsScope) = /// 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 (blocks : (TypedExpression * QsPositionedBlock) seq) = +let private verifyResultConditionalBlocks context condBlocks elseBlock = // Diagnostics for return statements. let returnStatements (statement : QsStatement) = statement.ExtractAll <| fun s -> match s.Statement with @@ -98,6 +98,11 @@ let private verifyResultConditionalBlocks context (blocks : (TypedExpression * Q QsCompilerDiagnostic.Error (ErrorCode.SetInResultConditionedBlock, []) (rangeRelativeToRoot location) let setErrors (block : QsPositionedBlock) = nonLocalUpdates context.Symbols block.Body |> Seq.map setError + let blocks = + elseBlock + |> Option.map (fun block -> SyntaxGenerator.BoolLiteral true, block) + |> Option.toList + |> Seq.append condBlocks let accumulateErrors (dependsOnResult, diagnostics) (condition : TypedExpression, block) = if dependsOnResult || condition.Exists isResultComparison then true, Seq.concat [returnErrors block; setErrors block; diagnostics] @@ -313,22 +318,12 @@ let NewIfStatement context (ifBlock : struct (TypedExpression * QsPositionedBloc match (ifBlock.ToTuple() |> snd).Location with | Null -> ArgumentException "No location is set for the given if-block." |> raise | Value location -> location - - // A sequence of the blocks that have a conditional expression. let condBlocks = Seq.append (ifBlock.ToTuple() |> Seq.singleton) elifBlocks - - // A sequence of all the blocks, with the final else-block treated as an elif-block with an always-true conditional - // expression. - let allBlocks = - match elseBlock with - | Value block -> (SyntaxGenerator.BoolLiteral true, block) |> Seq.singleton |> Seq.append condBlocks - | Null -> condBlocks - let statement = QsConditionalStatement.New (condBlocks, elseBlock) |> QsConditionalStatement |> asStatement QsComments.Empty location LocalDeclarations.Empty - let diagnostics = verifyResultConditionalBlocks context allBlocks + let diagnostics = verifyResultConditionalBlocks context condBlocks elseBlock.ToOption 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, From 58b58e39b48a8a17d403eca2506881e53cca9db5 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Mon, 29 Jun 2020 11:56:11 -0700 Subject: [PATCH 24/39] Use common base type in isResultComparison --- .../SyntaxProcessor/ExpressionVerification.fs | 2 +- .../SyntaxProcessor/StatementVerification.fs | 28 +++++++++++-------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs b/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs index df7a88aa4d..962860e398 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 internal 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 diff --git a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs index 5d85eb1812..39098e4e3a 100644 --- a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs @@ -52,15 +52,19 @@ let private onAutoInvertGenerateError (errCode, range) (symbols : SymbolTracker< if not (symbols.RequiredFunctorSupport.Contains QsFunctor.Adjoint) then [||] else [| range |> QsCompilerDiagnostic.Error errCode |] -let private isResultComparison ({ Expression = expr } : TypedExpression) = - // TODO: Technically, it should be the common base type of LHS and RHS. - let bothResult lhs rhs = - match lhs.ResolvedType.Resolution, rhs.ResolvedType.Resolution with - | Result, Result -> true - | _ -> false - match expr with - | EQ (lhs, rhs) -> bothResult lhs rhs - | NEQ (lhs, rhs) -> bothResult lhs rhs +/// +/// 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 parent ({ Expression = expression } : TypedExpression) = + let baseTypeKind lhs rhs = + ((lhs.ResolvedType, lhs.Range), (rhs.ResolvedType, rhs.Range)) + ||> CommonBaseType (fun _ _ -> ()) (ErrorCode.ArgumentMismatchInBinaryOp, []) parent + |> fun t -> t.Resolution + match expression with + | EQ (lhs, rhs) -> baseTypeKind lhs rhs = Result + | NEQ (lhs, rhs) -> baseTypeKind lhs rhs = Result | _ -> false /// Finds the locations where a mutable variable, which was not declared locally in the given scope, is reassigned. The @@ -103,12 +107,12 @@ let private verifyResultConditionalBlocks context condBlocks elseBlock = |> Option.map (fun block -> SyntaxGenerator.BoolLiteral true, block) |> Option.toList |> Seq.append condBlocks - let accumulateErrors (dependsOnResult, diagnostics) (condition : TypedExpression, block) = - if dependsOnResult || condition.Exists isResultComparison + let foldErrors (dependsOnResult, diagnostics) (condition : TypedExpression, block) = + if dependsOnResult || condition.Exists <| isResultComparison context.Symbols.Parent then true, Seq.concat [returnErrors block; setErrors block; diagnostics] else false, diagnostics if context.Capabilities = RuntimeCapabilities.QPRGen1 - then Seq.fold accumulateErrors (false, Seq.empty) blocks |> snd + then Seq.fold foldErrors (false, Seq.empty) blocks |> snd else Seq.empty From c7365c7483828fb95564fb7149ac1a4441eceb3f Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Mon, 29 Jun 2020 12:08:21 -0700 Subject: [PATCH 25/39] Remove QsStatement.RangeRelativeToRoot --- src/QsCompiler/SyntaxProcessor/StatementVerification.fs | 4 ++-- src/QsCompiler/SyntaxProcessor/VerificationTools.fs | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs index 39098e4e3a..7236b2c8f0 100644 --- a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs @@ -92,8 +92,8 @@ let private verifyResultConditionalBlocks context condBlocks elseBlock = | QsReturnStatement _ -> [s] | _ -> [] let returnError (statement : QsStatement) = - QsCompilerDiagnostic.Error (ErrorCode.ReturnInResultConditionedBlock, []) - (statement.RangeRelativeToRoot.ValueOr QsCompilerDiagnostic.DefaultRange) + let range = (QsNullable<_>.Map rangeRelativeToRoot statement.Location).ValueOr QsCompilerDiagnostic.DefaultRange + QsCompilerDiagnostic.Error (ErrorCode.ReturnInResultConditionedBlock, []) range let returnErrors (block : QsPositionedBlock) = block.Body.Statements |> Seq.collect returnStatements |> Seq.map returnError diff --git a/src/QsCompiler/SyntaxProcessor/VerificationTools.fs b/src/QsCompiler/SyntaxProcessor/VerificationTools.fs index 631a489a73..0f292f9b48 100644 --- a/src/QsCompiler/SyntaxProcessor/VerificationTools.fs +++ b/src/QsCompiler/SyntaxProcessor/VerificationTools.fs @@ -119,7 +119,3 @@ let internal rangeRelativeToRoot (location : QsLocation) = { Line = finish.Line + line Column = if start.Line = finish.Line then finish.Column + column else finish.Column } startFromRoot, finishFromRoot - -type QsStatement with - /// The range of this statement relative to its root node. - member this.RangeRelativeToRoot = QsNullable<_>.Map rangeRelativeToRoot this.Location From e0861cb6fabcdf09c23633578c73cc7c1034c774 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Mon, 29 Jun 2020 12:47:17 -0700 Subject: [PATCH 26/39] Add test for mutable set of tuple --- .../Tests.Compiler/CapabilityVerificationTests.fs | 7 ++++++- .../Tests.Compiler/TestCases/CapabilityVerification.qs | 10 ++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs b/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs index d6a9dfdd67..8d686789a3 100644 --- a/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs +++ b/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs @@ -42,12 +42,16 @@ let private all = "ResultAsBoolNeqOp" "ResultAsBoolOpReturnIf" "ResultAsBoolNeqOpReturnIf" + "ResultAsBoolOpReturnIfNested" "ResultAsBoolOpSetIf" "ResultAsBoolNeqOpSetIf" "ResultAsBoolOpElseSet" "ElifSet" "ElifElifSet" "ElifElseSet" + "SetLocal" + // TODO: "SetReusedName" + "SetTuple" "EmptyIf" "EmptyIfNeq" "EmptyIfOp" @@ -87,7 +91,8 @@ let ``QPRGen1 allows local mutable set from Result if`` () = [] let ``QPRGen1 restricts non-local mutable set from Result if`` () = [ "ResultAsBoolOpSetIf" - "ResultAsBoolNeqOpSetIf" ] + "ResultAsBoolNeqOpSetIf" + "SetTuple" ] |> List.iter (restricts gen1 [ErrorCode.SetInResultConditionedBlock]) "SetReusedName" |> restricts gen1 (ErrorCode.LocalVariableAlreadyExists :: List.replicate 2 ErrorCode.SetInResultConditionedBlock) diff --git a/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs b/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs index 88721e5b4a..5f3d833575 100644 --- a/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs +++ b/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs @@ -136,6 +136,7 @@ namespace Microsoft.Quantum.Testing.CapabilityVerification { 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; } @@ -143,6 +144,15 @@ namespace Microsoft.Quantum.Testing.CapabilityVerification { } } + 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) { } } From 71a04e2412769ccc39498354e51dc937bf74c8f0 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Mon, 29 Jun 2020 13:05:00 -0700 Subject: [PATCH 27/39] Add SetReusedName test case to Unknown/Gen0 --- .../CapabilityVerificationTests.fs | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs b/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs index 8d686789a3..3ab936d16e 100644 --- a/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs +++ b/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs @@ -28,14 +28,12 @@ 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 does not allow the given test case. -let private restricts (tester : CompilerTests) errorCodes name = tester.Verify (testName name, Seq.map Error errorCodes) - -/// 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 Gen0, +/// and no errors in Unknown. +let private simpleTests = [ "ResultAsBool" "ResultAsBoolNeq" "ResultAsBoolOp" @@ -50,7 +48,6 @@ let private all = "ElifElifSet" "ElifElseSet" "SetLocal" - // TODO: "SetReusedName" "SetTuple" "EmptyIf" "EmptyIfNeq" @@ -59,70 +56,73 @@ let private all = "Reset" "ResetNeq" ] -let [] ``Unknown allows all Result comparison`` () = List.iter (allows unknown) all +[] +let ``Unknown allows all Result comparison`` () = + List.iter (expect unknown []) simpleTests + "SetReusedName" |> expect unknown [ErrorCode.LocalVariableAlreadyExists] [] let ``QPRGen0 restricts all Result comparison`` () = - List.iter (restricts gen0 [ErrorCode.UnsupportedResultComparison]) all + List.iter (expect gen0 [ErrorCode.UnsupportedResultComparison]) simpleTests + "SetReusedName" |> expect gen0 [ErrorCode.LocalVariableAlreadyExists; ErrorCode.UnsupportedResultComparison] [] let ``QPRGen1 restricts Result comparison in functions`` () = [ "ResultAsBool" "ResultAsBoolNeq" ] - |> List.iter (restricts gen1 [ErrorCode.ResultComparisonNotInOperationIf]) + |> List.iter (expect gen1 [ErrorCode.ResultComparisonNotInOperationIf]) [] let ``QPRGen1 restricts non-if Result comparison in operations`` () = [ "ResultAsBoolOp" "ResultAsBoolNeqOp" ] - |> List.iter (restricts gen1 [ErrorCode.ResultComparisonNotInOperationIf]) + |> List.iter (expect gen1 [ErrorCode.ResultComparisonNotInOperationIf]) [] let ``QPRGen1 restricts return from Result if`` () = [ "ResultAsBoolOpReturnIf" "ResultAsBoolOpReturnIfNested" "ResultAsBoolNeqOpReturnIf" ] - |> List.iter (restricts gen1 <| Seq.replicate 2 ErrorCode.ReturnInResultConditionedBlock) + |> List.iter (expect gen1 <| Seq.replicate 2 ErrorCode.ReturnInResultConditionedBlock) [] -let ``QPRGen1 allows local mutable set from Result if`` () = - allows gen1 "SetLocal" +let ``QPRGen1 allows local mutable set from Result if`` () = "SetLocal" |> expect gen1 [] [] let ``QPRGen1 restricts non-local mutable set from Result if`` () = [ "ResultAsBoolOpSetIf" "ResultAsBoolNeqOpSetIf" "SetTuple" ] - |> List.iter (restricts gen1 [ErrorCode.SetInResultConditionedBlock]) + |> List.iter (expect gen1 [ErrorCode.SetInResultConditionedBlock]) "SetReusedName" - |> restricts gen1 (ErrorCode.LocalVariableAlreadyExists :: List.replicate 2 ErrorCode.SetInResultConditionedBlock) + |> expect gen1 (ErrorCode.LocalVariableAlreadyExists :: List.replicate 2 ErrorCode.SetInResultConditionedBlock) [] let ``QPRGen1 restricts non-local mutable set from Result elif`` () = [ "ElifSet" "ElifElifSet" ] - |> List.iter (restricts gen1 [ErrorCode.SetInResultConditionedBlock]) + |> List.iter (expect gen1 [ErrorCode.SetInResultConditionedBlock]) [] let ``QPRGen1 restricts non-local mutable set from Result else`` () = [ "ResultAsBoolOpElseSet" "ElifElseSet" ] - |> List.iter (restricts gen1 [ErrorCode.SetInResultConditionedBlock]) + |> List.iter (expect gen1 [ErrorCode.SetInResultConditionedBlock]) [] let ``QPRGen1 restricts empty Result if function`` () = [ "EmptyIf" "EmptyIfNeq" ] - |> List.iter (restricts gen1 [ErrorCode.ResultComparisonNotInOperationIf]) + |> List.iter (expect gen1 [ErrorCode.ResultComparisonNotInOperationIf]) [] let ``QPRGen1 allows empty Result if operation`` () = [ "EmptyIfOp" "EmptyIfNeqOp" ] - |> List.iter (allows gen1) + |> List.iter (expect gen1 []) [] let ``QPRGen1 allows operation call from Result if`` () = [ "Reset" "ResetNeq" ] - |> List.iter (allows gen1) + |> List.iter (expect gen1 []) From d785527de6ce53ea881aee27dc908022269da038 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Mon, 29 Jun 2020 13:09:00 -0700 Subject: [PATCH 28/39] Reformat new code in TryBuildIfStatement --- src/QsCompiler/CompilationManager/TypeChecking.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/QsCompiler/CompilationManager/TypeChecking.cs b/src/QsCompiler/CompilationManager/TypeChecking.cs index 25e6b2f1e8..527f0f07db 100644 --- a/src/QsCompiler/CompilationManager/TypeChecking.cs +++ b/src/QsCompiler/CompilationManager/TypeChecking.cs @@ -773,11 +773,11 @@ private static bool TryBuildIfStatement(IEnumerator nodes proceed = nodes.MoveNext(); } - var (ifStatement, ifDiagnostics) = Statements.NewIfStatement( - context, ifBlock.ToValueTuple(), elifBlocks, elseBlock); + var (ifStatement, ifDiagnostics) = + Statements.NewIfStatement(context, ifBlock.ToValueTuple(), elifBlocks, elseBlock); statement = ifStatement; - diagnostics.AddRange(ifDiagnostics.Select(diagnostic => Diagnostics.Generate( - context.Symbols.SourceFile.Value, diagnostic, rootPosition))); + diagnostics.AddRange(ifDiagnostics.Select( + diagnostic => Diagnostics.Generate(context.Symbols.SourceFile.Value, diagnostic, rootPosition))); return true; } (statement, proceed) = (null, true); From 71056442274c5d55f64e5589a37058aed2feb90b Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Mon, 29 Jun 2020 13:43:52 -0700 Subject: [PATCH 29/39] Update diagnostic messages --- src/QsCompiler/DataStructures/Diagnostics.fs | 18 ++++++++++-------- .../SyntaxProcessor/StatementVerification.fs | 7 ++++--- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/QsCompiler/DataStructures/Diagnostics.fs b/src/QsCompiler/DataStructures/Diagnostics.fs index 207e926664..9c85e5b27c 100644 --- a/src/QsCompiler/DataStructures/Diagnostics.fs +++ b/src/QsCompiler/DataStructures/Diagnostics.fs @@ -548,16 +548,18 @@ type DiagnosticItem = | 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 result - // comparison error messages. + // comparison error messages. | ErrorCode.UnsupportedResultComparison -> - "The execution target {0} does not support comparing measurement results. " + - "Choose an execution target with additional capabilities or avoid result comparisons." + "Measurement results cannot be compared because the execution target is {0}." | ErrorCode.ResultComparisonNotInOperationIf -> - "The execution target {0} supports comparing measurement results only as part of the condition of an " + - "if- or elif-statement in an operation. " + - "Choose an execution target with additional capabilities or avoid unsupported result comparisons." - | ErrorCode.ReturnInResultConditionedBlock -> "TODO: ReturnInResultConditionedBlock" - | ErrorCode.SetInResultConditionedBlock -> "TODO: SetInResultConditionedBlock" + "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." diff --git a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs index 7236b2c8f0..238ca2a91f 100644 --- a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs @@ -93,13 +93,14 @@ let private verifyResultConditionalBlocks context condBlocks elseBlock = | _ -> [] let returnError (statement : QsStatement) = let range = (QsNullable<_>.Map rangeRelativeToRoot statement.Location).ValueOr QsCompilerDiagnostic.DefaultRange - QsCompilerDiagnostic.Error (ErrorCode.ReturnInResultConditionedBlock, []) range + QsCompilerDiagnostic.Error (ErrorCode.ReturnInResultConditionedBlock, [context.ExecutionTarget.Value]) range let returnErrors (block : QsPositionedBlock) = block.Body.Statements |> Seq.collect returnStatements |> Seq.map returnError // Diagnostics for variable reassignments. - let setError (name, location) = - QsCompilerDiagnostic.Error (ErrorCode.SetInResultConditionedBlock, []) (rangeRelativeToRoot location) + let setError (name : NonNullable, location) = + QsCompilerDiagnostic.Error (ErrorCode.SetInResultConditionedBlock, [name.Value; context.ExecutionTarget.Value]) + (rangeRelativeToRoot location) let setErrors (block : QsPositionedBlock) = nonLocalUpdates context.Symbols block.Body |> Seq.map setError let blocks = From 9654e5bb64c82eebf13ed6d2e5bf616f6ca7a1e2 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Mon, 29 Jun 2020 13:45:44 -0700 Subject: [PATCH 30/39] Typo in doc comment --- src/QsCompiler/Core/SyntaxGenerator.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/QsCompiler/Core/SyntaxGenerator.fs b/src/QsCompiler/Core/SyntaxGenerator.fs index e4d19a0070..e4d31c4974 100644 --- a/src/QsCompiler/Core/SyntaxGenerator.fs +++ b/src/QsCompiler/Core/SyntaxGenerator.fs @@ -76,7 +76,7 @@ module SyntaxGenerator = let UnitValue = AutoGeneratedExpression UnitValue QsTypeKind.UnitType false - /// Creates a typed expression that corresponds to an Bool literal with the given value. + /// 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 From ce89dae86a3794839da3273604d8f1bb5af8cac6 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Wed, 1 Jul 2020 13:49:52 -0700 Subject: [PATCH 31/39] Remove QsNullable.ToOption --- src/QsCompiler/DataStructures/DataTypes.fs | 6 ------ src/QsCompiler/SyntaxProcessor/StatementVerification.fs | 6 +++--- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/QsCompiler/DataStructures/DataTypes.fs b/src/QsCompiler/DataStructures/DataTypes.fs index aac127a43c..fee665728f 100644 --- a/src/QsCompiler/DataStructures/DataTypes.fs +++ b/src/QsCompiler/DataStructures/DataTypes.fs @@ -53,12 +53,6 @@ type QsNullable<'T> = // to avoid having to include the F# core in the C# part o | Some v -> Value v | None -> Null - /// Converts the QsNullable to an F# option. - member this.ToOption = - match this with - | Value value -> Some value - | Null -> None - [] type NonNullable<'T> = private Item of 'T with diff --git a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs index 238ca2a91f..1821742bbc 100644 --- a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs @@ -105,8 +105,8 @@ let private verifyResultConditionalBlocks context condBlocks elseBlock = let blocks = elseBlock - |> Option.map (fun block -> SyntaxGenerator.BoolLiteral true, block) - |> Option.toList + |> 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 context.Symbols.Parent @@ -328,7 +328,7 @@ let NewIfStatement context (ifBlock : struct (TypedExpression * QsPositionedBloc QsConditionalStatement.New (condBlocks, elseBlock) |> QsConditionalStatement |> asStatement QsComments.Empty location LocalDeclarations.Empty - let diagnostics = verifyResultConditionalBlocks context condBlocks elseBlock.ToOption + 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, From 46fac78ad40ce4d8fce7c91c91645c5c1c0c05ff Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Wed, 1 Jul 2020 13:50:25 -0700 Subject: [PATCH 32/39] Formatting BoolLiteral --- src/QsCompiler/Core/SyntaxGenerator.fs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/QsCompiler/Core/SyntaxGenerator.fs b/src/QsCompiler/Core/SyntaxGenerator.fs index e4d31c4974..93aef3a27a 100644 --- a/src/QsCompiler/Core/SyntaxGenerator.fs +++ b/src/QsCompiler/Core/SyntaxGenerator.fs @@ -78,7 +78,8 @@ module SyntaxGenerator = /// 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 + 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. From 1af573c70d130fd8147e432cffbd9112c1419562 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Wed, 1 Jul 2020 14:05:20 -0700 Subject: [PATCH 33/39] Don't use CommonBaseType --- .../SyntaxProcessor/ExpressionVerification.fs | 2 +- .../SyntaxProcessor/StatementVerification.fs | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs b/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs index 962860e398..022275f74b 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 internal 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 diff --git a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs index 1821742bbc..664b47335f 100644 --- a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs @@ -57,14 +57,14 @@ let private onAutoInvertGenerateError (errCode, range) (symbols : SymbolTracker< /// /// The name of the callable in which the expression occurs. /// The expression. -let private isResultComparison parent ({ Expression = expression } : TypedExpression) = - let baseTypeKind lhs rhs = - ((lhs.ResolvedType, lhs.Range), (rhs.ResolvedType, rhs.Range)) - ||> CommonBaseType (fun _ _ -> ()) (ErrorCode.ArgumentMismatchInBinaryOp, []) parent - |> fun t -> t.Resolution +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 match expression with - | EQ (lhs, rhs) -> baseTypeKind lhs rhs = Result - | NEQ (lhs, rhs) -> baseTypeKind lhs rhs = Result + | 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 @@ -109,7 +109,7 @@ let private verifyResultConditionalBlocks context condBlocks elseBlock = |> QsNullable<_>.Fold (fun acc x -> x :: acc) [] |> Seq.append condBlocks let foldErrors (dependsOnResult, diagnostics) (condition : TypedExpression, block) = - if dependsOnResult || condition.Exists <| isResultComparison context.Symbols.Parent + if dependsOnResult || condition.Exists isResultComparison then true, Seq.concat [returnErrors block; setErrors block; diagnostics] else false, diagnostics if context.Capabilities = RuntimeCapabilities.QPRGen1 From 06894041f8602a634ae2cb8d5abce5d0eabc594e Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Wed, 1 Jul 2020 19:32:10 -0700 Subject: [PATCH 34/39] Add comment about no-derived-types assumption --- src/QsCompiler/SyntaxProcessor/StatementVerification.fs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs index 664b47335f..4e30ba0423 100644 --- a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs @@ -62,6 +62,7 @@ let private isResultComparison ({ Expression = expression } : TypedExpression) = | 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 can be used in equality comparisons. match expression with | EQ (lhs, rhs) -> binaryType lhs rhs = Result | NEQ (lhs, rhs) -> binaryType lhs rhs = Result From 3caeec11f66c5bee21b561ff01e1d0259dd9be57 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Wed, 1 Jul 2020 19:49:16 -0700 Subject: [PATCH 35/39] Document assumptions and add tests for compound types --- .../SyntaxProcessor/ExpressionVerification.fs | 4 ++++ .../SyntaxProcessor/StatementVerification.fs | 5 ++++- .../Tests.Compiler/CapabilityVerificationTests.fs | 13 +++++++++++-- .../TestCases/CapabilityVerification.qs | 11 +++++++++++ 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs b/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs index 022275f74b..7e5714d04c 100644 --- a/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs @@ -251,6 +251,10 @@ 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.ExecutionTarget.Value]) rhsRange diff --git a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs index 4e30ba0423..b6d7696b55 100644 --- a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs @@ -62,7 +62,10 @@ let private isResultComparison ({ Expression = expression } : TypedExpression) = | 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 can be used in equality comparisons. + + // 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 diff --git a/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs b/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs index 3ab936d16e..bb9820c18c 100644 --- a/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs +++ b/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs @@ -31,8 +31,8 @@ let private testName name = /// 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) -/// The names of all "simple" test cases: test cases that have exactly one unsupported result comparison error in Gen0, -/// and no errors in Unknown. +/// 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" @@ -60,17 +60,26 @@ let private simpleTests = 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`` () = [ "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`` () = diff --git a/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs b/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs index 5f3d833575..ee8fc25291 100644 --- a/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs +++ b/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs @@ -180,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; + } } From 0d8a1fc4315d8f55226621e42b79db3959866fd9 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Thu, 2 Jul 2020 15:36:49 -0700 Subject: [PATCH 36/39] Remove struct tuple --- src/QsCompiler/CompilationManager/TypeChecking.cs | 2 +- .../SyntaxProcessor/StatementVerification.fs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/QsCompiler/CompilationManager/TypeChecking.cs b/src/QsCompiler/CompilationManager/TypeChecking.cs index 527f0f07db..474423120c 100644 --- a/src/QsCompiler/CompilationManager/TypeChecking.cs +++ b/src/QsCompiler/CompilationManager/TypeChecking.cs @@ -774,7 +774,7 @@ private static bool TryBuildIfStatement(IEnumerator nodes } var (ifStatement, ifDiagnostics) = - Statements.NewIfStatement(context, ifBlock.ToValueTuple(), elifBlocks, elseBlock); + Statements.NewIfStatement(context, ifBlock.Item1, ifBlock.Item2, elifBlocks, elseBlock); statement = ifStatement; diagnostics.AddRange(ifDiagnostics.Select( diagnostic => Diagnostics.Generate(context.Symbols.SourceFile.Value, diagnostic, rootPosition))); diff --git a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs index b6d7696b55..13a764f31a 100644 --- a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs @@ -320,14 +320,14 @@ let NewConditionalBlock comments location context (qsExpr : QsExpression) = 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 context (ifBlock : struct (TypedExpression * QsPositionedBlock)) elifBlocks elseBlock = +/// 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 (ifBlock.ToTuple() |> snd).Location with + match (snd ifBlock).Location with | Null -> ArgumentException "No location is set for the given if-block." |> raise | Value location -> location - let condBlocks = Seq.append (ifBlock.ToTuple() |> Seq.singleton) elifBlocks + let condBlocks = Seq.append (Seq.singleton ifBlock) elifBlocks let statement = QsConditionalStatement.New (condBlocks, elseBlock) |> QsConditionalStatement From f5f08af68645495626289d1fd8bc8251092f13e5 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Mon, 6 Jul 2020 10:40:37 -0700 Subject: [PATCH 37/39] Update references to ExecutionTarget --- src/QsCompiler/SyntaxProcessor/StatementVerification.fs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs index 13a764f31a..91858ca3f7 100644 --- a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs @@ -97,14 +97,17 @@ let private verifyResultConditionalBlocks context condBlocks elseBlock = | _ -> [] let returnError (statement : QsStatement) = let range = (QsNullable<_>.Map rangeRelativeToRoot statement.Location).ValueOr QsCompilerDiagnostic.DefaultRange - QsCompilerDiagnostic.Error (ErrorCode.ReturnInResultConditionedBlock, [context.ExecutionTarget.Value]) range + QsCompilerDiagnostic.Error + (ErrorCode.ReturnInResultConditionedBlock, [context.ProcessorArchitecture.Value]) + range let returnErrors (block : QsPositionedBlock) = block.Body.Statements |> Seq.collect returnStatements |> Seq.map returnError // Diagnostics for variable reassignments. let setError (name : NonNullable, location) = - QsCompilerDiagnostic.Error (ErrorCode.SetInResultConditionedBlock, [name.Value; context.ExecutionTarget.Value]) - (rangeRelativeToRoot location) + QsCompilerDiagnostic.Error + (ErrorCode.SetInResultConditionedBlock, [name.Value; context.ProcessorArchitecture.Value]) + (rangeRelativeToRoot location) let setErrors (block : QsPositionedBlock) = nonLocalUpdates context.Symbols block.Body |> Seq.map setError let blocks = From 12b15a6c06d4f6ede89041d6d859f66cb76ae7e9 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Mon, 6 Jul 2020 13:19:44 -0700 Subject: [PATCH 38/39] Move range conversion to CompilationManager --- .../CompilationManager/DiagnosticTools.cs | 14 ++++++++++++++ src/QsCompiler/CompilationManager/TypeChecking.cs | 8 ++++++-- .../SyntaxProcessor/StatementVerification.fs | 14 ++++++-------- .../SyntaxProcessor/VerificationTools.fs | 12 ------------ 4 files changed, 26 insertions(+), 22 deletions(-) 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 a38d490f4e..425505d6a8 100644 --- a/src/QsCompiler/CompilationManager/TypeChecking.cs +++ b/src/QsCompiler/CompilationManager/TypeChecking.cs @@ -776,8 +776,12 @@ private static bool TryBuildIfStatement(IEnumerator nodes var (ifStatement, ifDiagnostics) = Statements.NewIfStatement(context, ifBlock.Item1, ifBlock.Item2, elifBlocks, elseBlock); statement = ifStatement; - diagnostics.AddRange(ifDiagnostics.Select( - diagnostic => Diagnostics.Generate(context.Symbols.SourceFile.Value, diagnostic, rootPosition))); + 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); diff --git a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs index 91858ca3f7..d27e87572c 100644 --- a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs @@ -96,18 +96,16 @@ let private verifyResultConditionalBlocks context condBlocks elseBlock = | QsReturnStatement _ -> [s] | _ -> [] let returnError (statement : QsStatement) = - let range = (QsNullable<_>.Map rangeRelativeToRoot statement.Location).ValueOr QsCompilerDiagnostic.DefaultRange - QsCompilerDiagnostic.Error - (ErrorCode.ReturnInResultConditionedBlock, [context.ProcessorArchitecture.Value]) - range + 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) = - QsCompilerDiagnostic.Error - (ErrorCode.SetInResultConditionedBlock, [name.Value; context.ProcessorArchitecture.Value]) - (rangeRelativeToRoot location) + 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 = diff --git a/src/QsCompiler/SyntaxProcessor/VerificationTools.fs b/src/QsCompiler/SyntaxProcessor/VerificationTools.fs index 0f292f9b48..ce20143c41 100644 --- a/src/QsCompiler/SyntaxProcessor/VerificationTools.fs +++ b/src/QsCompiler/SyntaxProcessor/VerificationTools.fs @@ -107,15 +107,3 @@ type ResolvedType with | Range -> Some (Int |> ResolvedType.New) | ArrayType bt -> Some bt | _ -> None - -/// Converts the range relative to the QsLocation's offset into a range relative to the root node. -let internal rangeRelativeToRoot (location : QsLocation) = - let line, column = location.Offset - let start, finish = location.Range - let startFromRoot = - { Line = start.Line + line - Column = start.Column + column } - let finishFromRoot = - { Line = finish.Line + line - Column = if start.Line = finish.Line then finish.Column + column else finish.Column } - startFromRoot, finishFromRoot From db89527fa5d90684df16d1a7b495c329f42fa038 Mon Sep 17 00:00:00 2001 From: Sarah Marshall Date: Mon, 6 Jul 2020 13:36:40 -0700 Subject: [PATCH 39/39] Fix conjugation diagnostic range --- src/QsCompiler/CompilationManager/TypeChecking.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/QsCompiler/CompilationManager/TypeChecking.cs b/src/QsCompiler/CompilationManager/TypeChecking.cs index 425505d6a8..2ed32be877 100644 --- a/src/QsCompiler/CompilationManager/TypeChecking.cs +++ b/src/QsCompiler/CompilationManager/TypeChecking.cs @@ -826,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();