Skip to content
This repository was archived by the owner on Jan 12, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
ccc34b2
Verify that result comparisons are in if condition
Jun 16, 2020
9190fbd
Add test cases for elif/else Result comparisons
Jun 22, 2020
73d94bc
Gen 1 result ifs can only be in operations
Jun 23, 2020
17da9e8
Un-skip QPRGen1 tests
Jun 23, 2020
029f1f6
Add new diagnostics
Jun 24, 2020
91d170e
Messy, but most QPRGen1 tests passing
Jun 24, 2020
41e00d4
Merge branch 'master' into samarsha/verify-gen1
Jun 24, 2020
e3ce632
Fix diagnostic messages
Jun 24, 2020
028e627
Update diagnostic messages
Jun 24, 2020
d2ac82f
Workaround to get diagnostics closer to the right place
Jun 24, 2020
8071f7e
Rename startOffset to rootPosition
Jun 24, 2020
4f6789f
Add test for return in nested scope
Jun 25, 2020
0c9d67f
Replace FindStatements transformation with private function
Jun 25, 2020
85c630a
Combine Offset and Range for return statements
Jun 25, 2020
51be79a
Add QsStatement.RangeRelativeToRoot
Jun 25, 2020
59cbfc4
Clean up verification
Jun 25, 2020
1aaa77d
Merge branch 'master' into samarsha/verify-gen1
bamarsha Jun 26, 2020
1834f33
Use _ for discarded case value
Jun 26, 2020
568f786
Use QsStatement.ExtractAll
Jun 26, 2020
2ecba91
Use TypedExpression.Exists
Jun 26, 2020
92c63c8
Add new test cases, currently failing
Jun 26, 2020
4e10baa
Merge branch 'master' into samarsha/verify-gen1
bamarsha Jun 26, 2020
c83156b
Merge branch 'master' into samarsha/verify-gen1
bamarsha Jun 26, 2020
1807c59
Update mutable set verification
Jun 27, 2020
d090b9d
Merge remote-tracking branch 'origin/samarsha/verify-gen1' into samar…
Jun 27, 2020
ebfb80b
Merge branch 'master' into samarsha/verify-gen1
Jun 27, 2020
5a1930a
Update doc comment for TreeNode.GetRootPosition
Jun 27, 2020
4922c33
Move verification functions to the top
Jun 27, 2020
5a89f1d
Move else-block normalization to verify function
Jun 27, 2020
58b58e3
Use common base type in isResultComparison
Jun 29, 2020
c7365c7
Remove QsStatement.RangeRelativeToRoot
Jun 29, 2020
e0861cb
Add test for mutable set of tuple
Jun 29, 2020
71a04e2
Add SetReusedName test case to Unknown/Gen0
Jun 29, 2020
d785527
Reformat new code in TryBuildIfStatement
Jun 29, 2020
7105644
Update diagnostic messages
Jun 29, 2020
9654e5b
Typo in doc comment
Jun 29, 2020
d9a64d9
Merge branch 'master' into samarsha/verify-gen1
bamarsha Jul 1, 2020
ce89dae
Remove QsNullable.ToOption
Jul 1, 2020
46fac78
Formatting BoolLiteral
Jul 1, 2020
1af573c
Don't use CommonBaseType
Jul 1, 2020
0689404
Add comment about no-derived-types assumption
Jul 2, 2020
3caeec1
Document assumptions and add tests for compound types
Jul 2, 2020
0d8a1fc
Remove struct tuple
Jul 2, 2020
e4b7d9d
Merge branch 'master' into samarsha/verify-gen1
bamarsha Jul 6, 2020
f5f08af
Update references to ExecutionTarget
Jul 6, 2020
12b15a6
Move range conversion to CompilationManager
Jul 6, 2020
db89527
Fix conjugation diagnostic range
Jul 6, 2020
71504cc
Merge branch 'master' into samarsha/verify-gen1
bamarsha Jul 6, 2020
dc1ad54
Merge branch 'master' into samarsha/verify-gen1
bamarsha Jul 7, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/QsCompiler/CompilationManager/DataStructures.cs
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,12 @@ internal struct TreeNode
private readonly Position rootPosition;
public readonly CodeFragment Fragment;
public readonly IReadOnlyList<TreeNode> Children;

