/// <summary>
        /// Returns an array with all usages of the identifier at the given position (if any) as DocumentHighlights.
        /// Returns null if some parameters are unspecified (null),
        /// or if the specified position is not a valid position within the currently processed file content,
        /// or if no identifier exists at the specified position at this time.
        /// </summary>
        public static DocumentHighlight[]? DocumentHighlights(this FileContentManager file, CompilationUnit compilation, Position?position)
        {
            DocumentHighlight AsHighlight(Lsp.Range range) =>
            new DocumentHighlight
            {
                Range = range, Kind = DocumentHighlightKind.Read
            };

            if (file == null || position is null)
            {
                return(null);
            }
            var found = file.TryGetReferences(
                compilation,
                position,
                out var declLocation,
                out var locations,
                limitToSourceFiles: ImmutableHashSet.Create(file.FileName));

            if (!found)
            {
                return(null);
            }

            QsCompilerError.Verify(declLocation == null || declLocation.Uri == file.Uri, "location outside current file");
            var highlights = locations.Select(loc =>
            {
                QsCompilerError.Verify(loc.Uri == file.Uri, "location outside current file");
                return(AsHighlight(loc.Range));
            });

            return(declLocation != null
                ? new[] { AsHighlight(declLocation.Range) }.Concat(highlights).ToArray()
                : highlights.ToArray());
        }
        /// <summary>
        /// Dequeues all lines whose tokens has changed and verifies the positions of these tokens.
        /// Does nothing if no lines have been modified.
        /// Recomputes and pushes the context diagnostics for the processed tokens otherwise.
        /// </summary>
        internal static void UpdateContext(this FileContentManager file)
        {
            file.SyncRoot.EnterUpgradeableReadLock();
            try
            {
                var changedLines = file.DequeueTokenChanges();
                if (!changedLines.Any())
                {
                    return;
                }
                QsCompilerError.RaiseOnFailure(
                    () =>
                {
                    var verifiedLines = file.VerifyContext(changedLines, out List <Diagnostic> diagnostics);
                    file.UpdateContextDiagnostics(verifiedLines, diagnostics);
                },
                    "updating the ContextDiagnostics failed");

                var edited = file.CallablesWithContentModifications(changedLines);
                file.MarkCallableAsContentEdited(edited);
            }
            finally
            {
                file.SyncRoot.ExitUpgradeableReadLock();
            }
        }
        /// <summary>
        /// Assuming both the current tokens and the tokens to update are sorted according to their range,
        /// merges the current and updated tokens such that the merged collection is sorted as well.
        /// </summary>
        /// <exception cref="QsCompilerException">The token verification for the merged collection failed.</exception>
        internal static List <CodeFragment> MergeTokens(IEnumerable <CodeFragment> current, IEnumerable <CodeFragment> updated)
        {
            var merged = new List <CodeFragment>(0);

            void NextBatch(ref IEnumerable <CodeFragment> batch, IEnumerable <CodeFragment> next)
            {
                if (next.Any())
                {
                    var start = next.First().Range.Start;
                    merged.AddRange(batch.TakeWhile(TokensUpTo(start)));
                    batch = batch.SkipWhile(TokensUpTo(start)).ToList();
                }
                else
                {
                    merged.AddRange(batch);
                    batch = Enumerable.Empty <CodeFragment>();
                }
            }

            while (updated.Any() || current.Any())
            {
                NextBatch(ref current, updated);
                NextBatch(ref updated, current);
            }

            var mergedTokens = merged.ToList();

            QsCompilerError.RaiseOnFailure(() => VerifyTokenOrdering(mergedTokens), "merged tokens are not ordered");
            return(mergedTokens);
        }
Example #4
0
        /// <summary>
        /// Logs the tokenization of each file in the given compilation as Information using the given logger.
        /// If the id of a file is consistent with the one assigned to a code snippet,
        /// strips the tokens that correspond to the wrapping defined by WrapSnippet.
        /// Throws an ArgumentNullException if the given compilation is null.
        /// </summary>
        private static void PrintContentTokenization(Compilation compilation, ILogger logger)
        {
            if (compilation == null)
            {
                throw new ArgumentNullException(nameof(compilation));
            }
            foreach (var file in compilation.SourceFiles)
            {
                var tokenization  = compilation.Tokenization[file].Select(tokens => tokens.Select(token => token.Kind));
                var stripWrapping = Options.IsCodeSnippet(file);
                QsCompilerError.Verify(!stripWrapping || tokenization.Count() >= 4,
                                       "expecting at least four lines of code for the compilation of a code snippet");

                if (stripWrapping)
                {
                    tokenization = tokenization.Skip(2).Take(tokenization.Count() - 4).ToImmutableArray();
                }
                var serialization = tokenization
                                    .Select(line => line.Select(item => JsonConvert.SerializeObject(item, Newtonsoft.Json.Formatting.Indented)))
                                    .Zip(Enumerable.Range(1, tokenization.Count()),
                                         (ts, i) => ts.Any() ? $"\n[ln {i}]: \n{String.Join("\n", ts)} \n" : "");

                serialization = new string[] { "" }.Concat(serialization);
                logger.Log(
                    InformationCode.BuiltTokenization,
                    Enumerable.Empty <string>(),
                    stripWrapping ? null : file.Value,
                    messageParam: serialization.ToArray()
                    );
            }
        }
