diff --git a/src/QsCompiler/CompilationManager/CompilationUnit.cs b/src/QsCompiler/CompilationManager/CompilationUnit.cs index 40daa00a71..c4a459c07b 100644 --- a/src/QsCompiler/CompilationManager/CompilationUnit.cs +++ b/src/QsCompiler/CompilationManager/CompilationUnit.cs @@ -234,6 +234,7 @@ public class CompilationUnit : IReaderWriterLock, IDisposable internal readonly RuntimeCapabilities RuntimeCapabilities; internal readonly bool IsExecutable; + internal readonly NonNullable ExecutionTarget; public void Dispose() { this.SyncRoot.Dispose(); } @@ -243,8 +244,12 @@ public void Dispose() /// with the given sequence of locks registered as dependent locks if the sequence is not null. /// Throws an ArgumentNullException if any of the given locks is. /// - internal CompilationUnit(RuntimeCapabilities capabilities, bool isExecutable, - References externals = null, IEnumerable dependentLocks = null) + internal CompilationUnit( + RuntimeCapabilities capabilities, + bool isExecutable, + NonNullable executionTarget, + References externals = null, + IEnumerable dependentLocks = null) { this.SyncRoot = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); this.DependentLocks = dependentLocks == null @@ -254,6 +259,7 @@ internal CompilationUnit(RuntimeCapabilities capabilities, bool isExecutable, this.RuntimeCapabilities = capabilities; this.IsExecutable = isExecutable; + this.ExecutionTarget = executionTarget; this.CompiledCallables = new Dictionary(); this.CompiledTypes = new Dictionary(); diff --git a/src/QsCompiler/CompilationManager/CompilationUnitManager.cs b/src/QsCompiler/CompilationManager/CompilationUnitManager.cs index a4df715e30..8dc0e3f635 100644 --- a/src/QsCompiler/CompilationManager/CompilationUnitManager.cs +++ b/src/QsCompiler/CompilationManager/CompilationUnitManager.cs @@ -64,11 +64,15 @@ public class CompilationUnitManager : IDisposable /// that action is called whenever diagnostics within a file have changed and are ready for publishing. /// public CompilationUnitManager( - Action exceptionLogger = null, Action publishDiagnostics = null, bool syntaxCheckOnly = false, - AssemblyConstants.RuntimeCapabilities capabilities = AssemblyConstants.RuntimeCapabilities.Unknown, bool isExecutable = false) + Action exceptionLogger = null, + Action publishDiagnostics = null, + bool syntaxCheckOnly = false, + AssemblyConstants.RuntimeCapabilities capabilities = AssemblyConstants.RuntimeCapabilities.Unknown, + bool isExecutable = false, + NonNullable executionTarget = default) { this.EnableVerification = !syntaxCheckOnly; - this.CompilationUnit = new CompilationUnit(capabilities, isExecutable); + this.CompilationUnit = new CompilationUnit(capabilities, isExecutable, executionTarget); this.FileContentManagers = new ConcurrentDictionary, FileContentManager>(); this.ChangedFiles = new ManagedHashSet>(new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion)); this.PublishDiagnostics = publishDiagnostics ?? (_ => { }); @@ -440,7 +444,12 @@ private Task SpawnGlobalTypeCheckingAsync(bool runSynchronously = false) // work with a separate compilation unit instance such that processing of all further edits can go on in parallel var sourceFiles = this.FileContentManagers.Values.OrderBy(m => m.FileName); this.ChangedFiles.RemoveAll(f => sourceFiles.Any(m => m.FileName.Value == f.Value)); - var compilation = new CompilationUnit(this.CompilationUnit.RuntimeCapabilities, this.CompilationUnit.IsExecutable, this.CompilationUnit.Externals, sourceFiles.Select(file => file.SyncRoot)); + var compilation = new CompilationUnit( + this.CompilationUnit.RuntimeCapabilities, + this.CompilationUnit.IsExecutable, + this.CompilationUnit.ExecutionTarget, + this.CompilationUnit.Externals, + sourceFiles.Select(file => file.SyncRoot)); var content = compilation.UpdateGlobalSymbolsFor(sourceFiles); foreach (var file in sourceFiles) this.PublishDiagnostics(file.Diagnostics()); diff --git a/src/QsCompiler/CompilationManager/ProjectManager.cs b/src/QsCompiler/CompilationManager/ProjectManager.cs index 564e489e63..e5855fdffe 100644 --- a/src/QsCompiler/CompilationManager/ProjectManager.cs +++ b/src/QsCompiler/CompilationManager/ProjectManager.cs @@ -23,17 +23,22 @@ internal class ProjectProperties public readonly string OutputPath; public readonly AssemblyConstants.RuntimeCapabilities RuntimeCapabilities; public readonly bool IsExecutable; + public readonly NonNullable ExecutionTarget; public readonly bool ExposeReferencesViaTestNames; - internal static ProjectProperties Default => - new ProjectProperties("Latest", "", AssemblyConstants.RuntimeCapabilities.Unknown, false, false); - - public ProjectProperties(string version, string outputPath, AssemblyConstants.RuntimeCapabilities runtimeCapabilities, bool isExecutable, bool loadTestNames) + public ProjectProperties( + string version, + string outputPath, + AssemblyConstants.RuntimeCapabilities runtimeCapabilities, + bool isExecutable, + NonNullable executionTarget, + bool loadTestNames) { this.Version = version ?? ""; this.OutputPath = outputPath ?? throw new ArgumentNullException(nameof(outputPath)); this.RuntimeCapabilities = runtimeCapabilities; this.IsExecutable = isExecutable; + this.ExecutionTarget = executionTarget; this.ExposeReferencesViaTestNames = loadTestNames; } } @@ -47,13 +52,34 @@ public class ProjectInformation public readonly ImmutableArray ProjectReferences; public readonly ImmutableArray References; - internal static ProjectInformation Empty(string version, string outputPath, AssemblyConstants.RuntimeCapabilities runtimeCapabilities) => - new ProjectInformation(version, outputPath, runtimeCapabilities, false, false, Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty()); - - public ProjectInformation(string version, string outputPath, AssemblyConstants.RuntimeCapabilities runtimeCapabilities, bool isExecutable, bool loadTestNames, - IEnumerable sourceFiles, IEnumerable projectReferences, IEnumerable references) + internal static ProjectInformation Empty( + string version, + string outputPath, + AssemblyConstants.RuntimeCapabilities runtimeCapabilities) => + new ProjectInformation( + version, + outputPath, + runtimeCapabilities, + false, + NonNullable.New("Unspecified"), + false, + Enumerable.Empty(), + Enumerable.Empty(), + Enumerable.Empty()); + + public ProjectInformation( + string version, + string outputPath, + AssemblyConstants.RuntimeCapabilities runtimeCapabilities, + bool isExecutable, + NonNullable executionTarget, + bool loadTestNames, + IEnumerable sourceFiles, + IEnumerable projectReferences, + IEnumerable references) { - this.Properties = new ProjectProperties(version, outputPath, runtimeCapabilities, isExecutable, loadTestNames); + this.Properties = new ProjectProperties( + version, outputPath, runtimeCapabilities, isExecutable, executionTarget, loadTestNames); this.SourceFiles = sourceFiles?.ToImmutableArray() ?? throw new ArgumentNullException(nameof(sourceFiles)); this.ProjectReferences = projectReferences?.ToImmutableArray() ?? throw new ArgumentNullException(nameof(projectReferences)); this.References = references?.ToImmutableArray() ?? throw new ArgumentNullException(nameof(references)); @@ -146,8 +172,12 @@ internal Project(Uri projectFile, ProjectInformation projectInfo, // but we don't do any semantic verification, and we don't publish diagnostics for them. this.Processing = new ProcessingQueue(onException); this.Manager = new CompilationUnitManager( - onException, ignore ? null : publishDiagnostics, syntaxCheckOnly: ignore, - this.Properties.RuntimeCapabilities, this.Properties.IsExecutable); + onException, + ignore ? null : publishDiagnostics, + syntaxCheckOnly: ignore, + this.Properties.RuntimeCapabilities, + this.Properties.IsExecutable, + this.Properties.ExecutionTarget); this.Log = log ?? ((msg, severity) => Console.WriteLine($"{severity}: {msg}")); this.LoadedSourceFiles = ImmutableHashSet.Empty; diff --git a/src/QsCompiler/CompilationManager/TypeChecking.cs b/src/QsCompiler/CompilationManager/TypeChecking.cs index 99934ce13e..9caeeaf924 100644 --- a/src/QsCompiler/CompilationManager/TypeChecking.cs +++ b/src/QsCompiler/CompilationManager/TypeChecking.cs @@ -10,14 +10,12 @@ using Microsoft.Quantum.QsCompiler.DataTypes; using Microsoft.Quantum.QsCompiler.Diagnostics; using Microsoft.Quantum.QsCompiler.SymbolManagement; -using Microsoft.Quantum.QsCompiler.SymbolTracker; +using Microsoft.Quantum.QsCompiler.SyntaxProcessing; using Microsoft.Quantum.QsCompiler.SyntaxTokens; using Microsoft.Quantum.QsCompiler.SyntaxTree; -using Microsoft.Quantum.QsCompiler.SyntaxProcessing; using Microsoft.Quantum.QsCompiler.TextProcessing; using Microsoft.VisualStudio.LanguageServer.Protocol; - namespace Microsoft.Quantum.QsCompiler.CompilationBuilder { using QsRangeInfo = QsNullable>; @@ -466,79 +464,81 @@ internal static void ImportGlobalSymbols(this FileContentManager file, Compilati // routines for building statements /// - /// Builds the QsScope containing the given list of tree nodes, - /// calling BuildStatement for each of them, and using the given symbol tracker to verify and track all symbols. - /// The declarations the scope inherits from its parents are assumed to be the current declarations in the given symbol tacker. - /// If a required set of functors are specified, then each operation called within the built scope needs to support these functors. - /// If the set of required functors is unspecified or null, then the functors to support are determined by the parent scope. - /// Throws an ArgumentNullException if the given list of tree nodes, symbol tracker or diagnostics are null. + /// Builds the QsScope containing the given list of tree nodes, + /// calling BuildStatement for each of them, and using the given scope context to verify and track all symbols. + /// The declarations the scope inherits from its parents are assumed to be the current declarations in the given scope context. + /// If a required set of functors are specified, then each operation called within the built scope needs to support these functors. + /// If the set of required functors is unspecified or null, then the functors to support are determined by the parent scope. + /// Throws an ArgumentNullException if the given list of tree nodes, scope context or diagnostics are null. /// - private static QsScope BuildScope(IReadOnlyList nodeContent, - SymbolTracker symbolTracker, List diagnostics, ImmutableHashSet requiredFunctorSupport = null) + private static QsScope BuildScope(IReadOnlyList nodeContent, + ScopeContext context, List diagnostics, ImmutableHashSet requiredFunctorSupport = null) { if (nodeContent == null) throw new ArgumentNullException(nameof(nodeContent)); - if (symbolTracker == null) throw new ArgumentNullException(nameof(symbolTracker)); + if (context == null) throw new ArgumentNullException(nameof(context)); if (diagnostics == null) throw new ArgumentNullException(nameof(diagnostics)); - var inheritedSymbols = symbolTracker.CurrentDeclarations; - symbolTracker.BeginScope(requiredFunctorSupport); - var statements = BuildStatements(nodeContent.GetEnumerator(), symbolTracker, diagnostics); - symbolTracker.EndScope(); + var inheritedSymbols = context.Symbols.CurrentDeclarations; + context.Symbols.BeginScope(requiredFunctorSupport); + var statements = BuildStatements(nodeContent.GetEnumerator(), context, diagnostics); + context.Symbols.EndScope(); return new QsScope(statements, inheritedSymbols); } /// - /// Applies the given build function to the position relative to the tree root and the given symbol tracker + /// Applies the given build function to the position relative to the tree root and the given scope context /// to get the desired object as well as a list of diagnostics. /// Adds the generated diagnostics to the given list of diagnostics, and returns the build object. - /// Throws an ArgumentNullException if the given build function, symbolTracker, or diagnostics are null. /// + /// + /// Thrown if the given build function, scope context, or diagnostics are null. + /// private static T BuildStatement(FragmentTree.TreeNode node, - Func,Tuple> build, - SymbolTracker symbolTracker, List diagnostics) + Func, Tuple> build, + ScopeContext context, List diagnostics) { if (build == null) throw new ArgumentNullException(nameof(build)); - if (symbolTracker == null) throw new ArgumentNullException(nameof(symbolTracker)); + if (context == null) throw new ArgumentNullException(nameof(context)); if (diagnostics == null) throw new ArgumentNullException(nameof(diagnostics)); var statementPos = node.Fragment.GetRange().Start; var location = new QsLocation(DiagnosticTools.AsTuple(node.GetPositionRelativeToRoot()), node.Fragment.HeaderRange); - var (statement, messages) = build(location, symbolTracker); - diagnostics.AddRange(messages.Select(msg => Diagnostics.Generate(symbolTracker.SourceFile.Value, msg, statementPos))); + var (statement, messages) = build(location, context); + diagnostics.AddRange(messages.Select(msg => Diagnostics.Generate(context.Symbols.SourceFile.Value, msg, statementPos))); return statement; } /// /// If the current tree node of the given iterator is a using-block intro, - /// builds the corresponding using-statement updating the given symbolTracker in the process, + /// builds the corresponding using-statement updating the given scope context in the process, /// and moves the iterator to the next node. - /// Adds the diagnostics generated during the building to the given list of diagnostics. + /// Adds the diagnostics generated during the building to the given list of diagnostics. /// Returns the built statement as out parameter, and returns true if the statement has been built. - /// Sets the out parameter to null and returns false otherwise. - /// Sets the boolean out parameter to true, if the iterator contains another node at the end of the routine - - /// i.e. it is set to true if either the iterator has not been moved (no statement built), - /// or if the last MoveNext() returned true, and is otherwise set to false. - /// This routine will fail if accessing the current iterator item fails. + /// Sets the out parameter to null and returns false otherwise. + /// Sets the boolean out parameter to true, if the iterator contains another node at the end of the routine - + /// i.e. it is set to true if either the iterator has not been moved (no statement built), + /// or if the last MoveNext() returned true, and is otherwise set to false. + /// This routine will fail if accessing the current iterator item fails. /// Throws an ArgumentNullException if any of the given arguments is null. - /// Throws an ArgumentException if the given symbol tracker does not currently contain an open scope. + /// Throws an ArgumentException if the given scope context does not currently contain an open scope. /// private static bool TryBuildUsingStatement(IEnumerator nodes, - SymbolTracker symbolTracker, List diagnostics, out bool proceed, out QsStatement statement) + ScopeContext context, List diagnostics, out bool proceed, out QsStatement statement) { if (nodes == null) throw new ArgumentNullException(nameof(nodes)); - if (symbolTracker == null) throw new ArgumentNullException(nameof(symbolTracker)); - if (symbolTracker.AllScopesClosed) throw new ArgumentException("invalid symbol tracker state - statements may only occur within a scope"); + if (context == null) throw new ArgumentNullException(nameof(context)); + if (context.Symbols.AllScopesClosed) throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); if (diagnostics == null) throw new ArgumentException(nameof(diagnostics)); if (nodes.Current.Fragment.Kind is QsFragmentKind.UsingBlockIntro allocate) { - symbolTracker.BeginScope(); // pushing a scope such that the declared variables are not available outside the body + context.Symbols.BeginScope(); // pushing a scope such that the declared variables are not available outside the body var allocationScope = BuildStatement(nodes.Current, - (relPos, symbols) => Statements.NewAllocateScope(nodes.Current.Fragment.Comments, relPos, symbols, allocate.Item1, allocate.Item2), - symbolTracker, diagnostics); - var body = BuildScope(nodes.Current.Children, symbolTracker, diagnostics); + (relPos, ctx) => Statements.NewAllocateScope(nodes.Current.Fragment.Comments, relPos, ctx, allocate.Item1, allocate.Item2), + context, diagnostics); + var body = BuildScope(nodes.Current.Children, context, diagnostics); statement = allocationScope(body); - symbolTracker.EndScope(); + context.Symbols.EndScope(); proceed = nodes.MoveNext(); return true; } @@ -548,35 +548,35 @@ private static bool TryBuildUsingStatement(IEnumerator no /// /// If the current tree node of the given iterator is a borrowing-block intro, - /// builds the corresponding borrowing-statement updating the given symbolTracker in the process, + /// builds the corresponding borrowing-statement updating the given scope context in the process, /// and moves the iterator to the next node. - /// Adds the diagnostics generated during the building to the given list of diagnostics. + /// Adds the diagnostics generated during the building to the given list of diagnostics. /// Returns the built statement as out parameter, and returns true if the statement has been built. - /// Sets the out parameter to null and returns false otherwise. - /// Sets the boolean out parameter to true, if the iterator contains another node at the end of the routine - - /// i.e. it is set to true if either the iterator has not been moved (no statement built), - /// or if the last MoveNext() returned true, and is otherwise set to false. - /// This routine will fail if accessing the current iterator item fails. + /// Sets the out parameter to null and returns false otherwise. + /// Sets the boolean out parameter to true, if the iterator contains another node at the end of the routine - + /// i.e. it is set to true if either the iterator has not been moved (no statement built), + /// or if the last MoveNext() returned true, and is otherwise set to false. + /// This routine will fail if accessing the current iterator item fails. /// Throws an ArgumentNullException if any of the given arguments is null. - /// Throws an ArgumentException if the given symbol tracker does not currently contain an open scope. + /// Throws an ArgumentException if the given scope context does not currently contain an open scope. /// private static bool TryBuildBorrowStatement(IEnumerator nodes, - SymbolTracker symbolTracker, List diagnostics, out bool proceed, out QsStatement statement) + ScopeContext context, List diagnostics, out bool proceed, out QsStatement statement) { if (nodes == null) throw new ArgumentNullException(nameof(nodes)); - if (symbolTracker == null) throw new ArgumentNullException(nameof(symbolTracker)); - if (symbolTracker.AllScopesClosed) throw new ArgumentException("invalid symbol tracker state - statements may only occur within a scope"); + if (context == null) throw new ArgumentNullException(nameof(context)); + if (context.Symbols.AllScopesClosed) throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); if (diagnostics == null) throw new ArgumentException(nameof(diagnostics)); if (nodes.Current.Fragment.Kind is QsFragmentKind.BorrowingBlockIntro borrow) { - symbolTracker.BeginScope(); // pushing a scope such that the declared variables are not available outside the body + context.Symbols.BeginScope(); // pushing a scope such that the declared variables are not available outside the body var borrowingScope = BuildStatement(nodes.Current, - (relPos, symbols) => Statements.NewBorrowScope(nodes.Current.Fragment.Comments, relPos, symbols, borrow.Item1, borrow.Item2), - symbolTracker, diagnostics); - var body = BuildScope(nodes.Current.Children, symbolTracker, diagnostics); + (relPos, ctx) => Statements.NewBorrowScope(nodes.Current.Fragment.Comments, relPos, ctx, borrow.Item1, borrow.Item2), + context, diagnostics); + var body = BuildScope(nodes.Current.Children, context, diagnostics); statement = borrowingScope(body); - symbolTracker.EndScope(); + context.Symbols.EndScope(); proceed = nodes.MoveNext(); return true; } @@ -586,53 +586,53 @@ private static bool TryBuildBorrowStatement(IEnumerator n /// /// If the current tree node of the given iterator is a repeat-until-success (RUS) intro, - /// builds the corresponding RUS-statement updating the given symbolTracker in the process, + /// builds the corresponding RUS-statement updating the given scope context in the process, /// and moves the iterator to the next node. - /// Adds the diagnostics generated during the building to the given list of diagnostics. + /// Adds the diagnostics generated during the building to the given list of diagnostics. /// Returns the built statement as out parameter, and returns true if the statement has been built. - /// Sets the out parameter to null and returns false otherwise. - /// Sets the boolean out parameter to true, if the iterator contains another node at the end of the routine - - /// i.e. it is set to true if either the iterator has not been moved (no statement built), - /// or if the last MoveNext() returned true, and is otherwise set to false. - /// This routine will fail if accessing the current iterator item fails. + /// Sets the out parameter to null and returns false otherwise. + /// Sets the boolean out parameter to true, if the iterator contains another node at the end of the routine - + /// i.e. it is set to true if either the iterator has not been moved (no statement built), + /// or if the last MoveNext() returned true, and is otherwise set to false. + /// This routine will fail if accessing the current iterator item fails. /// Throws an ArgumentNullException if any of the given arguments is null. - /// Throws an ArgumentException if the given symbol tracker does not currently contain an open scope, + /// Throws an ArgumentException if the given scope context does not currently contain an open scope, /// or if the repeat header is not followed by a until-success clause. /// private static bool TryBuildRepeatStatement(IEnumerator nodes, - SymbolTracker symbolTracker, List diagnostics, out bool proceed, out QsStatement statement) + ScopeContext context, List diagnostics, out bool proceed, out QsStatement statement) { if (nodes == null) throw new ArgumentNullException(nameof(nodes)); - if (symbolTracker == null) throw new ArgumentNullException(nameof(symbolTracker)); - if (symbolTracker.AllScopesClosed) throw new ArgumentException("invalid symbol tracker state - statements may only occur within a scope"); + if (context == null) throw new ArgumentNullException(nameof(context)); + if (context.Symbols.AllScopesClosed) throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); if (diagnostics == null) throw new ArgumentException(nameof(diagnostics)); if (nodes.Current.Fragment.Kind.IsRepeatIntro) { - symbolTracker.BeginScope(); + context.Symbols.BeginScope(); var headerNode = nodes.Current; - var inheritedSymbols = symbolTracker.CurrentDeclarations; - var repeatBody = new QsScope(BuildStatements(nodes.Current.Children.GetEnumerator(), symbolTracker, diagnostics), inheritedSymbols); + var inheritedSymbols = context.Symbols.CurrentDeclarations; + var repeatBody = new QsScope(BuildStatements(nodes.Current.Children.GetEnumerator(), context, diagnostics), inheritedSymbols); if (nodes.MoveNext() && nodes.Current.Fragment.Kind is QsFragmentKind.UntilSuccess untilCond) { - inheritedSymbols = symbolTracker.CurrentDeclarations; + inheritedSymbols = context.Symbols.CurrentDeclarations; var conditionalBlock = BuildStatement(nodes.Current, - (relPos, symbols) => Statements.NewConditionalBlock(nodes.Current.Fragment.Comments, relPos, symbols, untilCond.Item1), - symbolTracker, diagnostics); + (relPos, ctx) => Statements.NewConditionalBlock(nodes.Current.Fragment.Comments, relPos, ctx, untilCond.Item1), + context, diagnostics); var fixupBody = untilCond.Item2 - ? new QsScope(BuildStatements(nodes.Current.Children.GetEnumerator(), symbolTracker, diagnostics), inheritedSymbols) + ? new QsScope(BuildStatements(nodes.Current.Children.GetEnumerator(), context, diagnostics), inheritedSymbols) : new QsScope(ImmutableArray.Empty, inheritedSymbols); var (successCondition, fixupBlock) = conditionalBlock(fixupBody); // here, condition = true implies the block is *not* executed - symbolTracker.EndScope(); + context.Symbols.EndScope(); statement = BuildStatement(headerNode, - (relPos, symbols) => + (relPos, ctx) => { var repeatBlock = new QsPositionedBlock(repeatBody, QsNullable.NewValue(relPos), headerNode.Fragment.Comments); - return Statements.NewRepeatStatement(symbols, repeatBlock, successCondition, fixupBlock); - } - , symbolTracker, diagnostics); + return Statements.NewRepeatStatement(ctx.Symbols, repeatBlock, successCondition, fixupBlock); + }, + context, diagnostics); proceed = nodes.MoveNext(); return true; } @@ -644,35 +644,35 @@ private static bool TryBuildRepeatStatement(IEnumerator n /// /// If the current tree node of the given iterator is a for-loop intro, - /// builds the corresponding for-statement updating the given symbolTracker in the process, + /// builds the corresponding for-statement updating the given scope context in the process, /// and moves the iterator to the next node. - /// Adds the diagnostics generated during the building to the given list of diagnostics. + /// Adds the diagnostics generated during the building to the given list of diagnostics. /// Returns the built statement as out parameter, and returns true if the statement has been built. - /// Sets the out parameter to null and returns false otherwise. - /// Sets the boolean out parameter to true, if the iterator contains another node at the end of the routine - - /// i.e. it is set to true if either the iterator has not been moved (no statement built), - /// or if the last MoveNext() returned true, and is otherwise set to false. - /// This routine will fail if accessing the current iterator item fails. + /// Sets the out parameter to null and returns false otherwise. + /// Sets the boolean out parameter to true, if the iterator contains another node at the end of the routine - + /// i.e. it is set to true if either the iterator has not been moved (no statement built), + /// or if the last MoveNext() returned true, and is otherwise set to false. + /// This routine will fail if accessing the current iterator item fails. /// Throws an ArgumentNullException if any of the given arguments is null. - /// Throws an ArgumentException if the given symbol tracker does not currently contain an open scope. + /// Throws an ArgumentException if the given scope context does not currently contain an open scope. /// private static bool TryBuildForStatement(IEnumerator nodes, - SymbolTracker symbolTracker, List diagnostics, out bool proceed, out QsStatement statement) + ScopeContext context, List diagnostics, out bool proceed, out QsStatement statement) { if (nodes == null) throw new ArgumentNullException(nameof(nodes)); - if (symbolTracker == null) throw new ArgumentNullException(nameof(symbolTracker)); - if (symbolTracker.AllScopesClosed) throw new ArgumentException("invalid symbol tracker state - statements may only occur within a scope"); + if (context == null) throw new ArgumentNullException(nameof(context)); + if (context.Symbols.AllScopesClosed) throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); if (diagnostics == null) throw new ArgumentException(nameof(diagnostics)); if (nodes.Current.Fragment.Kind is QsFragmentKind.ForLoopIntro forStatement) { - symbolTracker.BeginScope(); // pushing a scope such that the declared variables are not available outside the body + context.Symbols.BeginScope(); // pushing a scope such that the declared variables are not available outside the body var forLoop = BuildStatement(nodes.Current, (relPos, symbols) => Statements.NewForStatement(nodes.Current.Fragment.Comments, relPos, symbols, forStatement.Item1, forStatement.Item2), - symbolTracker, diagnostics); - var body = BuildScope(nodes.Current.Children, symbolTracker, diagnostics); + context, diagnostics); + var body = BuildScope(nodes.Current.Children, context, diagnostics); statement = forLoop(body); - symbolTracker.EndScope(); + context.Symbols.EndScope(); proceed = nodes.MoveNext(); return true; } @@ -682,35 +682,35 @@ private static bool TryBuildForStatement(IEnumerator node /// /// If the current tree node of the given iterator is a while-loop intro, - /// builds the corresponding while-statement updating the given symbolTracker in the process, + /// builds the corresponding while-statement updating the given scope context in the process, /// and moves the iterator to the next node. - /// Adds the diagnostics generated during the building to the given list of diagnostics. + /// Adds the diagnostics generated during the building to the given list of diagnostics. /// Returns the built statement as out parameter, and returns true if the statement has been built. - /// Sets the out parameter to null and returns false otherwise. - /// Sets the boolean out parameter to true, if the iterator contains another node at the end of the routine - - /// i.e. it is set to true if either the iterator has not been moved (no statement built), - /// or if the last MoveNext() returned true, and is otherwise set to false. - /// This routine will fail if accessing the current iterator item fails. + /// Sets the out parameter to null and returns false otherwise. + /// Sets the boolean out parameter to true, if the iterator contains another node at the end of the routine - + /// i.e. it is set to true if either the iterator has not been moved (no statement built), + /// or if the last MoveNext() returned true, and is otherwise set to false. + /// This routine will fail if accessing the current iterator item fails. /// Throws an ArgumentNullException if any of the given arguments is null. - /// Throws an ArgumentException if the given symbol tracker does not currently contain an open scope. + /// Throws an ArgumentException if the given scope context does not currently contain an open scope. /// private static bool TryBuildWhileStatement(IEnumerator nodes, - SymbolTracker symbolTracker, List diagnostics, out bool proceed, out QsStatement statement) + ScopeContext context, List diagnostics, out bool proceed, out QsStatement statement) { if (nodes == null) throw new ArgumentNullException(nameof(nodes)); - if (symbolTracker == null) throw new ArgumentNullException(nameof(symbolTracker)); - if (symbolTracker.AllScopesClosed) throw new ArgumentException("invalid symbol tracker state - statements may only occur within a scope"); + if (context == null) throw new ArgumentNullException(nameof(context)); + if (context.Symbols.AllScopesClosed) throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); if (diagnostics == null) throw new ArgumentException(nameof(diagnostics)); if (nodes.Current.Fragment.Kind is QsFragmentKind.WhileLoopIntro whileStatement) { - symbolTracker.BeginScope(); // pushing a scope such that the declared variables are not available outside the body + context.Symbols.BeginScope(); // pushing a scope such that the declared variables are not available outside the body var whileLoop = BuildStatement(nodes.Current, - (relPos, symbols) => Statements.NewWhileStatement(nodes.Current.Fragment.Comments, relPos, symbols, whileStatement.Item), - symbolTracker, diagnostics); - var body = BuildScope(nodes.Current.Children, symbolTracker, diagnostics); + (relPos, ctx) => Statements.NewWhileStatement(nodes.Current.Fragment.Comments, relPos, ctx, whileStatement.Item), + context, diagnostics); + var body = BuildScope(nodes.Current.Children, context, diagnostics); statement = whileLoop(body); - symbolTracker.EndScope(); + context.Symbols.EndScope(); proceed = nodes.MoveNext(); return true; } @@ -720,33 +720,33 @@ private static bool TryBuildWhileStatement(IEnumerator no /// /// If the current tree node of the given iterator is an if-statement into, - /// builds the corresponding if-statement updating the given symbolTracker in the process, + /// builds the corresponding if-statement updating the given scope context in the process, /// and moves the iterator to the next node. - /// Adds the diagnostics generated during the building to the given list of diagnostics. + /// Adds the diagnostics generated during the building to the given list of diagnostics. /// Returns the built statement as out parameter, and returns true if the statement has been built. - /// Sets the out parameter to null and returns false otherwise. - /// Sets the boolean out parameter to true, if the iterator contains another node at the end of the routine - - /// i.e. it is set to true if either the iterator has not been moved (no statement built), - /// or if the last MoveNext() returned true, and is otherwise set to false. - /// This routine will fail if accessing the current iterator item fails. + /// Sets the out parameter to null and returns false otherwise. + /// Sets the boolean out parameter to true, if the iterator contains another node at the end of the routine - + /// i.e. it is set to true if either the iterator has not been moved (no statement built), + /// or if the last MoveNext() returned true, and is otherwise set to false. + /// This routine will fail if accessing the current iterator item fails. /// Throws an ArgumentNullException if any of the given arguments is null. - /// Throws an ArgumentException if the given symbol tracker does not currently contain an open scope. + /// Throws an ArgumentException if the given scope context does not currently contain an open scope. /// private static bool TryBuildIfStatement(IEnumerator nodes, - SymbolTracker symbolTracker, List diagnostics, out bool proceed, out QsStatement statement) + ScopeContext context, List diagnostics, out bool proceed, out QsStatement statement) { if (nodes == null) throw new ArgumentNullException(nameof(nodes)); - if (symbolTracker == null) throw new ArgumentNullException(nameof(symbolTracker)); - if (symbolTracker.AllScopesClosed) throw new ArgumentException("invalid symbol tracker state - statements may only occur within a scope"); + if (context == null) throw new ArgumentNullException(nameof(context)); + if (context.Symbols.AllScopesClosed) throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); if (diagnostics == null) throw new ArgumentException(nameof(diagnostics)); if (nodes.Current.Fragment.Kind is QsFragmentKind.IfClause ifCond) { // if block var buildClause = BuildStatement(nodes.Current, - (relPos, symbols) => Statements.NewConditionalBlock(nodes.Current.Fragment.Comments, relPos, symbols, ifCond.Item), - symbolTracker, diagnostics); - var ifBlock = buildClause(BuildScope(nodes.Current.Children, symbolTracker, diagnostics)); + (relPos, ctx) => Statements.NewConditionalBlock(nodes.Current.Fragment.Comments, relPos, ctx, ifCond.Item), + context, diagnostics); + var ifBlock = buildClause(BuildScope(nodes.Current.Children, context, diagnostics)); // elif blocks proceed = nodes.MoveNext(); @@ -754,9 +754,9 @@ private static bool TryBuildIfStatement(IEnumerator nodes while (proceed && nodes.Current.Fragment.Kind is QsFragmentKind.ElifClause elifCond) { buildClause = BuildStatement(nodes.Current, - (relPos, symbols) => Statements.NewConditionalBlock(nodes.Current.Fragment.Comments, relPos, symbols, elifCond.Item), - symbolTracker, diagnostics); - elifBlocks.Add(buildClause(BuildScope(nodes.Current.Children, symbolTracker, diagnostics))); + (relPos, ctx) => Statements.NewConditionalBlock(nodes.Current.Fragment.Comments, relPos, ctx, elifCond.Item), + context, diagnostics); + elifBlocks.Add(buildClause(BuildScope(nodes.Current.Children, context, diagnostics))); proceed = nodes.MoveNext(); } @@ -764,7 +764,7 @@ private static bool TryBuildIfStatement(IEnumerator nodes var elseBlock = QsNullable.Null; if (proceed && nodes.Current.Fragment.Kind.IsElseClause) { - var scope = BuildScope(nodes.Current.Children, symbolTracker, diagnostics); + var scope = BuildScope(nodes.Current.Children, context, diagnostics); var elseLocation = new QsLocation(DiagnosticTools.AsTuple(nodes.Current.GetPositionRelativeToRoot()), nodes.Current.Fragment.HeaderRange); elseBlock = QsNullable.NewValue( new QsPositionedBlock(scope, QsNullable.NewValue(elseLocation), nodes.Current.Fragment.Comments)); @@ -780,24 +780,24 @@ private static bool TryBuildIfStatement(IEnumerator nodes /// /// If the current tree node of the given iterator is a within-block intro, - /// builds the corresponding conjugation updating the given symbolTracker in the process, + /// builds the corresponding conjugation updating the given scope context in the process, /// and moves the iterator to the next node. - /// Adds the diagnostics generated during the building to the given list of diagnostics. + /// Adds the diagnostics generated during the building to the given list of diagnostics. /// Returns the built statement as out parameter, and returns true if the statement has been built. - /// Sets the out parameter to null and returns false otherwise. - /// Sets the boolean out parameter to true, if the iterator contains another node at the end of the routine - - /// i.e. it is set to true if either the iterator has not been moved (no statement built), - /// or if the last MoveNext() returned true, and is otherwise set to false. - /// This routine will fail if accessing the current iterator item fails. + /// Sets the out parameter to null and returns false otherwise. + /// Sets the boolean out parameter to true, if the iterator contains another node at the end of the routine - + /// i.e. it is set to true if either the iterator has not been moved (no statement built), + /// or if the last MoveNext() returned true, and is otherwise set to false. + /// This routine will fail if accessing the current iterator item fails. /// Throws an ArgumentNullException if any of the given arguments is null. - /// Throws an ArgumentException if the given symbol tracker does not currently contain an open scope. + /// Throws an ArgumentException if the given scope context does not currently contain an open scope. /// private static bool TryBuildConjugationStatement(IEnumerator nodes, - SymbolTracker symbolTracker, List diagnostics, out bool proceed, out QsStatement statement) + ScopeContext context, List diagnostics, out bool proceed, out QsStatement statement) { if (nodes == null) throw new ArgumentNullException(nameof(nodes)); - if (symbolTracker == null) throw new ArgumentNullException(nameof(symbolTracker)); - if (symbolTracker.AllScopesClosed) throw new ArgumentException("invalid symbol tracker state - statements may only occur within a scope"); + if (context == null) throw new ArgumentNullException(nameof(context)); + if (context.Symbols.AllScopesClosed) throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); if (diagnostics == null) throw new ArgumentException(nameof(diagnostics)); QsNullable RelativeLocation(FragmentTree.TreeNode node) => @@ -808,15 +808,15 @@ QsNullable RelativeLocation(FragmentTree.TreeNode node) => // The requirement for outer blocks in conjugations is always that an adjoint can be auto-generated for them, // independent on what functor specializations need to be auto-generated for the containing operation. var requiredFunctorSupport = ImmutableHashSet.Create(QsFunctor.Adjoint); - var outerTranformation = BuildScope(nodes.Current.Children, symbolTracker, diagnostics, requiredFunctorSupport); + var outerTranformation = BuildScope(nodes.Current.Children, context, diagnostics, requiredFunctorSupport); var outer = new QsPositionedBlock(outerTranformation, RelativeLocation(nodes.Current), nodes.Current.Fragment.Comments); if (nodes.MoveNext() && nodes.Current.Fragment.Kind.IsApplyBlockIntro) { - var innerTransformation = BuildScope(nodes.Current.Children, symbolTracker, diagnostics); + 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(symbolTracker.SourceFile.Value, msg.Item2, nodes.Current.GetRootPosition()))); + diagnostics.AddRange(built.Item2.Select(msg => Diagnostics.Generate(context.Symbols.SourceFile.Value, msg.Item2, nodes.Current.GetRootPosition()))); statement = built.Item1; proceed = nodes.MoveNext(); @@ -830,31 +830,31 @@ QsNullable RelativeLocation(FragmentTree.TreeNode node) => /// /// If the current tree node of the given iterator is a let-statement, - /// builds the corresponding let-statement updating the given symbolTracker in the process, + /// builds the corresponding let-statement updating the given scope context in the process, /// and moves the iterator to the next node. - /// Adds the diagnostics generated during the building to the given list of diagnostics. + /// Adds the diagnostics generated during the building to the given list of diagnostics. /// Returns the built statement as out parameter, and returns true if the statement has been built. - /// Sets the out parameter to null and returns false otherwise. - /// Sets the boolean out parameter to true, if the iterator contains another node at the end of the routine - - /// i.e. it is set to true if either the iterator has not been moved (no statement built), - /// or if the last MoveNext() returned true, and is otherwise set to false. - /// This routine will fail if accessing the current iterator item fails. + /// Sets the out parameter to null and returns false otherwise. + /// Sets the boolean out parameter to true, if the iterator contains another node at the end of the routine - + /// i.e. it is set to true if either the iterator has not been moved (no statement built), + /// or if the last MoveNext() returned true, and is otherwise set to false. + /// This routine will fail if accessing the current iterator item fails. /// Throws an ArgumentNullException if any of the given arguments is null. - /// Throws an ArgumentException if the given symbol tracker does not currently contain an open scope. + /// Throws an ArgumentException if the given scope context does not currently contain an open scope. /// private static bool TryBuildLetStatement(IEnumerator nodes, - SymbolTracker symbolTracker, List diagnostics, out bool proceed, out QsStatement statement) + ScopeContext context, List diagnostics, out bool proceed, out QsStatement statement) { if (nodes == null) throw new ArgumentNullException(nameof(nodes)); - if (symbolTracker == null) throw new ArgumentNullException(nameof(symbolTracker)); - if (symbolTracker.AllScopesClosed) throw new ArgumentException("invalid symbol tracker state - statements may only occur within a scope"); + if (context == null) throw new ArgumentNullException(nameof(context)); + if (context.Symbols.AllScopesClosed) throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); if (diagnostics == null) throw new ArgumentException(nameof(diagnostics)); if (nodes.Current.Fragment.Kind is QsFragmentKind.ImmutableBinding letStatement) { statement = BuildStatement(nodes.Current, - (relPos, symbols) => Statements.NewImmutableBinding(nodes.Current.Fragment.Comments, relPos, symbols, letStatement.Item1, letStatement.Item2), - symbolTracker, diagnostics); + (relPos, ctx) => Statements.NewImmutableBinding(nodes.Current.Fragment.Comments, relPos, ctx, letStatement.Item1, letStatement.Item2), + context, diagnostics); proceed = nodes.MoveNext(); return true; } @@ -864,31 +864,31 @@ private static bool TryBuildLetStatement(IEnumerator node /// /// If the current tree node of the given iterator is a mutable-statement, - /// builds the corresponding mutable-statement updating the given symbolTracker in the process, + /// builds the corresponding mutable-statement updating the given scope context in the process, /// and moves the iterator to the next node. - /// Adds the diagnostics generated during the building to the given list of diagnostics. + /// Adds the diagnostics generated during the building to the given list of diagnostics. /// Returns the built statement as out parameter, and returns true if the statement has been built. - /// Sets the out parameter to null and returns false otherwise. - /// Sets the boolean out parameter to true, if the iterator contains another node at the end of the routine - - /// i.e. it is set to true if either the iterator has not been moved (no statement built), - /// or if the last MoveNext() returned true, and is otherwise set to false. - /// This routine will fail if accessing the current iterator item fails. + /// Sets the out parameter to null and returns false otherwise. + /// Sets the boolean out parameter to true, if the iterator contains another node at the end of the routine - + /// i.e. it is set to true if either the iterator has not been moved (no statement built), + /// or if the last MoveNext() returned true, and is otherwise set to false. + /// This routine will fail if accessing the current iterator item fails. /// Throws an ArgumentNullException if any of the given arguments is null. - /// Throws an ArgumentException if the given symbol tracker does not currently contain an open scope. + /// Throws an ArgumentException if the given scope context does not currently contain an open scope. /// private static bool TryBuildMutableStatement(IEnumerator nodes, - SymbolTracker symbolTracker, List diagnostics, out bool proceed, out QsStatement statement) + ScopeContext context, List diagnostics, out bool proceed, out QsStatement statement) { if (nodes == null) throw new ArgumentNullException(nameof(nodes)); - if (symbolTracker == null) throw new ArgumentNullException(nameof(symbolTracker)); - if (symbolTracker.AllScopesClosed) throw new ArgumentException("invalid symbol tracker state - statements may only occur within a scope"); + if (context == null) throw new ArgumentNullException(nameof(context)); + if (context.Symbols.AllScopesClosed) throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); if (diagnostics == null) throw new ArgumentException(nameof(diagnostics)); if (nodes.Current.Fragment.Kind is QsFragmentKind.MutableBinding mutableStatement) { statement = BuildStatement(nodes.Current, - (relPos, symbols) => Statements.NewMutableBinding(nodes.Current.Fragment.Comments, relPos, symbols, mutableStatement.Item1, mutableStatement.Item2), - symbolTracker, diagnostics); + (relPos, ctx) => Statements.NewMutableBinding(nodes.Current.Fragment.Comments, relPos, ctx, mutableStatement.Item1, mutableStatement.Item2), + context, diagnostics); proceed = nodes.MoveNext(); return true; } @@ -898,31 +898,31 @@ private static bool TryBuildMutableStatement(IEnumerator /// /// If the current tree node of the given iterator is a set-statement, - /// builds the corresponding set-statement updating the given symbolTracker in the process, + /// builds the corresponding set-statement updating the given scope context in the process, /// and moves the iterator to the next node. - /// Adds the diagnostics generated during the building to the given list of diagnostics. + /// Adds the diagnostics generated during the building to the given list of diagnostics. /// Returns the built statement as out parameter, and returns true if the statement has been built. - /// Sets the out parameter to null and returns false otherwise. - /// Sets the boolean out parameter to true, if the iterator contains another node at the end of the routine - - /// i.e. it is set to true if either the iterator has not been moved (no statement built), - /// or if the last MoveNext() returned true, and is otherwise set to false. - /// This routine will fail if accessing the current iterator item fails. + /// Sets the out parameter to null and returns false otherwise. + /// Sets the boolean out parameter to true, if the iterator contains another node at the end of the routine - + /// i.e. it is set to true if either the iterator has not been moved (no statement built), + /// or if the last MoveNext() returned true, and is otherwise set to false. + /// This routine will fail if accessing the current iterator item fails. /// Throws an ArgumentNullException if any of the given arguments is null. - /// Throws an ArgumentException if the given symbol tracker does not currently contain an open scope. + /// Throws an ArgumentException if the given scope context does not currently contain an open scope. /// private static bool TryBuildSetStatement(IEnumerator nodes, - SymbolTracker symbolTracker, List diagnostics, out bool proceed, out QsStatement statement) + ScopeContext context, List diagnostics, out bool proceed, out QsStatement statement) { if (nodes == null) throw new ArgumentNullException(nameof(nodes)); - if (symbolTracker == null) throw new ArgumentNullException(nameof(symbolTracker)); - if (symbolTracker.AllScopesClosed) throw new ArgumentException("invalid symbol tracker state - statements may only occur within a scope"); + if (context == null) throw new ArgumentNullException(nameof(context)); + if (context.Symbols.AllScopesClosed) throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); if (diagnostics == null) throw new ArgumentException(nameof(diagnostics)); if (nodes.Current.Fragment.Kind is QsFragmentKind.ValueUpdate setStatement) { statement = BuildStatement(nodes.Current, - (relPos, symbols) => Statements.NewValueUpdate(nodes.Current.Fragment.Comments, relPos, symbols, setStatement.Item1, setStatement.Item2), - symbolTracker, diagnostics); + (relPos, ctx) => Statements.NewValueUpdate(nodes.Current.Fragment.Comments, relPos, ctx, setStatement.Item1, setStatement.Item2), + context, diagnostics); proceed = nodes.MoveNext(); return true; } @@ -932,31 +932,31 @@ private static bool TryBuildSetStatement(IEnumerator node /// /// If the current tree node of the given iterator is a fail-statement, - /// builds the corresponding fail-statement updating the given symbolTracker in the process, + /// builds the corresponding fail-statement updating the given scope context in the process, /// and moves the iterator to the next node. - /// Adds the diagnostics generated during the building to the given list of diagnostics. + /// Adds the diagnostics generated during the building to the given list of diagnostics. /// Returns the built statement as out parameter, and returns true if the statement has been built. - /// Sets the out parameter to null and returns false otherwise. - /// Sets the boolean out parameter to true, if the iterator contains another node at the end of the routine - - /// i.e. it is set to true if either the iterator has not been moved (no statement built), - /// or if the last MoveNext() returned true, and is otherwise set to false. - /// This routine will fail if accessing the current iterator item fails. + /// Sets the out parameter to null and returns false otherwise. + /// Sets the boolean out parameter to true, if the iterator contains another node at the end of the routine - + /// i.e. it is set to true if either the iterator has not been moved (no statement built), + /// or if the last MoveNext() returned true, and is otherwise set to false. + /// This routine will fail if accessing the current iterator item fails. /// Throws an ArgumentNullException if any of the given arguments is null. - /// Throws an ArgumentException if the given symbol tracker does not currently contain an open scope. + /// Throws an ArgumentException if the given scope context does not currently contain an open scope. /// private static bool TryBuildFailStatement(IEnumerator nodes, - SymbolTracker symbolTracker, List diagnostics, out bool proceed, out QsStatement statement) + ScopeContext context, List diagnostics, out bool proceed, out QsStatement statement) { if (nodes == null) throw new ArgumentNullException(nameof(nodes)); - if (symbolTracker == null) throw new ArgumentNullException(nameof(symbolTracker)); - if (symbolTracker.AllScopesClosed) throw new ArgumentException("invalid symbol tracker state - statements may only occur within a scope"); + if (context == null) throw new ArgumentNullException(nameof(context)); + if (context.Symbols.AllScopesClosed) throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); if (diagnostics == null) throw new ArgumentException(nameof(diagnostics)); if (nodes.Current.Fragment.Kind is QsFragmentKind.FailStatement failStatement) { statement = BuildStatement(nodes.Current, - (relPos, symbols) => Statements.NewFailStatement(nodes.Current.Fragment.Comments, relPos, symbols, failStatement.Item), - symbolTracker, diagnostics); + (relPos, ctx) => Statements.NewFailStatement(nodes.Current.Fragment.Comments, relPos, ctx, failStatement.Item), + context, diagnostics); proceed = nodes.MoveNext(); return true; } @@ -966,31 +966,31 @@ private static bool TryBuildFailStatement(IEnumerator nod /// /// If the current tree node of the given iterator is a return-statement, - /// builds the corresponding return-statement updating the given symbolTracker in the process, + /// builds the corresponding return-statement updating the given scope context in the process, /// and moves the iterator to the next node. - /// Adds the diagnostics generated during the building to the given list of diagnostics. + /// Adds the diagnostics generated during the building to the given list of diagnostics. /// Returns the built statement as out parameter, and returns true if the statement has been built. - /// Sets the out parameter to null and returns false otherwise. - /// Sets the boolean out parameter to true, if the iterator contains another node at the end of the routine - - /// i.e. it is set to true if either the iterator has not been moved (no statement built), - /// or if the last MoveNext() returned true, and is otherwise set to false. - /// This routine will fail if accessing the current iterator item fails. + /// Sets the out parameter to null and returns false otherwise. + /// Sets the boolean out parameter to true, if the iterator contains another node at the end of the routine - + /// i.e. it is set to true if either the iterator has not been moved (no statement built), + /// or if the last MoveNext() returned true, and is otherwise set to false. + /// This routine will fail if accessing the current iterator item fails. /// Throws an ArgumentNullException if any of the given arguments is null. - /// Throws an ArgumentException if the given symbol tracker does not currently contain an open scope. + /// Throws an ArgumentException if the given scope context does not currently contain an open scope. /// private static bool TryBuildReturnStatement(IEnumerator nodes, - SymbolTracker symbolTracker, List diagnostics, out bool proceed, out QsStatement statement) + ScopeContext context, List diagnostics, out bool proceed, out QsStatement statement) { if (nodes == null) throw new ArgumentNullException(nameof(nodes)); - if (symbolTracker == null) throw new ArgumentNullException(nameof(symbolTracker)); - if (symbolTracker.AllScopesClosed) throw new ArgumentException("invalid symbol tracker state - statements may only occur within a scope"); + if (context == null) throw new ArgumentNullException(nameof(context)); + if (context.Symbols.AllScopesClosed) throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); if (diagnostics == null) throw new ArgumentException(nameof(diagnostics)); if (nodes.Current.Fragment.Kind is QsFragmentKind.ReturnStatement returnStatement) { statement = BuildStatement(nodes.Current, - (relPos, symbols) => Statements.NewReturnStatement(nodes.Current.Fragment.Comments, relPos, symbols, returnStatement.Item), - symbolTracker, diagnostics); + (relPos, ctx) => Statements.NewReturnStatement(nodes.Current.Fragment.Comments, relPos, ctx, returnStatement.Item), + context, diagnostics); proceed = nodes.MoveNext(); return true; } @@ -1000,31 +1000,31 @@ private static bool TryBuildReturnStatement(IEnumerator n /// /// If the current tree node of the given iterator is an expression-statement, - /// builds the corresponding expression-statement updating the given symbolTracker in the process, + /// builds the corresponding expression-statement updating the given scope context in the process, /// and moves the iterator to the next node. - /// Adds the diagnostics generated during the building to the given list of diagnostics. + /// Adds the diagnostics generated during the building to the given list of diagnostics. /// Returns the built statement as out parameter, and returns true if the statement has been built. - /// Sets the out parameter to null and returns false otherwise. - /// Sets the boolean out parameter to true, if the iterator contains another node at the end of the routine - - /// i.e. it is set to true if either the iterator has not been moved (no statement built), - /// or if the last MoveNext() returned true, and is otherwise set to false. - /// This routine will fail if accessing the current iterator item fails. + /// Sets the out parameter to null and returns false otherwise. + /// Sets the boolean out parameter to true, if the iterator contains another node at the end of the routine - + /// i.e. it is set to true if either the iterator has not been moved (no statement built), + /// or if the last MoveNext() returned true, and is otherwise set to false. + /// This routine will fail if accessing the current iterator item fails. /// Throws an ArgumentNullException if any of the given arguments is null. - /// Throws an ArgumentException if the given symbol tracker does not currently contain an open scope. + /// Throws an ArgumentException if the given scope context does not currently contain an open scope. /// private static bool TryBuildExpressionStatement(IEnumerator nodes, - SymbolTracker symbolTracker, List diagnostics, out bool proceed, out QsStatement statement) + ScopeContext context, List diagnostics, out bool proceed, out QsStatement statement) { if (nodes == null) throw new ArgumentNullException(nameof(nodes)); - if (symbolTracker == null) throw new ArgumentNullException(nameof(symbolTracker)); - if (symbolTracker.AllScopesClosed) throw new ArgumentException("invalid symbol tracker state - statements may only occur within a scope"); + if (context == null) throw new ArgumentNullException(nameof(context)); + if (context.Symbols.AllScopesClosed) throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); if (diagnostics == null) throw new ArgumentException(nameof(diagnostics)); if (nodes.Current.Fragment.Kind is QsFragmentKind.ExpressionStatement expressionStatement) { statement = BuildStatement(nodes.Current, - (relPos, symbols) => Statements.NewExpressionStatement(nodes.Current.Fragment.Comments, relPos, symbols, expressionStatement.Item), - symbolTracker, diagnostics); + (relPos, ctx) => Statements.NewExpressionStatement(nodes.Current.Fragment.Comments, relPos, ctx, expressionStatement.Item), + context, diagnostics); proceed = nodes.MoveNext(); return true; } @@ -1033,20 +1033,20 @@ private static bool TryBuildExpressionStatement(IEnumerator - /// Given a sequence of tree nodes, builds the corrsponding array of Q# statements (ignoring invalid fragments) - /// using and updating the given symbol tracker and adding the generated diagnostics to the given list of diagnostics, - /// provided each statement consists of a suitable statement header followed by the required continuation(s), if any. - /// Throws an ArgumentException if this is not the case, - /// or if the given symbol tracker does not currently contain an open scope. + /// Given a sequence of tree nodes, builds the corrsponding array of Q# statements (ignoring invalid fragments) + /// using and updating the given scope context and adding the generated diagnostics to the given list of diagnostics, + /// provided each statement consists of a suitable statement header followed by the required continuation(s), if any. + /// Throws an ArgumentException if this is not the case, + /// or if the given scope context does not currently contain an open scope. /// Throws an ArgumentNullException if any of the given arguments is null, /// or if any of the fragments contained in the given nodes is null. /// - private static ImmutableArray BuildStatements(IEnumerator nodes, - SymbolTracker symbolTracker, List diagnostics) + private static ImmutableArray BuildStatements( + IEnumerator nodes, ScopeContext context, List diagnostics) { if (nodes == null) throw new ArgumentNullException(nameof(nodes)); - if (symbolTracker == null) throw new ArgumentNullException(nameof(symbolTracker)); - if (symbolTracker.AllScopesClosed) throw new ArgumentException("invalid symbol tracker state - statements may only occur within a scope"); + if (context == null) throw new ArgumentNullException(nameof(context)); + if (context.Symbols.AllScopesClosed) throw new ArgumentException("invalid scope context state - statements may only occur within a scope"); if (diagnostics == null) throw new ArgumentException(nameof(diagnostics)); var proceed = nodes.MoveNext(); @@ -1056,43 +1056,43 @@ private static ImmutableArray BuildStatements(IEnumerator BuildStatements(IEnumerator /// Builds the user defined implementation based on the (children of the) given specialization root. /// The implementation takes the given argument tuple as argument and needs to support auto-generation for the specified set of functors. - /// Uses the given SymbolTracker to resolve the symbols used within the implementation, and generates suitable diagnostics in the process. + /// Uses the given scope context to resolve the symbols used within the implementation, and generates suitable diagnostics in the process. /// If necessary, generates suitable diagnostics for functor arguments (only!), which are discriminated by the missing position information /// for argument variables defined in the callable declaration). /// If the expected return type for the specialization is not Unit, verifies that all paths return a value or fail, generating suitable diagnostics. /// Adds the generated diagnostics to the given list of diagnostics. - /// Throws an ArgumentNullException if the given argument, the symbol tracker, or diagnostics are null. + /// Throws an ArgumentNullException if the given argument, the scope context, or diagnostics are null. /// private static SpecializationImplementation BuildUserDefinedImplementation( - FragmentTree.TreeNode root, NonNullable sourceFile, + FragmentTree.TreeNode root, + NonNullable sourceFile, QsTuple> argTuple, ImmutableHashSet requiredFunctorSupport, - SymbolTracker symbolTracker, List diagnostics) + ScopeContext context, + List diagnostics) { if (argTuple == null) throw new ArgumentNullException(nameof(argTuple)); - if (symbolTracker == null) throw new ArgumentNullException(nameof(symbolTracker)); + if (context == null) throw new ArgumentNullException(nameof(context)); if (requiredFunctorSupport == null) throw new ArgumentNullException(nameof(requiredFunctorSupport)); if (diagnostics == null) throw new ArgumentNullException(nameof(diagnostics)); @@ -1129,27 +1131,29 @@ private static SpecializationImplementation BuildUserDefinedImplementation( // -> the position information is set to null (only) for variables defined in the declaration var (variablesOnDeclation, variablesOnSpecialization) = SyntaxGenerator.ExtractItems(argTuple).Partition(decl => decl.Position.IsNull); - symbolTracker.BeginScope(requiredFunctorSupport); + context.Symbols.BeginScope(requiredFunctorSupport); foreach (var decl in variablesOnDeclation) - { symbolTracker.TryAddVariableDeclartion(decl); } + { + context.Symbols.TryAddVariableDeclartion(decl); + } var specPos = root.Fragment.GetRange().Start; foreach (var decl in variablesOnSpecialization) { - var msgs = symbolTracker.TryAddVariableDeclartion(decl).Item2; + var msgs = context.Symbols.TryAddVariableDeclartion(decl).Item2; var position = specPos.Add(DiagnosticTools.AsPosition(decl.Position.Item)); diagnostics.AddRange(msgs.Select(msg => Diagnostics.Generate(sourceFile.Value, msg, position))); } - var implementation = BuildScope(root.Children, symbolTracker, diagnostics); - symbolTracker.EndScope(); + var implementation = BuildScope(root.Children, context, diagnostics); + context.Symbols.EndScope(); // verify that all paths return a value if needed (or fail) var (allPathsReturn, messages) = SyntaxProcessing.SyntaxTree.AllPathsReturnValueOrFail(implementation); var rootPosition = root.Fragment.GetRange().Start; Position AsAbsolutePosition(Tuple statementPos) => rootPosition.Add(DiagnosticTools.AsPosition(statementPos)); diagnostics.AddRange(messages.Select(msg => Diagnostics.Generate(sourceFile.Value, msg.Item2, AsAbsolutePosition(msg.Item1)))); - if (!(symbolTracker.ExpectedReturnType.Resolution.IsUnitType || symbolTracker.ExpectedReturnType.Resolution.IsInvalidType) && !allPathsReturn) + if (!(context.ReturnType.Resolution.IsUnitType || context.ReturnType.Resolution.IsInvalidType) && !allPathsReturn) { var errRange = Parsing.HeaderDelimiters(root.Fragment.Kind.IsControlledAdjointDeclaration ? 2 : 1).Invoke(root.Fragment.Text); var missingReturn = new QsCompilerDiagnostic(DiagnosticItem.NewError(ErrorCode.MissingReturnOrFailStatement), Enumerable.Empty(), errRange); @@ -1205,21 +1209,25 @@ private static IEnumerable RequiredFunctorSupport(QsSpecializationKin /// provided the children are exclusively valid statements. Fails with the corresponding exception otherwise. /// Adds the generated diagnostics to the given list of diagnostics. /// Throws an ArgumentNullException if the parent signature, its argument tuple, - /// the NamespaceManager containing all global declarations, or the given list of diagnostics is null. + /// the compilation unit, or the given list of diagnostics is null. /// Throws an ArgumentException if the given root is neither a specialization declaration, nor a callable declaration, /// or if the callable the specialization belongs to does not support that specialization according to the given NamespaceManager. /// - private static ImmutableArray BuildSpecializations - (FragmentTree specsRoot, ResolvedSignature parentSignature, QsTuple> argTuple, - NamespaceManager symbols, List diagnostics, CancellationToken cancellationToken) + private static ImmutableArray BuildSpecializations( + FragmentTree specsRoot, + ResolvedSignature parentSignature, + QsTuple> argTuple, + CompilationUnit compilation, + List diagnostics, + CancellationToken cancellationToken) { if (parentSignature == null) throw new ArgumentNullException(nameof(parentSignature)); if (argTuple == null) throw new ArgumentNullException(nameof(argTuple)); - if (symbols == null) throw new ArgumentNullException(nameof(symbols)); + if (compilation == null) throw new ArgumentNullException(nameof(compilation)); if (diagnostics == null) throw new ArgumentNullException(nameof(diagnostics)); if (cancellationToken.IsCancellationRequested) return ImmutableArray.Empty; - var definedSpecs = symbols.DefinedSpecializations(new QsQualifiedName(specsRoot.Namespace, specsRoot.Callable)) + var definedSpecs = compilation.GlobalSymbols.DefinedSpecializations(new QsQualifiedName(specsRoot.Namespace, specsRoot.Callable)) .ToLookup(s => s.Item2.Kind).ToImmutableDictionary( specs => specs.Key, specs => QsCompilerError.RaiseOnFailure(specs.Single, "more than one specialization of the same kind exists")); // currently not supported @@ -1235,8 +1243,6 @@ QsSpecialization BuildSpecialization(QsSpecializationKind kind, ResolvedSignatur if (!definedSpecs.TryGetValue(kind, out var defined)) throw new ArgumentException($"missing entry for {kind} specialization of {specsRoot.Namespace}.{specsRoot.Callable}"); var (directive, spec) = defined; - - var symbolTracker = new SymbolTracker(symbols, spec.SourceFile, spec.Parent); var implementation = directive.IsValue ? SpecializationImplementation.NewGenerated(directive.Item) : null; // a user defined implementation is ignored if it is invalid to specify such (e.g. for self-adjoint or intrinsic operations) @@ -1248,8 +1254,14 @@ QsSpecialization BuildSpecialization(QsSpecializationKind kind, ResolvedSignatur QsGeneratorDirective GetDirective(QsSpecializationKind k) => definedSpecs.TryGetValue(k, out defined) && defined.Item1.IsValue ? defined.Item1.Item : null; var requiredFunctorSupport = RequiredFunctorSupport(kind, GetDirective).ToImmutableHashSet(); - implementation = BuildUserDefinedImplementation(root, spec.SourceFile, arg, requiredFunctorSupport, symbolTracker, diagnostics); - QsCompilerError.Verify(symbolTracker.AllScopesClosed, "all scopes should be closed"); + var context = ScopeContext.Create( + compilation.GlobalSymbols, + compilation.RuntimeCapabilities, + compilation.ExecutionTarget, + spec); + implementation = BuildUserDefinedImplementation( + root, spec.SourceFile, arg, requiredFunctorSupport, context, diagnostics); + QsCompilerError.Verify(context.Symbols.AllScopesClosed, "all scopes should be closed"); } implementation = implementation ?? SpecializationImplementation.Intrinsic; return GetSpecialization(spec, signature, implementation, comments); @@ -1369,7 +1381,7 @@ internal static List RunTypeChecking (CompilationUnit compilation, (KeyValuePair specsRoot) { var info = callableDeclarations[specsRoot.Key]; - var specs = BuildSpecializations(specsRoot.Value.Item2, info.Signature, info.ArgumentTuple, compilation.GlobalSymbols, diagnostics, cancellationToken); + var specs = BuildSpecializations(specsRoot.Value.Item2, info.Signature, info.ArgumentTuple, compilation, diagnostics, cancellationToken); return (specsRoot.Key, specs); } diff --git a/src/QsCompiler/Compiler/CompilationLoader.cs b/src/QsCompiler/Compiler/CompilationLoader.cs index 8b39c05fe6..f5002a3c03 100644 --- a/src/QsCompiler/Compiler/CompilationLoader.cs +++ b/src/QsCompiler/Compiler/CompilationLoader.cs @@ -441,7 +441,14 @@ public CompilationLoader(SourceLoader loadSources, ReferenceLoader loadReference RaiseCompilationTaskStart("OverallCompilation", "Build"); this.CompilationStatus.Validation = Status.Succeeded; var files = CompilationUnitManager.InitializeFileManagers(sourceFiles, null, this.OnCompilerException); // do *not* live track (i.e. use publishing) here! - var compilationManager = new CompilationUnitManager(this.OnCompilerException, capabilities: this.Config.RuntimeCapabilities, isExecutable: this.Config.IsExecutable); + var executionTarget = this.Config.AssemblyConstants.GetValueOrDefault(ExecutionTarget); + var compilationManager = new CompilationUnitManager( + this.OnCompilerException, + capabilities: this.Config.RuntimeCapabilities, + isExecutable: this.Config.IsExecutable, + executionTarget: NonNullable.New(string.IsNullOrWhiteSpace(executionTarget) + ? "Unspecified" + : executionTarget)); compilationManager.UpdateReferencesAsync(references); compilationManager.AddOrUpdateSourceFilesAsync(files); this.VerifiedCompilation = compilationManager.Build(); diff --git a/src/QsCompiler/DataStructures/DataTypes.fs b/src/QsCompiler/DataStructures/DataTypes.fs index 1bbe527321..fee665728f 100644 --- a/src/QsCompiler/DataStructures/DataTypes.fs +++ b/src/QsCompiler/DataStructures/DataTypes.fs @@ -116,6 +116,3 @@ type IReaderWriterLock = abstract member ExitReadLock : unit -> unit abstract member EnterWriteLock : unit -> unit abstract member ExitWriteLock : unit -> unit - - - diff --git a/src/QsCompiler/DataStructures/Diagnostics.fs b/src/QsCompiler/DataStructures/Diagnostics.fs index d50d3c7e14..98770b5f7f 100644 --- a/src/QsCompiler/DataStructures/Diagnostics.fs +++ b/src/QsCompiler/DataStructures/Diagnostics.fs @@ -166,6 +166,7 @@ type ErrorCode = | ExpectingIterableExpr = 5020 | ExpectingCallableExpr = 5021 | UnknownIdentifier = 5022 + | UnsupportedResultComparison = 5023 | CallableRedefinition = 6001 | CallableOverlapWithTypeConstructor = 6002 @@ -544,7 +545,12 @@ 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." - + | 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.CallableRedefinition -> "Invalid callable declaration. A function or operation with the name \"{0}\" already exists." | ErrorCode.CallableOverlapWithTypeConstructor -> "Invalid callable declaration. A type constructor with the name \"{0}\" already exists." | ErrorCode.TypeRedefinition -> "Invalid type declaration. A type with the name \"{0}\" already exists." diff --git a/src/QsCompiler/LanguageServer/EditorState.cs b/src/QsCompiler/LanguageServer/EditorState.cs index 1297609b14..7c8e5a1fb2 100644 --- a/src/QsCompiler/LanguageServer/EditorState.cs +++ b/src/QsCompiler/LanguageServer/EditorState.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.Build.Execution; using Microsoft.Quantum.QsCompiler.CompilationBuilder; +using Microsoft.Quantum.QsCompiler.DataTypes; using Microsoft.Quantum.QsCompiler.ReservedKeywords; using Microsoft.VisualStudio.LanguageServer.Protocol; @@ -105,6 +106,7 @@ internal bool QsProjectLoader(Uri projectFile, out ProjectInformation info) var targetFile = projectInstance.GetPropertyValue("TargetFileName"); var outputPath = Path.Combine(projectInstance.Directory, outputDir, targetFile); + var executionTarget = projectInstance.GetPropertyValue("ResolvedQsharpExecutionTarget"); var resRuntimeCapability = projectInstance.GetPropertyValue("ResolvedRuntimeCapabilities"); var runtimeCapabilities = Enum.TryParse(resRuntimeCapability, out AssemblyConstants.RuntimeCapabilities capability) ? capability @@ -125,7 +127,16 @@ internal bool QsProjectLoader(Uri projectFile, out ProjectInformation info) telemetryMeas["csharpfiles"] = csharpFiles.Count(); telemetryProps["defaultSimulator"] = defaultSimulator; this.SendTelemetry("project-load", telemetryProps, telemetryMeas); // does not send anything unless the corresponding flag is defined upon compilation - info = new ProjectInformation(version, outputPath, runtimeCapabilities, isExecutable, loadTestNames, sourceFiles, projectReferences, references); + info = new ProjectInformation( + version, + outputPath, + runtimeCapabilities, + isExecutable, + NonNullable.New(string.IsNullOrWhiteSpace(executionTarget) ? "Unspecified" : executionTarget), + loadTestNames, + sourceFiles, + projectReferences, + references); return true; } diff --git a/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs b/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs index 62410c1e63..4c62ae08f6 100644 --- a/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/ExpressionVerification.fs @@ -10,7 +10,7 @@ open System.Linq open Microsoft.Quantum.QsCompiler open Microsoft.Quantum.QsCompiler.DataTypes open Microsoft.Quantum.QsCompiler.Diagnostics -open Microsoft.Quantum.QsCompiler.SymbolTracker +open Microsoft.Quantum.QsCompiler.ReservedKeywords.AssemblyConstants open Microsoft.Quantum.QsCompiler.SyntaxExtensions open Microsoft.Quantum.QsCompiler.SyntaxGenerator open Microsoft.Quantum.QsCompiler.SyntaxProcessing.VerificationTools @@ -246,11 +246,17 @@ let private VerifyConcatenation parent addError (lhsType : ResolvedType, lhsRang /// If a common base type exists, verifies that this base type supports equality comparison, /// adding the corresponding error otherwise. /// If one of the given types is a missing type, also adds the corresponding ExpressionOfUnknownType error(s). -let private VerifyEqualityComparison parent addError (lhsType : ResolvedType, lhsRange) (rhsType : ResolvedType, rhsRange) = - // NOTE: this may not be the behavior that we want (right now it does not matter, since we don't support equality comparison for any derived type) - let baseType = CommonBaseType addError (ErrorCode.ArgumentMismatchInBinaryOp, [lhsType |> toString; rhsType |> toString]) parent (lhsType, lhsRange) (rhsType, rhsRange) - let expected (t : ResolvedType) = t.supportsEqualityComparison - VerifyIsOneOf expected (ErrorCode.InvalidTypeInEqualityComparison, [baseType |> toString]) addError (baseType, rhsRange) |> ignore +let private VerifyEqualityComparison context addError (lhsType, lhsRange) (rhsType, rhsRange) = + // NOTE: this may not be the behavior that we want (right now it does not matter, since we don't support equality + // 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) + match baseType.Resolution with + | Result when context.Capabilities = RuntimeCapabilities.QPRGen0 -> + addError (ErrorCode.UnsupportedResultComparison, [context.ExecutionTarget.Value]) rhsRange + | _ -> + let unsupportedError = ErrorCode.InvalidTypeInEqualityComparison, [toString baseType] + VerifyIsOneOf (fun t -> t.supportsEqualityComparison) unsupportedError addError (baseType, rhsRange) |> ignore /// Given a list of all item types and there corresponding ranges, verifies that a value array literal can be built from them. /// Adds a MissingExprInArray error with the corresponding range using addError if one of the given types is missing. @@ -547,10 +553,10 @@ type QsExpression with /// recursively computes the corresponding typed expression for a Q# expression. /// Calls addDiagnostic on each diagnostic generated during the resolution. /// Returns the computed typed expression. - member this.Resolve (symbols : SymbolTracker<_>) addDiagnostic : TypedExpression = - + member this.Resolve ({ Symbols = symbols } as context) addDiagnostic : TypedExpression = + /// Calls Resolve on the given Q# expression. - let InnerExpression (item : QsExpression) = item.Resolve symbols addDiagnostic + let InnerExpression (item : QsExpression) = item.Resolve context addDiagnostic /// Builds a QsCompilerDiagnostic with the given error code and range. let addError code range = range |> QsCompilerDiagnostic.Error code |> addDiagnostic /// Builds a QsCompilerDiagnostic with the given warning code and range. @@ -797,7 +803,7 @@ type QsExpression with /// Resolves and verifies the given left hand side and right hand side of a call expression, /// and returns the corresponding expression as typed expression. let buildCall (method, arg) = - let getType (ex : QsExpression) = (ex.Resolve symbols (fun _ -> ())).ResolvedType // don't push resolution errors when tuple matching arguments + let getType (ex : QsExpression) = (ex.Resolve context (fun _ -> ())).ResolvedType // don't push resolution errors when tuple matching arguments let (resolvedMethod, resolvedArg) = (InnerExpression method, InnerExpression arg) let locQdepClassicalEx = resolvedMethod.InferredInformation.HasLocalQuantumDependency || resolvedArg.InferredInformation.HasLocalQuantumDependency let exprKind = CallLikeExpression (resolvedMethod, resolvedArg) @@ -824,7 +830,7 @@ type QsExpression with let localQDependency = if isPartialApplication then locQdepClassicalEx else true let exInfo = InferredExpressionInformation.New (isMutable = false, quantumDep = localQDependency) let typeParamResolutions, exType = (argT, resT) |> callTypeOrPartial (fun (i,o) -> QsTypeKind.Operation ((i,o), characteristics)) - if not (symbols.WithinOperation || isPartialApplication) then method.RangeOrDefault |> addError (ErrorCode.OperationCallOutsideOfOperation, []); invalidEx + if not (context.IsInOperation || isPartialApplication) then method.RangeOrDefault |> addError (ErrorCode.OperationCallOutsideOfOperation, []); invalidEx else TypedExpression.New (exprKind, typeParamResolutions, exType, exInfo, this.Range) | _ -> method.RangeOrDefault |> addError (ErrorCode.ExpectingCallableExpr, [resolvedMethod.ResolvedType |> toString]); invalidEx @@ -869,10 +875,8 @@ type QsExpression with | BXOR (lhs,rhs) -> buildIntegralOp BXOR (lhs, rhs) | AND (lhs,rhs) -> buildBooleanOpWith VerifyAreBooleans true AND (lhs, rhs) | OR (lhs,rhs) -> buildBooleanOpWith VerifyAreBooleans true OR (lhs, rhs) - | EQ (lhs,rhs) -> buildBooleanOpWith (VerifyEqualityComparison symbols.Parent) false EQ (lhs, rhs) - | NEQ (lhs,rhs) -> buildBooleanOpWith (VerifyEqualityComparison symbols.Parent) false NEQ (lhs, rhs) + | EQ (lhs,rhs) -> buildBooleanOpWith (VerifyEqualityComparison context) false EQ (lhs, rhs) + | NEQ (lhs,rhs) -> buildBooleanOpWith (VerifyEqualityComparison context) false NEQ (lhs, rhs) | NEG ex -> verifyAndBuildWith NEG VerifySupportsArithmetic ex | BNOT ex -> verifyAndBuildWith BNOT VerifyIsIntegral ex | NOT ex -> verifyAndBuildWith NOT (fun log arg -> VerifyIsBoolean log arg; ResolvedType.New Bool) ex - - diff --git a/src/QsCompiler/SyntaxProcessor/SymbolTracker.fs b/src/QsCompiler/SyntaxProcessor/ScopeTools.fs similarity index 87% rename from src/QsCompiler/SyntaxProcessor/SymbolTracker.fs rename to src/QsCompiler/SyntaxProcessor/ScopeTools.fs index 3b3aec8f50..c838e10fa4 100644 --- a/src/QsCompiler/SyntaxProcessor/SymbolTracker.fs +++ b/src/QsCompiler/SyntaxProcessor/ScopeTools.fs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -namespace Microsoft.Quantum.QsCompiler.SymbolTracker +namespace Microsoft.Quantum.QsCompiler.SyntaxProcessing open System open System.Collections.Generic @@ -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.SymbolManagement open Microsoft.Quantum.QsCompiler.SyntaxProcessing.VerificationTools @@ -65,17 +66,17 @@ type SymbolTracker<'P>(globals : NamespaceManager, sourceFile, parent : QsQualif InvalidOperationException "the content of the namespace manager associated with this symbol tracker has changed" |> raise globals - /// Denotes a property of the parent callable associated with this symbol tracker, - /// such as information about the kind, the type parameters, the returns type, and the source file it is declared in. - /// IMPORTANT: these need to be adapted if we want to support type specializations and/or external specializations! - let parentIsOperation, typeParameters, expectedReturnType = + /// The type parameters of the parent callable associated with this symbol tracker. + /// + /// IMPORTANT: This needs to be adapted if we want to support type specializations and/or external specializations! + let typeParameters = match GlobalSymbols().TryGetCallable parent (parent.Namespace, sourceFile) with | Found decl -> - let isOperation = decl.Kind |> function | QsCallableKind.Operation -> true | _ -> false - let validTypeParams = decl.Signature.TypeParameters |> Seq.choose (function | ValidName name -> Some name | InvalidName -> None) - isOperation, validTypeParams.ToImmutableArray(), decl.Signature.ReturnType |> StripPositionInfo.Apply // NOTE: valid only since we do not yet support external and/or type specializations + decl.Signature.TypeParameters + |> Seq.choose (function | ValidName name -> Some name | InvalidName -> None) + |> fun valid -> valid.ToImmutableArray () | _ -> ArgumentException "the given NamespaceManager does not contain a callable with the given parent name" |> raise - + /// If a local variable with the given name is visible on the current scope, /// returns the dictionary that contains its declaration as Value. /// Returns Null otherwise. @@ -104,12 +105,10 @@ type SymbolTracker<'P>(globals : NamespaceManager, sourceFile, parent : QsQualif /// the namespace and callable declaration within which the symbols tracked by this SymbolTracker instance are used member this.Parent = parent - /// set to true if the parent callable associated with this symbol tracker is an operation - member this.WithinOperation = parentIsOperation + /// the source file within which the Parent (a callable declaration) associated with this SymbolTracker instance is declared member this.SourceFile = sourceFile - /// the expected type for all values returned within the scope this symbol tracker is used for (contains no range information) - member this.ExpectedReturnType = expectedReturnType + /// returns true if no scope is currently open member this.AllScopesClosed = pushedScopes.Length = 0 @@ -246,7 +245,7 @@ type SymbolTracker<'P>(globals : NamespaceManager, sourceFile, parent : QsQualif /// For each diagnostic generated during the resolution, calls the given addDiagnostics function on it. /// Returns the resolved type, *including* its range information if applicable. member internal this.ResolveType addDiagnostic (qsType : QsType) = - let resolved, errs = GlobalSymbols().ResolveType (this.Parent, typeParameters, this.SourceFile) qsType + let resolved, errs = GlobalSymbols().ResolveType (parent, typeParameters, sourceFile) qsType for err in errs do addDiagnostic err resolved @@ -280,3 +279,41 @@ type SymbolTracker<'P>(globals : NamespaceManager, sourceFile, parent : QsQualif | LocalVariable name -> decl.TypeItems.Items |> Seq.choose (namedWithName name.Value) |> Seq.toList |> function | [itemDecl] -> itemDecl.Type |> StripPositionInfo.Apply | _ -> addError (ErrorCode.UnknownItemName, [udt.Name.Value; name.Value]); InvalidType |> ResolvedType.New + +/// The context used for symbol resolution and type checking within the scope of a callable. +type ScopeContext<'a> = + { /// The symbol tracker for the parent callable. + Symbols : SymbolTracker<'a> + /// True if the parent callable for the current scope is an operation. + IsInOperation : bool + /// The return type of the parent callable for the current scope. + ReturnType : ResolvedType + /// The runtime capabilities for the compilation unit. + Capabilities : RuntimeCapabilities + /// The name of the execution target for the compilation unit. + ExecutionTarget : NonNullable } + with + + /// + /// Creates a scope context for the specialization. + /// + /// The symbol tracker in the context does not make a copy of the given namespace manager. Instead, it throws an + /// if the namespace manager has been modified (i.e. the version number of + /// the namespace manager has changed). + /// + /// + /// Thrown if the given namespace manager does not contain all resolutions or if the specialization's parent does + /// not exist in the given namespace manager. + /// + static member Create (nsManager : NamespaceManager) + capabilities + executionTarget + (spec : SpecializationDeclarationHeader) = + match nsManager.TryGetCallable spec.Parent (spec.Parent.Namespace, spec.SourceFile) with + | Found declaration -> + { Symbols = SymbolTracker<'a> (nsManager, spec.SourceFile, spec.Parent) + IsInOperation = declaration.Kind = Operation + ReturnType = StripPositionInfo.Apply declaration.Signature.ReturnType + Capabilities = capabilities + ExecutionTarget = executionTarget } + | _ -> raise <| ArgumentException "The specialization's parent callable does not exist." diff --git a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs index 98b7ef89cb..569e285e2e 100644 --- a/src/QsCompiler/SyntaxProcessor/StatementVerification.fs +++ b/src/QsCompiler/SyntaxProcessor/StatementVerification.fs @@ -6,36 +6,35 @@ module Microsoft.Quantum.QsCompiler.SyntaxProcessing.Statements open System open System.Collections.Generic open System.Collections.Immutable -open System.Linq open Microsoft.Quantum.QsCompiler open Microsoft.Quantum.QsCompiler.DataTypes open Microsoft.Quantum.QsCompiler.Diagnostics 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.SymbolTracker open Microsoft.Quantum.QsCompiler.SyntaxTree open Microsoft.Quantum.QsCompiler.Transformations.SearchAndReplace // some utils for type checking statements -/// Given a verication function that takes an error logging function as well as an expression type and its range, +/// 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, /// and an array with the diagnostics generated during resolution and verification. -let private VerifyWith verification symbols (expr : QsExpression) = +let private VerifyWith verification context (expr : QsExpression) = let accumulatedDiagnostics = new List() let addError code = QsCompilerDiagnostic.Error code >> accumulatedDiagnostics.Add - let typedExpr = expr.Resolve symbols accumulatedDiagnostics.Add + let typedExpr = expr.Resolve context accumulatedDiagnostics.Add let resVerification = verification addError (typedExpr.ResolvedType, expr.RangeOrDefault) typedExpr, resVerification, accumulatedDiagnostics |> Seq.toArray /// Resolves the given expression, and returns the typed expression built upon resolution, /// as well as an array with the diagnostics generated during resolution. -let private Verify symbols expr = - VerifyWith (fun _ _ _ -> ()) symbols expr |> fun (ex,_,err) -> ex, err +let private Verify context expr = + VerifyWith (fun _ _ _ -> ()) context expr |> fun (ex,_,err) -> ex, err /// If the given SymbolTracker specifies that an auto-inversion of the routine is requested, /// checks if the given typed expression has any local quantum dependencies. @@ -66,26 +65,33 @@ let NewExpressionStatement comments location symbols expr = let verifiedExpr, _, diagnostics = VerifyWith VerifyIsUnit symbols expr verifiedExpr |> QsExpressionStatement |> asStatement comments location LocalDeclarations.Empty, diagnostics -/// Resolves and verifies the given Q# expression given a symbol tracker containing all currently declared symbols, -/// verifies that the resolved expression is indeed of type String, and builds a Q# fail-statement at the given location from it. +/// Resolves and verifies the given Q# expression given the resolution context, verifies that the resolved expression is +/// indeed of type String, and builds a Q# fail-statement at the given location from it. +/// /// Returns the built statement as well as an array of diagnostics generated during resolution and verification. -let NewFailStatement comments location symbols expr = - let verifiedExpr, _, diagnostics = VerifyWith VerifyIsString symbols expr - let autoGenErrs = (verifiedExpr, expr.RangeOrDefault) |> onAutoInvertCheckQuantumDependency symbols +let NewFailStatement comments location context expr = + let verifiedExpr, _, diagnostics = VerifyWith VerifyIsString context expr + let autoGenErrs = (verifiedExpr, expr.RangeOrDefault) |> onAutoInvertCheckQuantumDependency context.Symbols verifiedExpr |> QsFailStatement |> asStatement comments location LocalDeclarations.Empty, Array.concat [diagnostics; autoGenErrs] -/// Resolves and verifies the given Q# expression given a symbol tracker containing all currently declared symbols. -/// Verifies that the type of the resolved expression is indeed compatible with the expected return type associated with the symbol tracker. +/// Resolves and verifies the given Q# expression using the resolution context. +/// Verifies that the type of the resolved expression is indeed compatible with the expected return type of the parent callable. /// Uses VerifyAssignment to verify that this is indeed the case. /// Builds the corresponding Q# return-statement at the given location, /// and returns it along with an array of diagnostics generated during resolution and verification. /// Errors due to the statement not satisfying the necessary conditions for the required auto-generation of specializations /// (specified by the given SymbolTracker) are also included in the returned diagnostics. -let NewReturnStatement comments (location : QsLocation) (symbols : SymbolTracker<_>) expr = - let VerifyIsReturnType = VerifyAssignment symbols.ExpectedReturnType symbols.Parent ErrorCode.TypeMismatchInReturn - let verifiedExpr, _, diagnostics = VerifyWith VerifyIsReturnType symbols expr - let autoGenErrs = symbols |> onAutoInvertGenerateError ((ErrorCode.ReturnStatementWithinAutoInversion, []), location.Range) - verifiedExpr |> QsReturnStatement |> asStatement comments location LocalDeclarations.Empty, Array.concat [diagnostics; autoGenErrs] +let NewReturnStatement comments (location : QsLocation) (context : ScopeContext<_>) expr = + let verifyIsReturnType = VerifyAssignment context.ReturnType context.Symbols.Parent ErrorCode.TypeMismatchInReturn + let verifiedExpr, _, diagnostics = VerifyWith verifyIsReturnType context expr + let autoGenErrs = + context.Symbols + |> onAutoInvertGenerateError ((ErrorCode.ReturnStatementWithinAutoInversion, []), location.Range) + let statement = + verifiedExpr + |> QsReturnStatement + |> asStatement comments location LocalDeclarations.Empty + statement, Array.append diagnostics autoGenErrs /// Given a Q# symbol, as well as the resolved type of the right hand side that is assigned to it, /// shape matches the symbol tuple with the type to determine whether the assignment is valid, and @@ -127,30 +133,39 @@ let private VerifyBinding tryBuildDeclaration (qsSym, (rhsType, rhsRange)) warnO symbolTuple symItems, Array.concat declarations, Array.concat errs GetBindings (qsSym, rhsType) -/// Resolves and verifies the given Q# expressions given a symbol tracker containing all currently declared symbols. +/// Resolves and verifies the given Q# expressions using the resolution context. /// Verifies that the type of the resolved right hand side expression is indeed compatible with the resolved type of the left hand side expression. /// Uses VerifyAssignment to verify that this is indeed the case. /// Builds the corresponding Q# set-statement at the given location, /// and returns it along with an array of diagnostics generated during resolution and verification. /// Errors due to the statement not satisfying the necessary conditions for the required auto-generation of specializations /// (specified by the given SymbolTracker) are also included in the returned diagnostics. -let NewValueUpdate comments (location : QsLocation) symbols (lhs : QsExpression, rhs : QsExpression) = - let verifiedLhs, lhsErrs = Verify symbols lhs - let VerifyIsCorrectType = VerifyAssignment verifiedLhs.ResolvedType symbols.Parent ErrorCode.TypeMismatchInValueUpdate - let verifiedRhs, _, rhsErrs = VerifyWith VerifyIsCorrectType symbols rhs +let NewValueUpdate comments (location : QsLocation) context (lhs : QsExpression, rhs : QsExpression) = + let verifiedLhs, lhsErrs = Verify context lhs + let VerifyIsCorrectType = + VerifyAssignment verifiedLhs.ResolvedType context.Symbols.Parent ErrorCode.TypeMismatchInValueUpdate + let verifiedRhs, _, rhsErrs = VerifyWith VerifyIsCorrectType context rhs let localQdep = verifiedRhs.InferredInformation.HasLocalQuantumDependency let rec VerifyMutability = function | Tuple (exs : TypedExpression list) -> exs |> Seq.collect VerifyMutability |> Seq.toArray - | Item (ex : TypedExpression) when ex.InferredInformation.IsMutable -> ex.Expression |> function - | Identifier (LocalVariable id, Null) -> symbols.UpdateQuantumDependency id localQdep; [||] - | _ -> [||] + | Item (ex : TypedExpression) when ex.InferredInformation.IsMutable -> + match ex.Expression with + | Identifier (LocalVariable id, Null) -> context.Symbols.UpdateQuantumDependency id localQdep + | _ -> () + [||] | Item (ex : TypedExpression) -> let range = ex.Range.ValueOr QsCompilerDiagnostic.DefaultRange [| range |> QsCompilerDiagnostic.Error (ErrorCode.UpdateOfImmutableIdentifier, []) |] | _ -> [||] // both missing and invalid expressions on the lhs are fine let refErrs = verifiedLhs |> VerifyMutability - let autoGenErrs = symbols |> onAutoInvertGenerateError ((ErrorCode.ValueUpdateWithinAutoInversion, []), location.Range) - QsValueUpdate.New(verifiedLhs, verifiedRhs) |> QsValueUpdate |> asStatement comments location LocalDeclarations.Empty, Array.concat [lhsErrs; refErrs; rhsErrs; autoGenErrs] + let autoGenErrs = + context.Symbols + |> onAutoInvertGenerateError ((ErrorCode.ValueUpdateWithinAutoInversion, []), location.Range) + let statement = + QsValueUpdate.New(verifiedLhs, verifiedRhs) + |> QsValueUpdate + |> asStatement comments location LocalDeclarations.Empty + statement, Array.concat [lhsErrs; refErrs; rhsErrs; autoGenErrs] /// Adds a variable declaration with the given name, quantum dependency, and type at the given location to the given symbol tracker. /// Generates the corresponding error(s) if a symbol with the same name is already visible on that scope and/or the given name is not a valid variable name. @@ -167,19 +182,19 @@ let private TryAddDeclaration isMutable (symbols : SymbolTracker<_>) (name : Non (if added then Some decl else None), errs |> Array.append tpErr /// Given a Q# symbol, as well as the expression on the right hand side that is assigned to it, -/// resolves and verifies the assignment using VerifyBinding and the given symbol tracker that contains all currently declared symbols. +/// resolves and verifies the assignment using VerifyBinding and the resolution context. /// Pushes all determined variable declarations into the current scope of the symbol tracker, /// generating the corresponding error(s) if a symbol with the same name is already visible on that scope and/or the symbol is not a valid variable name. /// Builds the corresponding Q# let- or mutable-statement (depending on the given kind) at the given location, /// and returns it along with an array of all generated diagnostics. -let private NewBinding kind comments (location : QsLocation) (symbols : SymbolTracker<_>) (qsSym : QsSymbol, qsExpr : QsExpression) = - let rhs, rhsErrs = Verify symbols qsExpr +let private NewBinding kind comments (location : QsLocation) context (qsSym : QsSymbol, qsExpr : QsExpression) = + let rhs, rhsErrs = Verify context qsExpr let symTuple, varDeclarations, errs = let isMutable = kind |> function | MutableBinding -> true | ImmutableBinding -> false let localQdep = rhs.InferredInformation.HasLocalQuantumDependency - let addDeclaration (name, range) = TryAddDeclaration isMutable symbols (name, (Value location.Offset, range), localQdep) + let addDeclaration (name, range) = TryAddDeclaration isMutable context.Symbols (name, (Value location.Offset, range), localQdep) VerifyBinding addDeclaration (qsSym, (rhs.ResolvedType, qsExpr.RangeOrDefault)) false - let autoGenErrs = (rhs, qsExpr.RangeOrDefault) |> onAutoInvertCheckQuantumDependency symbols + let autoGenErrs = (rhs, qsExpr.RangeOrDefault) |> onAutoInvertCheckQuantumDependency context.Symbols let binding = QsBinding.New kind (symTuple, rhs) |> QsVariableDeclaration binding |> asStatement comments location (LocalDeclarations.New varDeclarations), Array.concat [rhsErrs; errs; autoGenErrs] @@ -198,7 +213,7 @@ let NewMutableBinding comments location symbols (qsSym, qsExpr) = type BlockStatement<'T> = delegate of QsScope -> 'T -/// Given the location of the statement header as well as a symbol tracker containing all currently declared symbols, +/// Given the location of the statement header and the resolution context, /// builds the Q# for-statement at the given location, that iterates over the given expression, with the given symbol as loop variable(s). /// In order to do so, verifies the expression is indeed iterable, /// and verifies that the shape of the symbol tuple is compatible with the type of the iteration item using VerifyBinding. @@ -206,32 +221,32 @@ type BlockStatement<'T> = delegate of QsScope -> 'T /// Returns an array with all generated diagnostics, /// as well as a delegate that given a Q# scope returns the built for-statement with the given scope as the body. /// NOTE: the declared loop variables are *not* visible after the statements ends, hence they are *not* attaches as local declarations to the statement! -let NewForStatement comments (location : QsLocation) (symbols : SymbolTracker<_>) (qsSym : QsSymbol, qsExpr : QsExpression) = - let iterExpr, itemT, iterErrs = VerifyWith VerifyIsIterable symbols qsExpr +let NewForStatement comments (location : QsLocation) context (qsSym : QsSymbol, qsExpr : QsExpression) = + let iterExpr, itemT, iterErrs = VerifyWith VerifyIsIterable context qsExpr let symTuple, _, varErrs = let localQdep = iterExpr.InferredInformation.HasLocalQuantumDependency - let addDeclaration (name, range) = TryAddDeclaration false symbols (name, (Value location.Offset, range), localQdep) + let addDeclaration (name, range) = TryAddDeclaration false context.Symbols (name, (Value location.Offset, range), localQdep) VerifyBinding addDeclaration (qsSym, (itemT, qsExpr.RangeOrDefault)) false - let autoGenErrs = (iterExpr, qsExpr.RangeOrDefault) |> onAutoInvertCheckQuantumDependency symbols + let autoGenErrs = (iterExpr, qsExpr.RangeOrDefault) |> onAutoInvertCheckQuantumDependency context.Symbols let forLoop body = QsForStatement.New ((symTuple, itemT), iterExpr, body) |> QsForStatement new BlockStatement<_>(forLoop >> asStatement comments location LocalDeclarations.Empty), Array.concat [iterErrs; varErrs; autoGenErrs] -/// Given the location of the statement header as well as a symbol tracker containing all currently declared symbols, +/// Given the location of the statement header and the resolution context, /// builds the Q# while-statement at the given location with the given expression as condition. /// Verifies the expression is indeed of type Bool, and returns an array with all generated diagnostics, /// as well as a delegate that given a Q# scope returns the built while-statement with the given scope as the body. -let NewWhileStatement comments (location : QsLocation) (symbols : SymbolTracker<_>) (qsExpr : QsExpression) = - let cond, _, errs = VerifyWith VerifyIsBoolean symbols qsExpr +let NewWhileStatement comments (location : QsLocation) context (qsExpr : QsExpression) = + let cond, _, errs = VerifyWith VerifyIsBoolean context qsExpr let whileLoop body = QsWhileStatement.New (cond, body) |> QsWhileStatement new BlockStatement<_>(whileLoop >> asStatement comments location LocalDeclarations.Empty), errs -/// Resolves and verifies the given Q# expression given a symbol tracker containing all currently declared symbols. +/// Resolves and verifies the given Q# expression using the resolution context. /// Verifies that the type of the resolved expression is indeed of kind Bool. /// Returns an array of all diagnostics generated during resolution and verification, /// as well as a delegate that given a positioned block of Q# statements returns the corresponding conditional block. -let NewConditionalBlock comments location (symbols : SymbolTracker<_>) (qsExpr : QsExpression) = - let condition, _, errs = VerifyWith VerifyIsBoolean symbols qsExpr - let autoGenErrs = (condition, qsExpr.RangeOrDefault) |> onAutoInvertCheckQuantumDependency symbols +let NewConditionalBlock comments location context (qsExpr : QsExpression) = + let condition, _, errs = VerifyWith VerifyIsBoolean context qsExpr + let autoGenErrs = (condition, qsExpr.RangeOrDefault) |> onAutoInvertCheckQuantumDependency context.Symbols let block body = condition, QsPositionedBlock.New comments (Value location) body new BlockStatement<_>(block), Array.concat [errs; autoGenErrs] @@ -278,7 +293,7 @@ let NewConjugation (outer : QsPositionedBlock, inner : QsPositionedBlock) = |> Seq.map (fun loc -> (loc.Offset, loc.Range |> QsCompilerDiagnostic.Error (ErrorCode.InvalidReassignmentInApplyBlock, []))) |> Seq.toArray QsConjugation.New (outer, inner) |> QsConjugation |> asStatement QsComments.Empty location LocalDeclarations.Empty, updateErrs -/// Given the location of the statement header as well as a symbol tracker containing all currently declared symbols, +/// Given the location of the statement header and the resolution context, /// builds the Q# using- or borrowing-statement (depending on the given kind) at the given location /// that initializes the qubits in the given initializer expression and assigns them to the given symbol. /// In order to do so, resolves and verifies the initializer expression, as well as its binding to the given symbol using VerifyBinding. @@ -286,12 +301,12 @@ let NewConjugation (outer : QsPositionedBlock, inner : QsPositionedBlock) = /// Returns an array with all generated diagnostics, /// as well as a delegate that given a Q# scope returns the built using- or borrowing-statement with the given scope as the body. /// NOTE: the declared variables are *not* visible after the statements ends, hence they are *not* attaches as local declarations to the statement! -let private NewBindingScope kind comments (location : QsLocation) (symbols : SymbolTracker<_>) (qsSym : QsSymbol, qsInit : QsInitializer) = +let private NewBindingScope kind comments (location : QsLocation) context (qsSym : QsSymbol, qsInit : QsInitializer) = let rec VerifyInitializer (init : QsInitializer) = init.Initializer |> function | SingleQubitAllocation -> SingleQubitAllocation |> ResolvedInitializer.New, [||] | QubitRegisterAllocation nr -> - let verifiedNr, _, err = VerifyWith VerifyIsInteger symbols nr - let autoGenErrs = (verifiedNr, nr.RangeOrDefault) |> onAutoInvertCheckQuantumDependency symbols + let verifiedNr, _, err = VerifyWith VerifyIsInteger context nr + let autoGenErrs = (verifiedNr, nr.RangeOrDefault) |> onAutoInvertCheckQuantumDependency context.Symbols QubitRegisterAllocation verifiedNr |> ResolvedInitializer.New, Array.concat [err; autoGenErrs] | QubitTupleAllocation is -> let items, errs = is |> Seq.map VerifyInitializer |> Seq.toList |> List.unzip @@ -301,10 +316,12 @@ let private NewBindingScope kind comments (location : QsLocation) (symbols : Sym let initializer, initErrs = VerifyInitializer qsInit let symTuple, _, varErrs = let rhsRange = qsInit.Range.ValueOr QsCompilerDiagnostic.DefaultRange - let addDeclaration (name, range) = TryAddDeclaration false symbols (name, (Value location.Offset, range), false) + let addDeclaration (name, range) = + TryAddDeclaration false context.Symbols (name, (Value location.Offset, range), false) VerifyBinding addDeclaration (qsSym, (initializer.Type, rhsRange)) true let bindingScope body = QsQubitScope.New kind ((symTuple, initializer), body) |> QsQubitScope - new BlockStatement<_>(bindingScope >> asStatement comments location LocalDeclarations.Empty), Array.concat [initErrs; varErrs] + let statement = BlockStatement<_>(bindingScope >> asStatement comments location LocalDeclarations.Empty) + statement, Array.append initErrs varErrs /// Resolves, verifies and builds the Q# using-statement at the given location binding the given initializer to the given symbol. /// Adds the corresponding local variable declarations to the given symbol tracker. @@ -315,4 +332,3 @@ let NewAllocateScope comments location symbols (qsSym, qsInit) = NewBindingScope /// Adds the corresponding local variable declarations to the given symbol tracker. /// Returns the built statement along with an array containing all diagnostics generated in the process. let NewBorrowScope comments location symbols (qsSym, qsInit) = NewBindingScope QsQubitScopeKind.Borrow comments location symbols (qsSym, qsInit) - diff --git a/src/QsCompiler/SyntaxProcessor/SyntaxProcessor.fsproj b/src/QsCompiler/SyntaxProcessor/SyntaxProcessor.fsproj index 8dbffc6716..ee8e14a0ae 100644 --- a/src/QsCompiler/SyntaxProcessor/SyntaxProcessor.fsproj +++ b/src/QsCompiler/SyntaxProcessor/SyntaxProcessor.fsproj @@ -13,7 +13,7 @@ DelaySign.fs - + diff --git a/src/QsCompiler/Tests.Compiler/AccessModifierTests.fs b/src/QsCompiler/Tests.Compiler/AccessModifierTests.fs index 72e8ddd8c9..50fb03c085 100644 --- a/src/QsCompiler/Tests.Compiler/AccessModifierTests.fs +++ b/src/QsCompiler/Tests.Compiler/AccessModifierTests.fs @@ -12,10 +12,9 @@ open Microsoft.Quantum.QsCompiler.SyntaxTree open Xunit -type AccessModifierTests (output) = +type AccessModifierTests () = inherit CompilerTests - (CompilerTests.Compile "TestCases" ["AccessModifiers.qs"] [File.ReadAllLines("ReferenceTargets.txt").[1]], - output) + (CompilerTests.Compile ("TestCases", ["AccessModifiers.qs"], [File.ReadAllLines("ReferenceTargets.txt").[1]])) member private this.Expect name (diagnostics : IEnumerable) = let ns = "Microsoft.Quantum.Testing.AccessModifiers" |> NonNullable<_>.New diff --git a/src/QsCompiler/Tests.Compiler/AutoGenerationTests.fs b/src/QsCompiler/Tests.Compiler/AutoGenerationTests.fs index 2ae27951e9..087c786ebf 100644 --- a/src/QsCompiler/Tests.Compiler/AutoGenerationTests.fs +++ b/src/QsCompiler/Tests.Compiler/AutoGenerationTests.fs @@ -9,11 +9,10 @@ open Microsoft.Quantum.QsCompiler.Diagnostics open Microsoft.Quantum.QsCompiler.SyntaxExtensions open Microsoft.Quantum.QsCompiler.SyntaxTree open Xunit -open Xunit.Abstractions -type FunctorAutoGenTests (output:ITestOutputHelper) = - inherit CompilerTests(CompilerTests.Compile "TestCases" ["General.qs"; "FunctorGeneration.qs"] [], output) +type FunctorAutoGenTests () = + inherit CompilerTests(CompilerTests.Compile ("TestCases", ["General.qs"; "FunctorGeneration.qs"])) member private this.Expect name (diag : IEnumerable) = let ns = "Microsoft.Quantum.Testing.FunctorGeneration" |> NonNullable<_>.New diff --git a/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs b/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs new file mode 100644 index 0000000000..e2c5c20ca8 --- /dev/null +++ b/src/QsCompiler/Tests.Compiler/CapabilityVerificationTests.fs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +module Microsoft.Quantum.QsCompiler.Testing.CapabilityVerificationTests + +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.SyntaxTree +open Xunit + +/// Compiles the capability verification test cases using the given capability. +let private compile capabilities = + CompilerTests.Compile ("TestCases", ["CapabilityVerification.qs"], capabilities = capabilities) + +/// The unknown capability tester. +let private unknown = CompilerTests (compile RuntimeCapabilities.Unknown) + +/// The QPRGen0 capability tester. +let private gen0 = CompilerTests (compile RuntimeCapabilities.QPRGen0) + +/// The QPRGen1 capability tester. +let private gen1 = CompilerTests (compile RuntimeCapabilities.QPRGen1) + +/// The qualified name for the test case name. +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 disallows the given test case. +let private restricts (tester : CompilerTests) name = + tester.Verify (testName name, [Error ErrorCode.UnsupportedResultComparison]) + +/// The names of all test cases. +let private all = + [ "ResultAsBool" + "ResultAsBoolNeq" + "ResultAsBoolOp" + "ResultAsBoolNeqOp" + "ResultAsBoolOpReturnIf" + "ResultAsBoolNeqOpReturnIf" + "ResultAsBoolOpSetIf" + "ResultAsBoolNeqOpSetIf" + "EmptyIf" + "EmptyIfNeq" + "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 ``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" + +[] +let ``QPRGen1 allows empty Result if`` () = + allows gen1 "EmptyIf" + allows gen1 "EmptyIfNeq" + +[] +let ``QPRGen1 allows operation call from Result if`` () = + allows gen1 "Reset" + allows gen1 "ResetNeq" diff --git a/src/QsCompiler/Tests.Compiler/GlobalVerificationTests.fs b/src/QsCompiler/Tests.Compiler/GlobalVerificationTests.fs index ed81cadd5b..4e2efe2af6 100644 --- a/src/QsCompiler/Tests.Compiler/GlobalVerificationTests.fs +++ b/src/QsCompiler/Tests.Compiler/GlobalVerificationTests.fs @@ -9,11 +9,10 @@ open Microsoft.Quantum.QsCompiler.Diagnostics open Microsoft.Quantum.QsCompiler.SyntaxExtensions open Microsoft.Quantum.QsCompiler.SyntaxTree open Xunit -open Xunit.Abstractions -type GlobalVerificationTests (output:ITestOutputHelper) = - inherit CompilerTests(CompilerTests.Compile "TestCases" ["General.qs"; "GlobalVerification.qs"; "Types.qs"; System.IO.Path.Join("LinkingTests", "Core.qs")] [], output) +type GlobalVerificationTests () = + inherit CompilerTests(CompilerTests.Compile ("TestCases", ["General.qs"; "GlobalVerification.qs"; "Types.qs"; System.IO.Path.Join("LinkingTests", "Core.qs")])) member private this.Expect name (diag : IEnumerable) = let ns = "Microsoft.Quantum.Testing.GlobalVerification" |> NonNullable<_>.New diff --git a/src/QsCompiler/Tests.Compiler/LinkingTests.fs b/src/QsCompiler/Tests.Compiler/LinkingTests.fs index c907697921..71aee9cf46 100644 --- a/src/QsCompiler/Tests.Compiler/LinkingTests.fs +++ b/src/QsCompiler/Tests.Compiler/LinkingTests.fs @@ -22,11 +22,10 @@ open Microsoft.Quantum.QsCompiler.Transformations.Monomorphization.Validation open Microsoft.Quantum.QsCompiler.Transformations.SearchAndReplace open Microsoft.VisualStudio.LanguageServer.Protocol open Xunit -open Xunit.Abstractions -type LinkingTests (output:ITestOutputHelper) = - inherit CompilerTests(CompilerTests.Compile (Path.Combine ("TestCases", "LinkingTests" )) ["Core.qs"; "InvalidEntryPoints.qs"] [], output) +type LinkingTests () = + inherit CompilerTests(CompilerTests.Compile (Path.Combine ("TestCases", "LinkingTests"), ["Core.qs"; "InvalidEntryPoints.qs"])) let compilationManager = new CompilationUnitManager(new Action (fun ex -> failwith ex.Message), isExecutable = true) @@ -78,7 +77,7 @@ type LinkingTests (output:ITestOutputHelper) = compilationManager.AddOrUpdateSourceFileAsync(file) |> ignore let built = compilationManager.Build() compilationManager.TryRemoveSourceFileAsync(fileId, false) |> ignore - let tests = new CompilerTests(built, output) + let tests = CompilerTests built for callable in built.Callables.Values |> Seq.filter inFile do tests.Verify (callable.FullName, diag) diff --git a/src/QsCompiler/Tests.Compiler/LocalVerificationTests.fs b/src/QsCompiler/Tests.Compiler/LocalVerificationTests.fs index 5e08e5305f..ea8dae81f3 100644 --- a/src/QsCompiler/Tests.Compiler/LocalVerificationTests.fs +++ b/src/QsCompiler/Tests.Compiler/LocalVerificationTests.fs @@ -10,15 +10,14 @@ open Microsoft.Quantum.QsCompiler.Diagnostics open Microsoft.Quantum.QsCompiler.SyntaxExtensions open Microsoft.Quantum.QsCompiler.SyntaxTree open Xunit -open Xunit.Abstractions -type LocalVerificationTests (output:ITestOutputHelper) = +type LocalVerificationTests () = inherit CompilerTests( - CompilerTests.Compile "TestCases" [ + CompilerTests.Compile ("TestCases", [ "General.qs"; "LocalVerification.qs"; "Types.qs"; Path.Combine ("LinkingTests", "Core.qs"); - ] [], output) + ])) member private this.Expect name (diag : IEnumerable) = let ns = "Microsoft.Quantum.Testing.LocalVerification" |> NonNullable<_>.New diff --git a/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs b/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs new file mode 100644 index 0000000000..000161c30a --- /dev/null +++ b/src/QsCompiler/Tests.Compiler/TestCases/CapabilityVerification.qs @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +/// Test cases for verification of execution target runtime capabilities. +namespace Microsoft.Quantum.Testing.CapabilityVerification { + internal operation X(q : Qubit) : Unit { + body intrinsic; + } + + internal operation M(q : Qubit) : Result { + body intrinsic; + } + + function ResultAsBool(result : Result) : Bool { + return result == Zero ? false | true; + } + + function ResultAsBoolNeq(result : Result) : Bool { + return result != One ? false | true; + } + + operation ResultAsBoolOp(result : Result) : Bool { + return result == Zero ? false | true; + } + + function ResultAsBoolNeqOp(result : Result) : Bool { + return result != One ? false | true; + } + + operation ResultAsBoolOpReturnIf(result : Result) : Bool { + if (result == Zero) { + return false; + } else { + return true; + } + } + + operation ResultAsBoolNeqOpReturnIf(result : Result) : Bool { + if (result != One) { + return false; + } else { + return true; + } + } + + operation ResultAsBoolOpSetIf(result : Result) : Bool { + mutable b = false; + if (result == One) { + set b = true; + } + return b; + } + + operation ResultAsBoolNeqOpSetIf(result : Result) : Bool { + mutable b = false; + if (result != Zero) { + set b = true; + } + return b; + } + + operation EmptyIf(result : Result) : Unit { + if (result == Zero) { } + } + + operation EmptyIfNeq(result : Result) : Unit { + if (result != Zero) { } + } + + operation Reset(q : Qubit) : Unit { + if (M(q) == One) { + X(q); + } + } + + operation ResetNeq(q : Qubit) : Unit { + if (M(q) != Zero) { + X(q); + } + } +} diff --git a/src/QsCompiler/Tests.Compiler/TestUtils/SetupVerificationTests.fs b/src/QsCompiler/Tests.Compiler/TestUtils/SetupVerificationTests.fs index eb03d7079a..2187681451 100644 --- a/src/QsCompiler/Tests.Compiler/TestUtils/SetupVerificationTests.fs +++ b/src/QsCompiler/Tests.Compiler/TestUtils/SetupVerificationTests.fs @@ -12,13 +12,13 @@ open Microsoft.Quantum.QsCompiler open Microsoft.Quantum.QsCompiler.CompilationBuilder open Microsoft.Quantum.QsCompiler.DataTypes open Microsoft.Quantum.QsCompiler.Diagnostics +open Microsoft.Quantum.QsCompiler.ReservedKeywords.AssemblyConstants open Microsoft.Quantum.QsCompiler.SyntaxTree open Microsoft.VisualStudio.LanguageServer.Protocol open Xunit -open Xunit.Abstractions -type CompilerTests (compilation : CompilationUnitManager.Compilation, output:ITestOutputHelper) = +type CompilerTests (compilation : CompilationUnitManager.Compilation) = let syntaxTree = let mutable compilation = compilation.BuiltCompilation @@ -90,9 +90,11 @@ type CompilerTests (compilation : CompilationUnitManager.Compilation, output:ITe if other.Any() then NotImplementedException "unknown diagnostics item to verify" |> raise - static member Compile srcFolder files references = + static member Compile (srcFolder, files, ?references, ?capabilities) = + let references = defaultArg references [] + let capabilities = defaultArg capabilities RuntimeCapabilities.Unknown let compileFiles (files : IEnumerable<_>) = - let mgr = new CompilationUnitManager(fun ex -> failwith ex.Message) + let mgr = new CompilationUnitManager((fun ex -> failwith ex.Message), capabilities = capabilities) files.ToImmutableDictionary(Path.GetFullPath >> Uri, File.ReadAllText) |> CompilationUnitManager.InitializeFileManagers |> mgr.AddOrUpdateSourceFilesAsync diff --git a/src/QsCompiler/Tests.Compiler/Tests.Compiler.fsproj b/src/QsCompiler/Tests.Compiler/Tests.Compiler.fsproj index d5f9528eff..eca202e1a5 100644 --- a/src/QsCompiler/Tests.Compiler/Tests.Compiler.fsproj +++ b/src/QsCompiler/Tests.Compiler/Tests.Compiler.fsproj @@ -105,22 +105,28 @@ PreserveNewest - - PreserveNewest + + PreserveNewest PreserveNewest + + PreserveNewest + PreserveNewest - + PreserveNewest - + PreserveNewest - + + PreserveNewest + + PreserveNewest @@ -132,8 +138,8 @@ PreserveNewest - - PreserveNewest + + PreserveNewest @@ -141,6 +147,7 @@ + diff --git a/src/QsCompiler/Tests.Compiler/TypeCheckingTests.fs b/src/QsCompiler/Tests.Compiler/TypeCheckingTests.fs index 60f2136d45..b4eda29e7e 100644 --- a/src/QsCompiler/Tests.Compiler/TypeCheckingTests.fs +++ b/src/QsCompiler/Tests.Compiler/TypeCheckingTests.fs @@ -9,11 +9,10 @@ open Microsoft.Quantum.QsCompiler.Diagnostics open Microsoft.Quantum.QsCompiler.SyntaxExtensions open Microsoft.Quantum.QsCompiler.SyntaxTree open Xunit -open Xunit.Abstractions -type TypeCheckingTests (output:ITestOutputHelper) = - inherit CompilerTests(CompilerTests.Compile "TestCases" ["General.qs"; "TypeChecking.qs"; "Types.qs"] [], output) +type TypeCheckingTests () = + inherit CompilerTests(CompilerTests.Compile ("TestCases", ["General.qs"; "TypeChecking.qs"; "Types.qs"])) member private this.Expect name (diag : IEnumerable) = let ns = "Microsoft.Quantum.Testing.TypeChecking" |> NonNullable<_>.New