/// <summary>
/// Returns the position of the root node that all child node positions are relative to.
/// </summary>
public Position GetRootPosition() => rootPosition.Copy();

public Position GetPositionRelativeToRoot() => relPosition.Copy();

/// <summary>
Expand Down
14 changes: 14 additions & 0 deletions src/QsCompiler/CompilationManager/DiagnosticTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,20 @@ internal static Position GetAbsolutePosition(Position offset, QsPositionInfo rel
return absPos;
}

/// <summary>
/// Creates an absolute position by adding the zero-based offset to the relative position.
/// </summary>
/// <param name="position">The position.</param>
/// <param name="offset">A zero-based line and column offset.</param>
/// <exception cref="ArgumentException">Thrown if the position line or column is negative.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown if offset line or column is negative.</exception>
/// <returns>The absolute position.</returns>
internal static Position GetAbsolutePosition(Position position, Tuple<int, int> offset)
{
var (line, column) = offset;
return GetAbsolutePosition(position, new QsPositionInfo(line + 1, column + 1));
}

/// <summary>
/// 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!
Expand Down
23 changes: 19 additions & 4 deletions src/QsCompiler/CompilationManager/TypeChecking.cs
Original file line number Diff line number Diff line change
Expand Up @@ -742,10 +742,12 @@ private static bool TryBuildIfStatement(IEnumerator<FragmentTree.TreeNode> nodes

if (nodes.Current.Fragment.Kind is QsFragmentKind.IfClause ifCond)
{
var rootPosition = nodes.Current.GetRootPosition();

// if block
var buildClause = BuildStatement(nodes.Current,
(relPos, ctx) => Statements.NewConditionalBlock(nodes.Current.Fragment.Comments, relPos, ctx, ifCond.Item),
context, diagnostics);
context.WithinIfCondition, diagnostics);
var ifBlock = buildClause(BuildScope(nodes.Current.Children, context, diagnostics));

// elif blocks
Expand All @@ -755,7 +757,7 @@ private static bool TryBuildIfStatement(IEnumerator<FragmentTree.TreeNode> 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();
}
Expand All @@ -771,7 +773,15 @@ private static bool TryBuildIfStatement(IEnumerator<FragmentTree.TreeNode> nodes
proceed = nodes.MoveNext();
}

statement = Statements.NewIfStatement(ifBlock, elifBlocks, elseBlock);
var (ifStatement, ifDiagnostics) =
Statements.NewIfStatement(context, ifBlock.Item1, ifBlock.Item2, elifBlocks, elseBlock);
statement = ifStatement;
diagnostics.AddRange(ifDiagnostics.Select(item =>
{
var (relativeOffset, diagnostic) = item;
var offset = DiagnosticTools.GetAbsolutePosition(rootPosition, relativeOffset);
return Diagnostics.Generate(context.Symbols.SourceFile.Value, diagnostic, offset);
}));
return true;
}
(statement, proceed) = (null, true);
Expand Down Expand Up @@ -816,7 +826,12 @@ QsNullable<QsLocation> 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();
Expand Down
5 changes: 5 additions & 0 deletions src/QsCompiler/Core/SyntaxGenerator.fs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ module SyntaxGenerator =
let UnitValue =
AutoGeneratedExpression UnitValue QsTypeKind.UnitType false

/// Creates a typed expression that corresponds to a Bool literal with the given value.
/// Sets the range information for the built expression to Null.
let BoolLiteral value =
AutoGeneratedExpression (BoolLiteral value) QsTypeKind.Bool false

/// Creates a typed expression that corresponds to an Int literal with the given value.
/// Sets the range information for the built expression to Null.
let IntLiteral v =
Expand Down
16 changes: 15 additions & 1 deletion src/QsCompiler/DataStructures/Diagnostics.fs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ type ErrorCode =
| ExpectingCallableExpr = 5021
| UnknownIdentifier = 5022
| UnsupportedResultComparison = 5023
| ResultComparisonNotInOperationIf = 5024
| ReturnInResultConditionedBlock = 5025
| SetInResultConditionedBlock = 5026