Example #5
0
        /// <summary>
        /// Extracts the code fragments based on the current file content that need to be re-processed due to content changes on the given lines.
        /// Ignores any whitespace or comments at the beginning of the file (whether they have changed or not).
        /// Ignores any whitespace or comments that occur after the last piece of code in the file.
        /// Throws an ArgumentNullException if any of the arguments is null.
        /// </summary>
        private static IEnumerable <CodeFragment> FragmentsToProcess(this FileContentManager file, SortedSet <int> changedLines)
        {
            // NOTE: I suggest not to touch this routine unless absolutely necessary...(things *will* break)
            if (file == null)
            {
                throw new ArgumentNullException(nameof(file));
            }
            if (changedLines == null)
            {
                throw new ArgumentNullException(nameof(changedLines));
            }

            var iter       = changedLines.GetEnumerator();
            var lastInFile = LastInFile(file);

            Position processed = new Position(0, 0);

            while (iter.MoveNext())
            {
                QsCompilerError.Verify(0 <= iter.Current && iter.Current < file.NrLines(), "index out of range for changed line");
                if (processed.Line < iter.Current)
                {
                    var statementStart = file.PositionAfterPrevious(new Position(iter.Current, 0));
                    if (processed.IsSmallerThan(statementStart))
                    {
                        processed = statementStart;
                    }
                }

                while (processed.Line <= iter.Current && processed.IsSmallerThan(lastInFile))
                {
                    processed = processed.Copy(); // because we don't want to modify the ending of the previous code fragment ...
                    var nextEnding     = file.FragmentEnd(ref processed);
                    var extractedPiece = file.GetCodeSnippet(new Range {
                        Start = processed, End = nextEnding
                    });

                    // constructing the CodeFragment -
                    // NOTE: its Range.End is the position of the delimiting char (if such a char exists), i.e. the position right after Code ends

                    if (extractedPiece.Length > 0) // length = 0 can occur e.g. if the last piece of code in the file does not terminate with a statement ending
                    {
                        var code = file.GetLine(nextEnding.Line).ExcessBracketPositions.Contains(nextEnding.Character - 1)
                            ? extractedPiece.Substring(0, extractedPiece.Length - 1)
                            : extractedPiece;
                        if (code.Length == 0 || !CodeFragment.DelimitingChars.Contains(code.Last()))
                        {
                            code = $"{code}{CodeFragment.MissingDelimiter}";
                        }

                        var endChar   = nextEnding.Character - (extractedPiece.Length - code.Length) - 1;
                        var codeRange = new Range {
                            Start = processed, End = new Position(nextEnding.Line, endChar)
                        };
                        yield return(new CodeFragment(file.IndentationAt(codeRange.Start), codeRange, code.Substring(0, code.Length - 1), code.Last()));
                    }
                    processed = nextEnding;
                }
            }
        }
Example #6
0
        /// <summary>
        /// Givent a position, verifies that the position is within the given file, and
        /// returns the effective indentation (i.e. the indentation when ignoring excess brackets throughout the file)
        /// at that position (i.e. not including the char at the given position).
        /// </summary>
        internal static int IndentationAt(this FileContentManager file, Position pos)
        {
            if (!file.ContainsPosition(pos))
            {
                throw new ArgumentException("given position is not within file");
            }
            var line  = file.GetLine(pos.Line);
            var index = pos.Column;

            // check if the given position is within a string or a comment, or denotes an excess bracket,
            // and find the next closest position that isn't
            if (index >= line.WithoutEnding.Length)
            {
                return(line.FinalIndentation());                                   // not necessary, but saves doing the rest of the computation
            }
            index = line.FindInCode(trimmed => trimmed.Length - 1, 0, pos.Column); // if the given position is within a string, then this is the most convenient way to get the closest position that isn't..
            if (index < 0)
            {
                return(line.Indentation); // perfectly valid scenario (if there is no relevant code before the given position)
            }

            // check how much the indentation changes in all valid (i.e. non-comment, non-string, non-excess-brackets) code up to that point
            // NOTE: per specification, this routine returns the indentation not incuding the char at the given position,
            // but if this char was within non-code text, then we need to include this char ... see (*)
            var indexInCode = IndexInRelevantCode(index, line);

            QsCompilerError.Verify(indexInCode >= 0, "index in code should be positive");
            var code        = RelevantCode(line).Substring(0, index < pos.Column ? indexInCode + 1 : indexInCode); // (*) - yep, that's a bit awkward, but the cleanest I can come up with right now
            var indentation = line.Indentation + NrIndents(code) - NrUnindents(code);

            QsCompilerError.Verify(indentation >= 0, "computed indentation at any position in the file should be positive");
            return(indentation);
        }
Example #7
0
        /// <summary>
        /// Logs the given text in the editor.
        /// </summary>
        internal static void LogToWindow(this QsLanguageServer server, string text, MessageType severity)
        {
            var message = AsMessageParams(text, severity);

            QsCompilerError.Verify(server != null && message != null, "cannot log message - given server or text was null");
            _ = server.NotifyClientAsync(Methods.WindowLogMessageName, message);
        }