| CallableRedefinition = 6001
| CallableOverlapWithTypeConstructor = 6002
Expand Down Expand Up @@ -544,8 +547,19 @@ type DiagnosticItem =
| ErrorCode.ExpectingIterableExpr -> "The type {0} does not support iteration. Expecting an expression of array type or of type Range."
| ErrorCode.ExpectingCallableExpr -> "The type of the expression must be a function or operation type. The given expression is of type {0}."
| ErrorCode.UnknownIdentifier -> "No identifier with the name \"{0}\" exists."
// TODO: When the names of the runtime capabilities are finalized, they can be included in the error message.
// TODO: When the names of the runtime capabilities are finalized, they can be included in the result
// comparison error messages.
| ErrorCode.UnsupportedResultComparison -> "The target {0} does not support comparing measurement results."
| ErrorCode.ResultComparisonNotInOperationIf ->
"Measurement results cannot be compared here. " +
"The execution target {0} only supports comparing measurement results as part of the condition of an if- or elif-statement in an operation."
| ErrorCode.ReturnInResultConditionedBlock ->
"A return statement cannot be used here. " +
"The execution target {0} does not support return statements in conditional blocks that depend on a measurement result."
| ErrorCode.SetInResultConditionedBlock ->
"The variable \"{0}\" cannot be reassigned here. " +
"In conditional blocks that depend on a measurement result, the execution target {1} only supports reassigning variables that were declared within the block."