Example #8
0
        /// <summary>
        /// Logs the content of each file in the given compilation as Information using the given logger.
        /// If the id of a file is consistent with the one assigned to a code snippet,
        /// strips the lines of code that correspond to the wrapping defined by WrapSnippet.
        /// Throws an ArgumentNullException if the given compilation is null.
        /// </summary>
        private static void PrintFileContentInMemory(Compilation compilation, ILogger logger)
        {
            if (compilation == null)
            {
                throw new ArgumentNullException(nameof(compilation));
            }
            foreach (var file in compilation.SourceFiles)
            {
                IEnumerable <string> inMemory = compilation.FileContent[file];
                var stripWrapping             = Options.IsCodeSnippet(file);
                QsCompilerError.Verify(!stripWrapping || inMemory.Count() >= 4,
                                       "expecting at least four lines of code for the compilation of a code snippet");

                if (stripWrapping)
                {
                    inMemory = inMemory.Skip(2).Take(inMemory.Count() - 4);
                }
                logger.Log(
                    InformationCode.FileContentInMemory,
                    Enumerable.Empty <string>(),
                    stripWrapping ? null : file.Value,
                    messageParam: $"{Environment.NewLine}{String.Concat(inMemory)}"
                    );
            }
        }
Example #9
0
        // routine(s) called by the FileContentManager upon updating a file

        /// <summary>
        /// Attempts to compute an incremental update for the change specified by start, count and newText, and updates file accordingly.
        /// The given argument newText replaces the entire lines from start to (but not including) start + count.
        /// If the given change is null, then (only) the currently queued unprocessed changes are processed.
        /// Throws an ArgumentNullException if file is null.
        /// Any other exceptions should be throws (and caught, and possibly re-thrown) during the updating.
        /// </summary>
        internal static void UpdateScopeTacking(this FileContentManager file, TextDocumentContentChangeEvent change)
        {
            if (file == null)
            {
                throw new ArgumentNullException(nameof(file));
            }

            /// <summary>
            /// Replaces the lines in the range [start, end] with those for the given text.
            /// </summary>
            void ComputeUpdate(int start, int end, string text)
            {
                QsCompilerError.Verify(start >= 0 && end >= start && end < file.NrLines(), "invalid range for update");

                // since both LF and CR in VS cause a line break on their own,
                // we need to check if the change causes subequent CR LF to merge into a single line break
                if (text.StartsWith(Utils.LF) && start > 0 && file.GetLine(start - 1).Text.EndsWith(Utils.CR))
                {
                    text = file.GetLine(--start).Text + text;
                }

                // we need to check if the change causes the next line to merge with the (last) changed line
                if (end + 1 < file.NrLines() && !Utils.EndOfLine.Match(text).Success)
                {
                    text = text + file.GetLine(++end).Text;
                }

                var newLines = Utils.SplitLines(text);
                var count    = end - start + 1;

                // note that the last line in the file won't end with a line break,
                // and is hence only captured by SplitLines if it is not empty
                // -> we therefore manually add the last line in the file if it is empty
                if (newLines.Length == 0 || // the case if the file will be empty after the update
                    (start + count == file.NrLines() && Utils.EndOfLine.Match(newLines.Last()).Success))
                {
                    newLines = newLines.Concat(new string[] { String.Empty }).ToArray();
                }

                QsCompilerError.Verify(newLines.Any(), "should have at least one line to replace");
                file.Update(start, count, newLines);
            }

            file.SyncRoot.EnterUpgradeableReadLock();
            try
            {
                // process the currently queued changes if necessary
                if (file.DequeueUnprocessedChanges(out int start, out string text))
                {
                    ComputeUpdate(start, start, text);
                }

                // process the given change if necessary
                if (change != null)
                {
                    ComputeUpdate(change.Range.Start.Line, change.Range.End.Line, Utils.GetTextChangedLines(file, change));
                }
            }
            finally { file.SyncRoot.ExitUpgradeableReadLock(); }
        }
        /// <summary>
        /// Returns true if the given file contains any tokens overlapping with the given fragment.
        /// The range of the tokens in the file is assumed to be relative to their start line (the index at which they are listed),
        /// whereas the range of the given fragment is assumed to be the absolute range.
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="range"/> is not a valid range within <paramref name="file"/>.</exception>
        internal static bool ContainsTokensOverlappingWith(this FileContentManager file, Range range)
        {
            if (!file.ContainsRange(range))
            {
                throw new ArgumentOutOfRangeException(nameof(range));
            }

            var(start, end) = (range.Start.Line, range.End.Line);
            if (start != end && file.GetTokenizedLines(start + 1, end - start - 1).SelectMany(x => x).Any())
            {
                return(true);
            }

            var inRange = file.GetTokenizedLine(start).Where(TokensAfter(Position.Create(0, range.Start.Column))); // checking tokens overlapping with range.Start below

            inRange = start == end
                ? inRange.Where(TokensStartingBefore(Position.Create(0, range.End.Column)))
                : inRange.Concat(file.GetTokenizedLine(end).Where(TokensStartingBefore(Position.Create(0, range.End.Column))));

            if (inRange.Any())
            {
                QsCompilerError.Raise($"{range.DiagnosticString()} overlaps for start = {start}, end = {end}, \n\n" +
                                      $"{string.Join("\n", file.GetTokenizedLine(start).Select(x => $"{x.Range.DiagnosticString()}"))},\n\n " +
                                      $"{string.Join("\n", file.GetTokenizedLine(end).Select(x => $"{x.Range.DiagnosticString()}"))},");
                return(true);
            }

            var overlapsWithStart = file.TryGetFragmentAt(range.Start, out _);

            return(overlapsWithStart != null);
        }