| ErrorCode.CallableRedefinition -> "Invalid callable declaration. A function or operation with the name \"{0}\" already exists."
| ErrorCode.CallableOverlapWithTypeConstructor -> "Invalid callable declaration. A type constructor with the name \"{0}\" already exists."
| ErrorCode.TypeRedefinition -> "Invalid type declaration. A type with the name \"{0}\" already exists."
Expand Down
9 changes: 8 additions & 1 deletion src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ let internal toString (t : ResolvedType) = SyntaxTreeToQsharp.Default.ToCode t
/// This subtyping carries over to tuple types containing operations, and callable types containing operations as within their in- and/or output type.
/// However, arrays in particular are treated as invariant;
/// i.e. an array of operations of type t1 are *not* a subtype of arrays of operations of type t2 even if t1 is a subtype of t2.
let private CommonBaseType addError mismatchErr parent (lhsType : ResolvedType, lhsRange) (rhsType : ResolvedType, rhsRange) : ResolvedType =
let private CommonBaseType addError mismatchErr parent (lhsType : ResolvedType, lhsRange) (rhsType : ResolvedType, rhsRange) : ResolvedType =
let raiseError errCode (lhsCond, rhsCond) =
if lhsCond then lhsRange |> addError errCode
if rhsCond then rhsRange |> addError errCode
Expand Down Expand Up @@ -251,9 +251,16 @@ let private VerifyEqualityComparison context addError (lhsType, lhsRange) (rhsTy
// comparison for any derived type).
let argumentError = ErrorCode.ArgumentMismatchInBinaryOp, [toString lhsType; toString rhsType]
let baseType = CommonBaseType addError argumentError context.Symbols.Parent (lhsType, lhsRange) (rhsType, rhsRange)

// This assumes that:
// - Result has no derived types that support equality comparisons.
// - Compound types containing Result (e.g., tuples or arrays of results) do not support equality comparison.
match baseType.Resolution with
| Result when context.Capabilities = RuntimeCapabilities.QPRGen0 ->
addError (ErrorCode.UnsupportedResultComparison, [context.ProcessorArchitecture.Value]) rhsRange
| Result when context.Capabilities = RuntimeCapabilities.QPRGen1 &&
not (context.IsInOperation && context.IsInIfCondition) ->
addError (ErrorCode.ResultComparisonNotInOperationIf, [context.ProcessorArchitecture.Value]) rhsRange
| _ ->
let unsupportedError = ErrorCode.InvalidTypeInEqualityComparison, [toString baseType]
VerifyIsOneOf (fun t -> t.supportsEqualityComparison) unsupportedError addError (baseType, rhsRange) |> ignore
Expand Down
7 changes: 7 additions & 0 deletions src/QsCompiler/SyntaxProcessor/ScopeTools.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -313,7 +315,12 @@ type ScopeContext<'a> =
| Found declaration ->
{ Symbols = SymbolTracker<'a> (nsManager, spec.SourceFile, spec.Parent)
IsInOperation = declaration.Kind = Operation
IsInIfCondition = false
ReturnType = StripPositionInfo.Apply declaration.Signature.ReturnType
Capabilities = capabilities
ProcessorArchitecture = processorArchitecture }
| _ -> raise <| ArgumentException "The specialization's parent callable does not exist."

/// Returns a new scope context for an expression that is contained within the condition of an if- or
/// elif-statement.
member this.WithinIfCondition = { this with IsInIfCondition = true }
94 changes: 85 additions & 9 deletions src/QsCompiler/SyntaxProcessor/StatementVerification.fs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ open System.Collections.Immutable
open Microsoft.Quantum.QsCompiler
open Microsoft.Quantum.QsCompiler.DataTypes
open Microsoft.Quantum.QsCompiler.Diagnostics
open Microsoft.Quantum.QsCompiler.ReservedKeywords.AssemblyConstants
open Microsoft.Quantum.QsCompiler.SyntaxExtensions
open Microsoft.Quantum.QsCompiler.SyntaxProcessing
open Microsoft.Quantum.QsCompiler.SyntaxProcessing.Expressions
Expand Down Expand Up @@ -51,6 +52,75 @@ let private onAutoInvertGenerateError (errCode, range) (symbols : SymbolTracker<
if not (symbols.RequiredFunctorSupport.Contains QsFunctor.Adjoint) then [||]
else [| range |> QsCompilerDiagnostic.Error errCode |]

/// <summary>
/// Returns true if the expression is an equality or inequality comparison between two expressions of type Result.
/// </summary>
/// <param name="parent">The name of the callable in which the expression occurs.</param>
/// <param name="expression">The expression.</param>
let private isResultComparison ({ Expression = expression } : TypedExpression) =
let validType = function
| InvalidType -> None
| kind -> Some kind
let binaryType lhs rhs = validType lhs.ResolvedType.Resolution |> Option.defaultValue rhs.ResolvedType.Resolution

// This assumes that:
// - Result has no derived types that support equality comparisons.
// - Compound types containing Result (e.g., tuples or arrays of results) do not support equality comparison.
match expression with
| EQ (lhs, rhs) -> binaryType lhs rhs = Result
| NEQ (lhs, rhs) -> binaryType lhs rhs = Result
| _ -> false

/// Finds the locations where a mutable variable, which was not declared locally in the given scope, is reassigned. The
/// symbol tracker is not modified. Returns the name of the variable and the location of the reassignment.
let private nonLocalUpdates (symbols : SymbolTracker<_>) (scope : QsScope) =
// This assumes that a variable with the same name cannot be re-declared in an inner scope (i.e., shadowing is not
// allowed).
let identifierExists (name, location : QsLocation) =
let symbol = { Symbol = Symbol name; Range = Value location.Range }
match (symbols.ResolveIdentifier ignore symbol |> fst).VariableName with
| InvalidIdentifier -> false
| _ -> true
let accumulator = AccumulateIdentifiers()
accumulator.Statements.OnScope scope |> ignore
accumulator.SharedState.ReassignedVariables
|> Seq.collect (fun grouping -> Seq.map (fun location -> grouping.Key, location) grouping)
|> Seq.filter identifierExists

/// Verifies that any conditional blocks which depend on a measurement result do not use any language constructs that
/// are not supported by the runtime capabilities. Returns the diagnostics for the blocks.
let private verifyResultConditionalBlocks context condBlocks elseBlock =
// Diagnostics for return statements.
let returnStatements (statement : QsStatement) = statement.ExtractAll <| fun s ->
match s.Statement with
| QsReturnStatement _ -> [s]
| _ -> []
let returnError (statement : QsStatement) =
let error = ErrorCode.ReturnInResultConditionedBlock, [context.ProcessorArchitecture.Value]
let location = statement.Location.ValueOr { Offset = 0, 0; Range = QsCompilerDiagnostic.DefaultRange }
location.Offset, QsCompilerDiagnostic.Error error location.Range
let returnErrors (block : QsPositionedBlock) =
block.Body.Statements |> Seq.collect returnStatements |> Seq.map returnError

// Diagnostics for variable reassignments.
let setError (name : NonNullable<string>, location : QsLocation) =
let error = ErrorCode.SetInResultConditionedBlock, [name.Value; context.ProcessorArchitecture.Value]
location.Offset, QsCompilerDiagnostic.Error error location.Range
let setErrors (block : QsPositionedBlock) = nonLocalUpdates context.Symbols block.Body |> Seq.map setError

let blocks =
elseBlock
|> QsNullable<_>.Map (fun block -> SyntaxGenerator.BoolLiteral true, block)
|> QsNullable<_>.Fold (fun acc x -> x :: acc) []
|> Seq.append condBlocks
let foldErrors (dependsOnResult, diagnostics) (condition : TypedExpression, block) =
if dependsOnResult || condition.Exists isResultComparison
then true, Seq.concat [returnErrors block; setErrors block; diagnostics]
else false, diagnostics
if context.Capabilities = RuntimeCapabilities.QPRGen1
then Seq.fold foldErrors (false, Seq.empty) blocks |> snd
else Seq.empty


// utils for building QsStatements from QsFragmentKinds

Expand Down Expand Up @@ -250,15 +320,21 @@ let NewConditionalBlock comments location context (qsExpr : QsExpression) =
let block body = condition, QsPositionedBlock.New comments (Value location) body
new BlockStatement<_>(block), Array.concat [errs; autoGenErrs]

/// Given a conditional block for the if-clause of a Q# if-statement, a sequence of conditional blocks for the elif-clauses,
/// as well as optionally a positioned block of Q# statements and its location for the else-clause, builds and returns the complete if-statement.
/// Throws an ArgumentException if the given if-block contains no location information.
let NewIfStatement (ifBlock : TypedExpression * QsPositionedBlock, elifBlocks, elseBlock : QsNullable<QsPositionedBlock>) =
let location = (snd ifBlock).Location |> function
| Null -> ArgumentException "no location is set for the given if-block" |> raise
| Value loc -> loc
let condBlocks = seq { yield ifBlock; yield! elifBlocks; }
QsConditionalStatement.New (condBlocks, elseBlock) |> QsConditionalStatement |> asStatement QsComments.Empty location LocalDeclarations.Empty
/// Given a conditional block for the if-clause of a Q# if-statement, a sequence of conditional blocks for the elif-clauses,
/// as well as optionally a positioned block of Q# statements and its location for the else-clause, builds and returns the complete if-statement.
/// Throws an ArgumentException if the given if-block contains no location information.
let NewIfStatement context (ifBlock : TypedExpression * QsPositionedBlock) elifBlocks elseBlock =
let location =
match (snd ifBlock).Location with
| Null -> ArgumentException "No location is set for the given if-block." |> raise
| Value location -> location
let condBlocks = Seq.append (Seq.singleton ifBlock) elifBlocks
let statement =
QsConditionalStatement.New (condBlocks, elseBlock)
|> QsConditionalStatement
|> asStatement QsComments.Empty location LocalDeclarations.Empty
let diagnostics = verifyResultConditionalBlocks context condBlocks elseBlock
statement, diagnostics

/// Given a positioned block of Q# statements for the repeat-block of a Q# RUS-statement, a typed expression containing the success condition,
/// as well as a positioned block of Q# statements for the fixup-block, builds the complete RUS-statement at the given location and returns it.
Expand Down
2 changes: 0 additions & 2 deletions src/QsCompiler/SyntaxProcessor/VerificationTools.fs
Original file line number Diff line number Diff line change
Expand Up @@ -107,5 +107,3 @@ type ResolvedType with
| Range -> Some (Int |> ResolvedType.New)
| ArrayType bt -> Some bt
| _ -> None


Loading