Example #11
0
        }                                                                                           // will raise an exception if file is null

        // the actual update routine

        /// <summary>
        /// Attempts to perform the necessary updates when replacing the range [start, start + count) by newText for the given file
        /// wrapping each step in a QsCompilerError.RaiseOnFailure.
        /// </summary>
        private static void Update(this FileContentManager file, int start, int count, IEnumerable <string> newText)
        {
            CodeLine[] replacements = QsCompilerError.RaiseOnFailure(() =>
                                                                     ComputeCodeLines(newText, start > 0 ? file.GetLine(start - 1) : null).ToArray(),
                                                                     "scope tracking update failed during computing the replacements");

            IEnumerable <CodeLine> updateRemaining = QsCompilerError.RaiseOnFailure(() =>
                                                                                    ComputeUpdates(file, start, count, replacements),
                                                                                    "scope tracking update failed during computing the updates");

            QsCompilerError.RaiseOnFailure(() =>
            {
                if (updateRemaining == null)
                {
                    file.ContentUpdate(start, count, replacements);
                }
                else
                {
                    file.ContentUpdate(start, file.NrLines() - start, replacements.Concat(updateRemaining).ToArray());
                }
            }, "the proposed ContentUpdate failed");

            QsCompilerError.RaiseOnFailure(() =>
            {
                if (updateRemaining == null)
                {
                    file.AddScopeDiagnostics(file.ComputeScopeDiagnostics(start, replacements.Length));
                }
                else
                {
                    file.AddScopeDiagnostics(file.ComputeScopeDiagnostics(start));
                }
                file.AddScopeDiagnostics(file.CheckForMissingClosings());
            }, "updating the scope diagnostics failed");
        }
Example #12
0
        /// <summary>
        /// Dequeues all lines whose content has changed and extracts the code fragments overlapping with those lines that need to be reprocessed.
        /// Does nothing if no lines have been modified.
        /// Recomputes and pushes the syntax diagnostics for the extracted fragments and all end-of-file diagnostics otherwise.
        /// Processes the extracted fragment and inserts the processed fragments into the corresponding data structure
        /// Throws an ArgumentNullException if file is null.
        /// </summary>
        internal static void UpdateLanguageProcessing(this FileContentManager file)
        {
            if (file == null)
            {
                throw new ArgumentNullException(nameof(file));
            }

            file.SyncRoot.EnterUpgradeableReadLock();
            try
            {
                var changedLines = file.DequeueContentChanges();
                if (!changedLines.Any())
                {
                    return;
                }

                var reprocess   = QsCompilerError.RaiseOnFailure(() => file.FragmentsToProcess(changedLines).ToList(), "processing the edited lines failed");
                var diagnostics = reprocess.CheckForEmptyFragments(file.FileName.Value)
                                  .Concat(ParseCode(ref reprocess, file.FileName.Value)).ToList();

                QsCompilerError.RaiseOnFailure(() => file.TokensUpdate(reprocess), "the computed token update failed");
                QsCompilerError.RaiseOnFailure(() => file.AddSyntaxDiagnostics(diagnostics), "updating the SyntaxDiagnostics failed");
            }
            finally { file.SyncRoot.ExitUpgradeableReadLock(); }
        }
Example #13
0
        /// <summary>
        /// Given the initial indentation of a sequence of CodeLines, and a sequence of code lines with the correct string delimiters set,
        /// computes and sets the correct indentation level and excess bracket positions for each line.
        /// The previous line being null or not provided indicates that there is no previous line.
        /// </summary>
        private static IEnumerable <CodeLine> SetIndentations(IEnumerable <CodeLine> lines, int currentIndentation)
        {
            foreach (var line in lines)
            {
                var updated = line.SetIndentation(currentIndentation);

                var stripped    = RemoveStringsAndComment(updated);
                var nrIndents   = NrIndents(stripped);
                var nrUnindents = NrUnindents(stripped);

                if (currentIndentation - nrUnindents < 0)
                {
                    // in this case it is possible (but not necessarily the case) that there are excess brackets
                    // if closings occur before openings, then we can have excess brackets even when the indentation is larger than zero
                    updated             = updated.SetExcessBrackets(ComputeExcessClosings(updated, currentIndentation));
                    currentIndentation += updated.ExcessBracketPositions.Count();
                }
                else
                {
                    updated = updated.SetExcessBrackets(new List <int>().AsReadOnly());
                }

                currentIndentation += nrIndents - nrUnindents;
                QsCompilerError.Verify(currentIndentation >= 0, "initial indentation should always be larger or equal to zero");
                yield return(updated);
            }
        }
Example #14
0
        // TODO: this needs to be made more robust.
        // We currently rely on the fact that all attributes defined by the Q# compiler
        // have a single constructor taking a single string argument.
        private static (string, string)? GetAttribute(MetadataReader metadataReader, CustomAttribute attribute)
        {
            var attrType = GetAttributeType(metadataReader, attribute);

            QsCompilerError.Verify(attrType.HasValue, "the type of the custom attribute could not be determined");
            var(ns, name) = attrType.Value;

            var attrNS = metadataReader.GetString(ns);

            if (attrNS.StartsWith("Microsoft.Quantum", StringComparison.InvariantCulture))
            {
                var attrReader = metadataReader.GetBlobReader(attribute.Value);
                _ = attrReader.ReadUInt16(); // All custom attributes start with 0x0001, so read that now and discard it.
                try
                {
                    var serialization = attrReader.ReadSerializedString(); // FIXME: this needs to be made more robust
                    return(metadataReader.GetString(name), serialization);
                }
                catch
                {
                    return(null);
                }
            }
            return(null);
        }
                private (TypedExpression Id, TypedExpression Args)? IsValidScope(QsScope? scope)
                {
                    // if the scope has exactly one statement in it and that statement is a call like expression statement
                    if (scope != null &&
                        scope.Statements.Length == 1 &&
                        scope.Statements[0].Statement is QsStatementKind.QsExpressionStatement expr &&
                        expr.Item.ResolvedType.Resolution.IsUnitType &&
                        expr.Item.Expression is ExpressionKind.CallLikeExpression call &&
                        !TypedExpression.IsPartialApplication(expr.Item.Expression) &&
                        call.Item1.Expression is ExpressionKind.Identifier)
                    {
                        var newCallIdentifier = call.Item1;
                        var callTypeArguments = expr.Item.TypeParameterResolutions;

                        // This relies on anything having type parameters must be a global callable.
                        if (newCallIdentifier.Expression is ExpressionKind.Identifier id &&
                            id.Item1 is Identifier.GlobalCallable global &&
                            callTypeArguments.Any())
                        {
                            // We are dissolving the application of arguments here, so the call's type argument
                            // resolutions have to be moved to the 'identifier' sub expression.
                            var combination           = new TypeResolutionCombination(expr.Item);
                            var combinedTypeArguments = combination.CombinedResolutionDictionary.FilterByOrigin(global.Item);
                            QsCompilerError.Verify(combination.IsValid, "failed to combine type parameter resolution");

                            var globalCallable = this.SharedState.Compilation.Namespaces
                                                 .Where(ns => ns.Name.Equals(global.Item.Namespace))
                                                 .Callables()
                                                 .FirstOrDefault(c => c.FullName.Name.Equals(global.Item.Name));

                            QsCompilerError.Verify(globalCallable != null, $"Could not find the global reference {global.Item}.");

                            var callableTypeParameters = globalCallable.Signature.TypeParameters.Select(param =>
                            {
                                var name = param as QsLocalSymbol.ValidName;
                                QsCompilerError.Verify(!(name is null), "Invalid type parameter name.");
                                return(name);
                            });

                            newCallIdentifier = new TypedExpression(
                                ExpressionKind.NewIdentifier(
                                    id.Item1,
                                    QsNullable <ImmutableArray <ResolvedType> > .NewValue(
                                        callableTypeParameters
                                        .Select(x => combinedTypeArguments[Tuple.Create(global.Item, x.Item)]).ToImmutableArray())),
                                TypedExpression.AsTypeArguments(combinedTypeArguments),
                                call.Item1.ResolvedType,
                                call.Item1.InferredInformation,
                                call.Item1.Range);
                        }

                        return(newCallIdentifier, call.Item2);
                    }

                    return(null);
                }
 private void OnBufferOverflow(object sender, ErrorEventArgs e)
 {
     // Todo: We should at some point implement a mechanism to try and recover from buffer overflows in the file watcher.
     try
     {
         QsCompilerError.Raise($"buffer overflow in file system watcher: \n{e.GetException()}");
     }
     catch (Exception ex)
     {
         this.onException(ex);
     }
 }
Example #17
0
 private Task ProcessingTaskAsync(Action action) =>
 new Task(() =>
 {
     try
     {
         QsCompilerError.RaiseOnFailure(action, this.exceptionHeader);
     }
     catch (Exception ex)
     {
         this.logException(ex);
     }
 });
Example #18
0
        /// <summary>
        /// Calls the Q# parser on each fragment, splitting one fragment into several if necessary
        /// (i.e. modifies the list of given fragments!).
        /// Fragments for which the code only consists of whitespace are left unchanged (i.e. the Kind remains set to null).
        /// Adds a suitable error to the returned diagnostics for each fragment that cannot be processed.
        /// Raises an ArgumentNullException if the given diagnostics or fragments are null.
        /// </summary>
        private static IEnumerable <Diagnostic> ParseCode(ref List <CodeFragment> fragments, string filename)
        {
            if (fragments == null)
            {
                throw new ArgumentNullException(nameof(fragments));
            }
            var processedFragments = new List <CodeFragment>(fragments.Count());
            var diagnostics        = new List <Diagnostic>();

            foreach (var snippet in fragments)
            {
                var snippetStart = snippet.GetRange().Start;
                var outputs      = Parsing.ProcessCodeFragment(snippet.Text);
                for (var outputIndex = 0; outputIndex < outputs.Length; ++outputIndex)
                {
                    var output        = outputs[outputIndex];
                    var fragmentRange = DiagnosticTools.GetAbsoluteRange(snippetStart, output.Range);
                    var fragment      = new CodeFragment(
                        snippet.Indentation,
                        fragmentRange,
                        output.Text.Value,
                        outputIndex == outputs.Length - 1 ? snippet.FollowedBy : CodeFragment.MissingDelimiter,
                        output.Kind);
                    processedFragments.Add(fragment);

                    var checkEnding = true; // if there is already a diagnostic overlapping with the ending, then don't bother checking the ending
                    foreach (var fragmentDiagnostic in output.Diagnostics)
                    {
                        var generated = Diagnostics.Generate(filename, fragmentDiagnostic, fragmentRange.Start);
                        diagnostics.Add(generated);

                        var fragmentEnd = fragment.GetRange().End;
                        var diagnosticGoesUpToFragmentEnd = fragmentEnd.IsWithinRange(generated.Range) || fragmentEnd.Equals(generated.Range.End);
                        if (fragmentDiagnostic.Diagnostic.IsError && diagnosticGoesUpToFragmentEnd)
                        {
                            checkEnding = false;
                        }
                    }
                    if (checkEnding)
                    {
                        diagnostics.AddRange(fragment.CheckFragmentDelimiters(filename));
                    }
                }
                if (outputs.Length == 0)
                {
                    processedFragments.Add(snippet); // keep empty fragments around (note that the kind is set to null in this case!)
                }
            }
            QsCompilerError.RaiseOnFailure(() => ContextBuilder.VerifyTokenOrdering(processedFragments), "processed fragments are not ordered properly and/or overlap");
            fragments = processedFragments;
            return(diagnostics);
        }
Example #19
0
        // external routines for context verification

        /// <summary>
        /// Given the line number of the lines that contain tokens that (possibly) have been modified,
        /// checks which callable declaration they can potentially belong to and returns the fully qualified name of those callables.
        /// Throws an ArgumentNullException if the given file or the collection of changed lines is null.
        /// </summary>
        internal static IEnumerable <(Range, QsQualifiedName)> CallablesWithContentModifications(this FileContentManager file, IEnumerable <int> changedLines)
        {
            if (file == null)
            {
                throw new ArgumentNullException(nameof(file));
            }
            if (changedLines == null)
            {
                throw new ArgumentNullException(nameof(changedLines));
            }

            var lastInFile = file.LastToken()?.GetFragment()?.GetRange()?.End ?? file.End();
            var callables  = file.GetCallableDeclarations().Select(tuple => // these are sorted according to their line number
            {
                var ns = file.TryGetNamespaceAt(tuple.Item2.Start);
                QsCompilerError.Verify(ns != null, "namespace for callable declaration should not be null"); // invalid namespace names default to an unknown namespace name, but remain included in the compilation
                return(tuple.Item2.Start, new QsQualifiedName(NonNullable <string> .New(ns), tuple.Item1));
            }).ToList();

            // NOTE: The range of modifications that has to trigger an update of the syntax tree for a callable
            // does need to go up to and include modifications to the line containing the next callable!
            // Otherwise inserting a callable declaration in the middle of an existing callable does not trigger the right behavior!
            (Range, QsQualifiedName) TypeCheckingRange((Position, QsQualifiedName) lastPreceding, IEnumerable <(Position, QsQualifiedName)> next)
            {
                var callableStart = lastPreceding.Item1;
                var callableEnd   = next.Any() ? next.First().Item1 : lastInFile;

                return(new Range {
                    Start = callableStart, End = callableEnd
                }, lastPreceding.Item2);
            }

            foreach (var lineNr in changedLines)
            {
                bool precedes((Position, QsQualifiedName) tuple) => tuple.Item1.Line < lineNr;

                var preceding = callables.TakeWhile(precedes);
                var following = callables.SkipWhile(precedes);

                if (preceding.Any())
                {
                    yield return(TypeCheckingRange(preceding.Last(), following));
                }
                if (following.Any() && following.First().Item1.Line == lineNr)
                {
                    yield return(TypeCheckingRange(following.First(), following.Skip(1)));
                }
            }
        }
Example #20
0
        /// <summary>
        /// Returns the position of fst relative to snd under the assumption that both positions are zero-based.
        /// Throws an ArgumentNullException if a given position is null.
        /// Throws an ArgumentException if a given position is not valid, or if fst is smaller than snd.
        /// </summary>
        internal static Position Subtract(this Position fst, Position snd)
        {
            if (!IsValidPosition(fst))
            {
                throw new ArgumentException(nameof(fst));
            }
            if (!IsValidPosition(snd))
            {
                throw new ArgumentException(nameof(snd));
            }
            if (fst.IsSmallerThan(snd))
            {
                throw new ArgumentException(nameof(snd), "the position to subtract from needs to be larger than the position to subract");
            }
            var relPos = new Position(fst.Line - snd.Line, fst.Line == snd.Line ? fst.Character - snd.Character : fst.Character);

            QsCompilerError.Verify(snd.Add(relPos).Equals(fst), "adding the relative position to snd does not equal fst");
            return(relPos);
        }
Example #21
0
        /// <summary>
        /// Verifies the given stringDelimiters and returns the given text without the content between the delimiters.
        /// </summary>
        private static string RemoveStrings(string text, IEnumerable <int> stringDelimiters)
        {
            QsCompilerError.RaiseOnFailure(() => VerifyStringDelimiters(text, stringDelimiters), "invalid delimiters for given text in call to RemoveStrings");

            var iter    = stringDelimiters.GetEnumerator();
            var trimmed =
                iter.MoveNext() ?
                iter.Current < 0 ? string.Empty : text.Substring(0, StartDelimiter(iter.Current)) :
                text;

            while (iter.MoveNext() && iter.Current < text.Length)
            {
                // Note: if modifications here are needed, modify Start- and EndDelimiter to make sure these changes are reflected in IndexInFullString
                var end   = iter.Current == text.Length ? text.Length : EndDelimiter(iter.Current); // end of a substring
                var start = iter.MoveNext() ? StartDelimiter(iter.Current) : text.Length;
                trimmed += text.Substring(end, start - end);
            }
            return(trimmed);
        }
Example #22
0
        /// <summary>
        /// Returns the completion environment at the given position in the file or null if the environment cannot be
        /// determined. Stores the code fragment found at or before the given position into an out parameter.
        /// </summary>
        private static (CompletionScope?, QsFragmentKind?) GetCompletionEnvironment(
            FileContentManager file, Position position, out CodeFragment?fragment)
        {
            if (!file.ContainsPosition(position))
            {
                fragment = null;
                return(null, null);
            }
            var token = GetTokenAtOrBefore(file, position);

            if (token is null)
            {
                fragment = null;
                return(null, null);
            }

            fragment = token.GetFragment();
            var relativeIndentation = fragment.Indentation - file.IndentationAt(position);

            QsCompilerError.Verify(Math.Abs(relativeIndentation) <= 1);

            var parents = new[] { token }
            .Concat(token.GetNonEmptyParents())
            .Skip(relativeIndentation + 1)
            .Select(index => index.GetFragment())
            .ToImmutableList();
            var scope =
                parents.IsEmpty ? CompletionScope.TopLevel
                : parents.FirstOrDefault()?.Kind?.IsNamespaceDeclaration ?? false ? CompletionScope.NamespaceTopLevel
                : parents.Any(parent => parent.Kind?.IsFunctionDeclaration ?? false) ? CompletionScope.Function
                : parents.FirstOrDefault()?.Kind?.IsOperationDeclaration ?? false ? CompletionScope.OperationTopLevel
                : parents.Any(parent => parent.Kind?.IsOperationDeclaration ?? false) ? CompletionScope.Operation
                : null;
            var previous =
                relativeIndentation == 0 && IsPositionAfterDelimiter(file, fragment, position) ? fragment.Kind
                : relativeIndentation == 0 ? token.PreviousOnScope()?.GetFragment().Kind
                : relativeIndentation == 1 ? token.GetNonEmptyParent()?.GetFragment().Kind
                : null;

            return(scope, previous);
        }
                /// <summary>
                /// Creates an operation call from the conditional control API for non-literal Result comparisons.
                /// The equalityScope and inequalityScope cannot both be null.
                /// </summary>
                private TypedExpression?CreateApplyConditionallyExpression(TypedExpression conditionExpr1, TypedExpression conditionExpr2, QsScope?equalityScope, QsScope?inequalityScope)
                {
                    QsCompilerError.Verify(equalityScope != null || inequalityScope != null, $"Cannot have null for both equality and inequality scopes when creating ApplyConditionally expressions.");

                    var equalityInfo   = this.IsValidScope(equalityScope);
                    var inequalityInfo = this.IsValidScope(inequalityScope);

                    if (!equalityInfo.HasValue && equalityScope != null)
                    {
                        return(null); // ToDo: Diagnostic message - equality block exists, but is not valid
                    }

                    if (!inequalityInfo.HasValue && inequalityScope != null)
                    {
                        return(null); // ToDo: Diagnostic message - inequality block exists, but is not valid
                    }

                    equalityInfo ??= this.GetNoOp();
                    inequalityInfo ??= this.GetNoOp();

                    // Get characteristic properties from global id
                    var props = ImmutableHashSet <OpProperty> .Empty;

                    if (equalityInfo.Value.Id.ResolvedType.Resolution is ResolvedTypeKind.Operation op)
                    {
                        props = op.Item2.Characteristics.GetProperties();
                        if (inequalityInfo.HasValue && inequalityInfo.Value.Id.ResolvedType.Resolution is ResolvedTypeKind.Operation defaultOp)
                        {
                            props = props.Intersect(defaultOp.Item2.Characteristics.GetProperties());
                        }
                    }

                    var controlOpInfo = (props.Contains(OpProperty.Adjointable), props.Contains(OpProperty.Controllable)) switch
                    {
                        (true, true) => BuiltIn.ApplyConditionallyCA,
                        (true, false) => BuiltIn.ApplyConditionallyA,
                        (false, true) => BuiltIn.ApplyConditionallyC,
                        (false, false) => BuiltIn.ApplyConditionally
                    };
        /// <summary>
        /// Creates an array literal with the given items, setting the range information to Null.
        /// If no items are given, creates an empty array of type Unit[].
        /// The resolved types for all of the given expressions must match,
        /// none of the given expressions should have a local quantum dependency,
        /// and all range information should be stripped from each given expression.
        /// </summary>
        internal static TypedExpression CreateValueArray(params TypedExpression[] expressions)
        {
            ResolvedType type = null;

            if (expressions.Any())
            {
                type = expressions.First().ResolvedType;
                QsCompilerError.Verify(expressions.All(expr => expr.ResolvedType.Equals(type)));
            }
            else
            {
                type = ResolvedType.New(ResolvedTypeKind.UnitType);
            }

            return(new TypedExpression
                   (
                       ExpressionKind.NewValueArray(expressions.ToImmutableArray()),
                       TypeArgsResolution.Empty,
                       ResolvedType.New(ResolvedTypeKind.NewArrayType(type)),
                       new InferredExpressionInformation(false, false),
                       QsNullable <Tuple <QsPositionInfo, QsPositionInfo> > .Null
                   ));
        }
                /// <summary>
                /// Creates an operation call from the conditional control API for non-literal Result comparisons.
                /// The equalityScope and inequalityScope cannot both be null.
                /// </summary>
                private TypedExpression CreateApplyConditionallyExpression(TypedExpression conditionExpr1, TypedExpression conditionExpr2, QsScope equalityScope, QsScope inequalityScope)
                {
                    QsCompilerError.Verify(equalityScope != null || inequalityScope != null, $"Cannot have null for both equality and inequality scopes when creating ApplyConditionally expressions.");

                    var(isEqualityValid, equalityId, equalityArgs)       = this.IsValidScope(equalityScope);
                    var(isInequaltiyValid, inequalityId, inequalityArgs) = this.IsValidScope(inequalityScope);

                    if (!isEqualityValid && equalityScope != null)
                    {
                        return(null); // ToDo: Diagnostic message - equality block exists, but is not valid
                    }

                    if (!isInequaltiyValid && inequalityScope != null)
                    {
                        return(null); // ToDo: Diagnostic message - inequality block exists, but is not valid
                    }

                    if (equalityScope == null)
                    {
                        (equalityId, equalityArgs) = this.GetNoOp();
                    }
                    else if (inequalityScope == null)
                    {
                        (inequalityId, inequalityArgs) = this.GetNoOp();
                    }

                    // Get characteristic properties from global id
                    var props = ImmutableHashSet <OpProperty> .Empty;

                    if (equalityId.ResolvedType.Resolution is ResolvedTypeKind.Operation op)
                    {
                        props = op.Item2.Characteristics.GetProperties();
                        if (inequalityId != null && inequalityId.ResolvedType.Resolution is ResolvedTypeKind.Operation defaultOp)
                        {
                            props = props.Intersect(defaultOp.Item2.Characteristics.GetProperties());
                        }
                    }

                    BuiltIn controlOpInfo;

                    (bool adj, bool ctl) = (props.Contains(OpProperty.Adjointable), props.Contains(OpProperty.Controllable));
                    if (adj && ctl)
                    {
                        controlOpInfo = BuiltIn.ApplyConditionallyCA;
                    }
                    else if (adj)
                    {
                        controlOpInfo = BuiltIn.ApplyConditionallyA;
                    }
                    else if (ctl)
                    {
                        controlOpInfo = BuiltIn.ApplyConditionallyC;
                    }
                    else
                    {
                        controlOpInfo = BuiltIn.ApplyConditionally;
                    }

                    // Takes a single TypedExpression of type Result and puts in into a
                    // value array expression with the given expression as its only item.
                    TypedExpression BoxResultInArray(TypedExpression expression) =>
                    new TypedExpression(
                        ExpressionKind.NewValueArray(ImmutableArray.Create(expression)),
                        TypeArgsResolution.Empty,
                        ResolvedType.New(ResolvedTypeKind.NewArrayType(ResolvedType.New(ResolvedTypeKind.Result))),
                        new InferredExpressionInformation(false, expression.InferredInformation.HasLocalQuantumDependency),
                        QsNullable <Tuple <QsPositionInfo, QsPositionInfo> > .Null);

                    var equality    = this.CreateValueTupleExpression(equalityId, equalityArgs);
                    var inequality  = this.CreateValueTupleExpression(inequalityId, inequalityArgs);
                    var controlArgs = this.CreateValueTupleExpression(
                        BoxResultInArray(conditionExpr1),
                        BoxResultInArray(conditionExpr2),
                        equality,
                        inequality);
                    var targetArgsTypes = ImmutableArray.Create(equalityArgs.ResolvedType, inequalityArgs.ResolvedType);

                    return(this.CreateControlCall(controlOpInfo, props, controlArgs, targetArgsTypes));
                }
Example #26
0
        // language server tools -
        // wrapping these into a try .. catch .. to make sure errors don't go unnoticed as they otherwise would

        public static T TryJTokenAs <T>(JToken arg)
            where T : class =>
        QsCompilerError.RaiseOnFailure(() => arg.ToObject <T>(), "could not cast given JToken");
        /// <summary>
        /// Returns the completion environment at the given position in the file or null if the environment cannot be
        /// determined. Stores the code fragment found at or before the given position into an out parameter.
        /// </summary>
        /// <exception cref="ArgumentNullException">Thrown when any argument is null.</exception>
        /// <exception cref="ArgumentException">Thrown when the position is invalid.</exception>
        private static (CompletionScope, QsFragmentKind) GetCompletionEnvironment(
            FileContentManager file, Position position, out CodeFragment fragment)
        {
            if (file == null)
            {
                throw new ArgumentNullException(nameof(file));
            }
            if (!Utils.IsValidPosition(position))
            {
                throw new ArgumentException(nameof(position));
            }
            if (!Utils.IsValidPosition(position, file))
            {
                // FileContentManager.IndentationAt will fail if the position is not within the file.
                fragment = null;
                return(null, null);
            }

            var token = GetTokenAtOrBefore(file, position);

            if (token == null)
            {
                fragment = null;
                return(null, null);
            }

            fragment = token.GetFragment();
            var relativeIndentation = fragment.Indentation - file.IndentationAt(position);

            QsCompilerError.Verify(Math.Abs(relativeIndentation) <= 1);
            var parents =
                new[] { token }.Concat(token.GetNonEmptyParents())
            .Skip(relativeIndentation + 1)
            .Select(t => t.GetFragment());

            CompletionScope scope = null;

            if (!parents.Any())
            {
                scope = CompletionScope.TopLevel;
            }
            else if (parents.Any() && parents.First().Kind.IsNamespaceDeclaration)
            {
                scope = CompletionScope.NamespaceTopLevel;
            }
            else if (parents.Where(parent => parent.Kind.IsFunctionDeclaration).Any())
            {
                scope = CompletionScope.Function;
            }
            else if (parents.Any() && parents.First().Kind.IsOperationDeclaration)
            {
                scope = CompletionScope.OperationTopLevel;
            }
            else if (parents.Where(parent => parent.Kind.IsOperationDeclaration).Any())
            {
                scope = CompletionScope.Operation;
            }

            QsFragmentKind previous = null;

            if (relativeIndentation == 0 && IsPositionAfterDelimiter(file, fragment, position))
            {
                previous = fragment.Kind;
            }
            else if (relativeIndentation == 0)
            {
                previous = token.PreviousOnScope()?.GetFragment().Kind;
            }
            else if (relativeIndentation == 1)
            {
                previous = token.GetNonEmptyParent()?.GetFragment().Kind;
            }

            return(scope, previous);
        }