Пример #1
0
        /// <summary>
        /// Returns workspace edits for correcting the casing or capitalization of unknown identifiers.
        /// </summary>
        private static IEnumerable <(string, WorkspaceEdit)> UnknownIdCaseSuggestions(
            this FileContentManager file, CompilationUnit compilation, IReadOnlyCollection <Diagnostic> diagnostics)
        {
            (string, WorkspaceEdit) SuggestedIdEdit(string suggestedId, Lsp.Range range)
            {
                var edit = new TextEdit {
                    Range = range, NewText = suggestedId
                };

                return($"Replace with \"{suggestedId}\".", file.GetWorkspaceEdit(edit));
            }

            var idSuggestions =
                from diagnostic in diagnostics
                where DiagnosticTools.ErrorType(ErrorCode.UnknownIdentifier)(diagnostic)
                from id in file.IdCaseSuggestions(diagnostic.Range.Start.ToQSharp(), compilation)
                select SuggestedIdEdit(id, diagnostic.Range);

            var typeSuggestions =
                from diagnostic in diagnostics
                where DiagnosticTools.ErrorType(ErrorCode.UnknownType)(diagnostic)
                from type in file.TypeCaseSuggestions(diagnostic.Range.Start.ToQSharp(), compilation)
                select SuggestedIdEdit(type, diagnostic.Range);

            return(idSuggestions.Concat(typeSuggestions));
        }
Пример #2
0
        /// <summary>
        /// Returns a sequence of suggestions on how errors for ambiguous types and callable in the given diagnostics can be fixed,
        /// given the file for which those diagnostics were generated and the corresponding compilation.
        /// Returns an empty enumerable if any of the given arguments is null.
        /// </summary>
        internal static IEnumerable <(string, WorkspaceEdit)> SuggestionsForAmbiguousIdentifiers
            (this FileContentManager file, CompilationUnit compilation, IEnumerable <Diagnostic> diagnostics)
        {
            if (file == null || diagnostics == null)
            {
                return(Enumerable.Empty <(string, WorkspaceEdit)>());
            }
            var ambiguousCallables = diagnostics.Where(DiagnosticTools.ErrorType(ErrorCode.AmbiguousCallable));
            var ambiguousTypes     = diagnostics.Where(DiagnosticTools.ErrorType(ErrorCode.AmbiguousType));

            if (!ambiguousCallables.Any() && !ambiguousTypes.Any())
            {
                return(Enumerable.Empty <(string, WorkspaceEdit)>());
            }

            (string, WorkspaceEdit) SuggestedNameQualification(NonNullable <string> suggestedNS, string id, Position pos)
            {
                var edit = new TextEdit {
                    Range = new LSP.Range {
                        Start = pos, End = pos
                    }, NewText = $"{suggestedNS.Value}."
                };

                return($"{suggestedNS.Value}.{id}", file.GetWorkspaceEdit(edit));
            }

            var suggestedIdQualifications = ambiguousCallables.Select(d => d.Range.Start)
                                            .SelectMany(pos => file.NamespaceSuggestionsForIdAtPosition(pos, compilation, out var id)
                                                        .Select(ns => SuggestedNameQualification(ns, id, pos)));
            var suggestedTypeQualifications = ambiguousTypes.Select(d => d.Range.Start)
                                              .SelectMany(pos => file.NamespaceSuggestionsForTypeAtPosition(pos, compilation, out var id)
                                                          .Select(ns => SuggestedNameQualification(ns, id, pos)));

            return(suggestedIdQualifications.Concat(suggestedTypeQualifications));
        }
Пример #3
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);
        }
Пример #4
0
 /// <summary>
 /// Generates a suitable Diagnostic from the given CompilerDiagnostic returned by the Q# compiler.
 /// The message range contained in the given CompilerDiagnostic is first converted to a Position object,
 /// and then added to the given positionOffset if the latter is not null.
 /// Throws an ArgumentNullException if the Range of the given CompilerDiagnostic is null.
 /// Throws an ArgumentOutOfRangeException if the contained range contains zero or negative entries, or if its Start is bigger than its End.
 /// </summary>
 internal static Diagnostic Generate(string filename, QsCompilerDiagnostic msg, Position positionOffset = null)
 {
     if (msg.Range == null)
     {
         throw new ArgumentNullException(nameof(msg.Range));
     }
     return(new Diagnostic
     {
         Severity = Severity(msg),
         Code = Code(msg),
         Source = filename,
         Message = msg.Message,
         Range = DiagnosticTools.GetAbsoluteRange(positionOffset, msg.Range)
     });
 }
Пример #5
0
        /// <summary>
        /// Returns a sequence of suggestions on how deprecated syntax can be updated based on the generated diagnostics,
        /// and given the file for which those diagnostics were generated.
        /// Returns an empty enumerable if any of the given arguments is null.
        /// </summary>
        internal static IEnumerable <(string, WorkspaceEdit)> SuggestionsForDeprecatedSyntax
            (this FileContentManager file, IEnumerable <Diagnostic> diagnostics)
        {
            if (file == null || diagnostics == null)
            {
                return(Enumerable.Empty <(string, WorkspaceEdit)>());
            }
            var deprecatedUnitTypes         = diagnostics.Where(DiagnosticTools.WarningType(WarningCode.DeprecatedUnitType));
            var deprecatedNOToperators      = diagnostics.Where(DiagnosticTools.WarningType(WarningCode.DeprecatedNOToperator));
            var deprecatedANDoperators      = diagnostics.Where(DiagnosticTools.WarningType(WarningCode.DeprecatedANDoperator));
            var deprecatedORoperators       = diagnostics.Where(DiagnosticTools.WarningType(WarningCode.DeprecatedORoperator));
            var deprecatedOpCharacteristics = diagnostics.Where(DiagnosticTools.WarningType(WarningCode.DeprecatedOpCharacteristics));

            (string, WorkspaceEdit) ReplaceWith(string text, LSP.Range range)
            {
Пример #6
0
        /// <summary>
        /// Returns workspace edits for opening namespaces of unknown identifiers.
        /// </summary>
        private static IEnumerable <(string, WorkspaceEdit)> UnknownIdNamespaceSuggestions(
            this FileContentManager file,
            CompilationUnit compilation,
            int lineNr,
            IReadOnlyCollection <Diagnostic> diagnostics)
        {
            var idSuggestions = diagnostics
                                .Where(DiagnosticTools.ErrorType(ErrorCode.UnknownIdentifier))
                                .SelectMany(d => file.IdNamespaceSuggestions(d.Range.Start.ToQSharp(), compilation, out _));
            var typeSuggestions = diagnostics
                                  .Where(DiagnosticTools.ErrorType(ErrorCode.UnknownType))
                                  .SelectMany(d => file.TypeNamespaceSuggestions(d.Range.Start.ToQSharp(), compilation, out _));

            return(file
                   .OpenDirectiveSuggestions(lineNr, idSuggestions.Concat(typeSuggestions))
                   .Select(edit => (edit.NewText.Trim().Trim(';'), file.GetWorkspaceEdit(edit))));
        }
Пример #7
0
        /// <summary>
        /// Returns a sequence of suggestions on how errors for ambiguous types and callable in the given diagnostics can be fixed,
        /// given the file for which those diagnostics were generated and the corresponding compilation.
        /// Returns an empty enumerable if any of the given arguments is null.
        /// </summary>
        internal static IEnumerable <(string, WorkspaceEdit)> AmbiguousIdSuggestions(
            this FileContentManager file, CompilationUnit compilation, IEnumerable <Diagnostic> diagnostics)
        {
            if (file == null || diagnostics == null)
            {
                return(Enumerable.Empty <(string, WorkspaceEdit)>());
            }
            var ambiguousCallables = diagnostics.Where(DiagnosticTools.ErrorType(ErrorCode.AmbiguousCallable));
            var ambiguousTypes     = diagnostics.Where(DiagnosticTools.ErrorType(ErrorCode.AmbiguousType));

            if (!ambiguousCallables.Any() && !ambiguousTypes.Any())
            {
                return(Enumerable.Empty <(string, WorkspaceEdit)>());
            }

            (string, WorkspaceEdit)? SuggestedNameQualification(string suggestedNS, string?id, Position pos)
            {
                var edit = new TextEdit
                {
                    Range = new Lsp.Range {
                        Start = pos.ToLsp(), End = pos.ToLsp()
                    },
                    NewText = $"{suggestedNS}."
                };

                return(id is null
                    ? null as (string, WorkspaceEdit)?
                    : ($"{suggestedNS}.{id}", file.GetWorkspaceEdit(edit)));
            }

            var suggestedIdQualifications = ambiguousCallables
                                            .Select(d => d.Range.Start.ToQSharp())
                                            .SelectMany(pos => file
                                                        .IdNamespaceSuggestions(pos, compilation, out var id)
                                                        .SelectNotNull(ns => SuggestedNameQualification(ns, id, pos)));
            var suggestedTypeQualifications = ambiguousTypes
                                              .Select(d => d.Range.Start.ToQSharp())
                                              .SelectMany(pos => file
                                                          .TypeNamespaceSuggestions(pos, compilation, out var id)
                                                          .SelectNotNull(ns => SuggestedNameQualification(ns, id, pos)));

            return(suggestedIdQualifications.Concat(suggestedTypeQualifications));
        }
Пример #8
0
        /// <summary>
        /// Returns a sequence of suggestions on how errors for unknown types and callable in the given diagnostics can be fixed,
        /// given the file for which those diagnostics were generated and the corresponding compilation.
        /// The given line number is used to determine the containing namespace.
        /// Returns an empty enumerable if any of the given arguments is null.
        /// </summary>
        internal static IEnumerable <(string, WorkspaceEdit)> SuggestionsForUnknownIdentifiers
            (this FileContentManager file, CompilationUnit compilation, int lineNr, IEnumerable <Diagnostic> diagnostics)
        {
            if (file == null || diagnostics == null)
            {
                return(Enumerable.Empty <(string, WorkspaceEdit)>());
            }
            var unknownCallables = diagnostics.Where(DiagnosticTools.ErrorType(ErrorCode.UnknownIdentifier));
            var unknownTypes     = diagnostics.Where(DiagnosticTools.ErrorType(ErrorCode.UnknownType));

            if (!unknownCallables.Any() && !unknownTypes.Any())
            {
                return(Enumerable.Empty <(string, WorkspaceEdit)>());
            }

            var suggestionsForIds = unknownCallables.Select(d => d.Range.Start)
                                    .SelectMany(pos => file.NamespaceSuggestionsForIdAtPosition(pos, compilation, out var _));
            var suggestionsForTypes = unknownTypes.Select(d => d.Range.Start)
                                      .SelectMany(pos => file.NamespaceSuggestionsForTypeAtPosition(pos, compilation, out var _));

            return(file.OpenDirectiveSuggestions(lineNr, suggestionsForIds.Concat(suggestionsForTypes).ToArray())
                   .Select(edit => (edit.NewText.Trim().Trim(';'), file.GetWorkspaceEdit(edit))));
        }
Пример #9
0
        /// <summary>
        /// Returns a sequence of suggestions for update-and-reassign statements based on the generated diagnostics,
        /// and given the file for which those diagnostics were generated.
        /// Returns an empty enumerable if any of the given arguments is null.
        /// </summary>
        internal static IEnumerable <(string, WorkspaceEdit)> SuggestionsForUpdateAndReassignStatements
            (this FileContentManager file, IEnumerable <Diagnostic> diagnostics)
        {
            if (file == null || diagnostics == null)
            {
                return(Enumerable.Empty <(string, WorkspaceEdit)>());
            }
            var updateOfArrayItemExprs = diagnostics.Where(DiagnosticTools.ErrorType(ErrorCode.UpdateOfArrayItemExpr));

            (string, WorkspaceEdit) SuggestedCopyAndUpdateExpr(CodeFragment fragment)
            {
                var exprInfo = Parsing.ProcessUpdateOfArrayItemExpr.Invoke(fragment.Text);

                // Skip if the statement did not match a pattern for which we can give a code action
                if (exprInfo == null || (exprInfo.Item1.Line == 1 && exprInfo.Item1.Column == 1))
                {
                    return("", null);
                }

                // Convert set <identifier>[<index>] = <rhs> to set <identifier> w/= <index> <- <rhs>
                var rhs           = $"{exprInfo.Item3} {Keywords.qsCopyAndUpdateOp.cont} {exprInfo.Item4}";
                var outputStr     = $"{Keywords.qsValueUpdate.id} {exprInfo.Item2} {Keywords.qsCopyAndUpdateOp.op}= {rhs}";
                var fragmentRange = fragment.GetRange();
                var edit          = new TextEdit {
                    Range = fragmentRange.Copy(), NewText = outputStr
                };

                return("Replace with an update-and-reassign statement.", file.GetWorkspaceEdit(edit));
            }

            return(updateOfArrayItemExprs
                   .Select(d => file?.TryGetFragmentAt(d.Range.Start, out var _, includeEnd: true))
                   .Where(frag => frag != null)
                   .Select(frag => SuggestedCopyAndUpdateExpr(frag))
                   .Where(s => s.Item2 != null));
        }
Пример #10
0
        /// <summary>
        /// Returns a sequence of suggestions for replacing ranges over array indices with the corresponding library call,
        /// provided the corresponding library is referenced.
        /// Returns an empty enumerable if this is not the case or any of the given arguments is null.
        /// </summary>
        internal static IEnumerable <(string, WorkspaceEdit)> SuggestionsForIndexRange
            (this FileContentManager file, CompilationUnit compilation, LSP.Range range)
        {
            if (file == null || compilation == null || range?.Start == null)
            {
                return(Enumerable.Empty <(string, WorkspaceEdit)>());
            }
            var indexRangeNamespaces = compilation.GlobalSymbols.NamespacesContainingCallable(BuiltIn.IndexRange.Name);

            if (!indexRangeNamespaces.Contains(BuiltIn.IndexRange.Namespace))
            {
                return(Enumerable.Empty <(string, WorkspaceEdit)>());
            }
            var suggestedOpenDir = file.OpenDirectiveSuggestions(range.Start.Line, BuiltIn.IndexRange.Namespace);

            /// Returns true the given expression is of the form "0 .. Length(args) - 1",
            /// as well as the range of the entire expression and the argument tuple "(args)" as out parameters.
            bool IsIndexRange(QsExpression iterExpr, Position offset, out LSP.Range exprRange, out LSP.Range argRange)
            {
                if (iterExpr.Expression is QsExpressionKind <QsExpression, QsSymbol, QsType> .RangeLiteral rangeExpression && iterExpr.Range.IsValue &&                               // iterable expression is a valid range literal
                    rangeExpression.Item1.Expression is QsExpressionKind <QsExpression, QsSymbol, QsType> .IntLiteral intLiteralExpression && intLiteralExpression.Item == 0L &&      // .. starting at 0 ..
                    rangeExpression.Item2.Expression is QsExpressionKind <QsExpression, QsSymbol, QsType> .SUB SUBExpression &&                                                       // .. and ending in subracting ..
                    SUBExpression.Item2.Expression is QsExpressionKind <QsExpression, QsSymbol, QsType> .IntLiteral subIntLiteralExpression && subIntLiteralExpression.Item == 1L &&  // .. 1 from ..
                    SUBExpression.Item1.Expression is QsExpressionKind <QsExpression, QsSymbol, QsType> .CallLikeExpression callLikeExression &&                                      // .. a call ..
                    callLikeExression.Item1.Expression is QsExpressionKind <QsExpression, QsSymbol, QsType> .Identifier identifier &&                                                 // .. to and identifier ..
                    identifier.Item1.Symbol is QsSymbolKind <QsSymbol> .Symbol symName && symName.Item.Value == BuiltIn.Length.Name.Value &&                                          // .. "Length" called with ..
                    callLikeExression.Item2.Expression is QsExpressionKind <QsExpression, QsSymbol, QsType> .ValueTuple valueTuple && callLikeExression.Item2.Range.IsValue)          // .. a valid argument tuple
                {
                    exprRange = DiagnosticTools.GetAbsoluteRange(offset, iterExpr.Range.Item);
                    argRange  = DiagnosticTools.GetAbsoluteRange(offset, callLikeExression.Item2.Range.Item);
                    return(true);
                }

                (exprRange, argRange) = (null, null);
                return(false);
            }

            /// Returns the text edits for replacing an range over the indices with the corresponding library call if the given code fragment is a suitable for-loop intro.
            /// The returned edits do *not* include an edit for adding the corresponding open-directive if necessary.
            IEnumerable <TextEdit> IndexRangeEdits(CodeFragment fragment)
            {
                if (fragment.Kind is QsFragmentKind.ForLoopIntro forLoopIntro && // todo: in principle we could give these suggestions for any index range
                    IsIndexRange(forLoopIntro.Item2, fragment.GetRange().Start, out var iterExprRange, out var argTupleRange))
                {
                    yield return(new TextEdit()
                    {
                        Range = new LSP.Range()
                        {
                            Start = iterExprRange.Start, End = argTupleRange.Start
                        },
                        NewText = BuiltIn.IndexRange.Name.Value
                    });

                    yield return(new TextEdit()
                    {
                        Range = new LSP.Range()
                        {
                            Start = argTupleRange.End, End = iterExprRange.End
                        },
                        NewText = ""
                    });
                }
            }

            var fragments = file.FragmentsOverlappingWithRange(range);
            var edits     = fragments.SelectMany(IndexRangeEdits);

            return(edits.Any()
                ? new[] { ("Use IndexRange to iterate over indices.", file.GetWorkspaceEdit(suggestedOpenDir.Concat(edits).ToArray())) }
Пример #11
0
        /// <summary>
        /// Returns a sequence of suggestions on how deprecated syntax can be updated based on the generated diagnostics,
        /// and given the file for which those diagnostics were generated.
        /// Returns an empty enumerable if any of the given arguments is null.
        /// </summary>
        internal static IEnumerable <(string, WorkspaceEdit)> SuggestionsForDeprecatedSyntax
            (this FileContentManager file, IEnumerable <Diagnostic> diagnostics)
        {
            if (file == null || diagnostics == null)
            {
                return(Enumerable.Empty <(string, WorkspaceEdit)>());
            }
            var deprecatedUnitTypes         = diagnostics.Where(DiagnosticTools.WarningType(WarningCode.DeprecatedUnitType));
            var deprecatedNOToperators      = diagnostics.Where(DiagnosticTools.WarningType(WarningCode.DeprecatedNOToperator));
            var deprecatedANDoperators      = diagnostics.Where(DiagnosticTools.WarningType(WarningCode.DeprecatedANDoperator));
            var deprecatedORoperators       = diagnostics.Where(DiagnosticTools.WarningType(WarningCode.DeprecatedORoperator));
            var deprecatedOpCharacteristics = diagnostics.Where(DiagnosticTools.WarningType(WarningCode.DeprecatedOpCharacteristics));

            (string, WorkspaceEdit) ReplaceWith(string text, LSP.Range range)
            {
                bool NeedsWs(Char ch) => Char.IsLetterOrDigit(ch) || ch == '_';

                if (range?.Start != null && range.End != null)
                {
                    var beforeEdit = file.GetLine(range.Start.Line).Text.Substring(0, range.Start.Character);
                    var afterEdit  = file.GetLine(range.End.Line).Text.Substring(range.End.Character);
                    if (beforeEdit.Any() && NeedsWs(beforeEdit.Last()))
                    {
                        text = $" {text}";
                    }
                    if (afterEdit.Any() && NeedsWs(afterEdit.First()))
                    {
                        text = $"{text} ";
                    }
                }
                var edit = new TextEdit {
                    Range = range?.Copy(), NewText = text
                };

                return($"Replace with \"{text.Trim()}\".", file.GetWorkspaceEdit(edit));
            }

            // update deprecated keywords and operators

            var suggestionsForUnitType = deprecatedUnitTypes.Select(d => ReplaceWith(Keywords.qsUnit.id, d.Range));
            var suggestionsForNOT      = deprecatedNOToperators.Select(d => ReplaceWith(Keywords.qsNOTop.op, d.Range));
            var suggestionsForAND      = deprecatedANDoperators.Select(d => ReplaceWith(Keywords.qsANDop.op, d.Range));
            var suggestionsForOR       = deprecatedORoperators.Select(d => ReplaceWith(Keywords.qsORop.op, d.Range));

            // update deprecated operation characteristics syntax

            var typeToQs = new ExpressionTypeToQs(new ExpressionToQs());

            string CharacteristicsAnnotation(Characteristics c)
            {
                typeToQs.onCharacteristicsExpression(SymbolResolution.ResolveCharacteristics(c));
                return($"{Keywords.qsCharacteristics.id} {typeToQs.Output}");
            }

            var suggestionsForOpCharacteristics = deprecatedOpCharacteristics.SelectMany(d =>
            {
                // TODO: TryGetQsSymbolInfo currently only returns information about the inner most leafs rather than all types etc.
                // Once it returns indeed all types in the fragment, the following code block should be replaced by the commented out code below.
                var fragment = file.TryGetFragmentAt(d.Range.Start, out var _);
                IEnumerable <Characteristics> GetCharacteristics(QsTuple <Tuple <QsSymbol, QsType> > argTuple) =>
                SyntaxGenerator.ExtractItems(argTuple).SelectMany(item => item.Item2.ExtractCharacteristics()).Distinct();
                var characteristicsInFragment =
                    fragment?.Kind is QsFragmentKind.FunctionDeclaration function ? GetCharacteristics(function.Item2.Argument) :
                    fragment?.Kind is QsFragmentKind.OperationDeclaration operation ? GetCharacteristics(operation.Item2.Argument) :
                    fragment?.Kind is QsFragmentKind.TypeDefinition type ? GetCharacteristics(type.Item2) :
                    Enumerable.Empty <Characteristics>();

                //var symbolInfo = file.TryGetQsSymbolInfo(d.Range.Start, false, out var fragment);
                //var characteristicsInFragment = (symbolInfo?.UsedTypes ?? Enumerable.Empty<QsType>())
                //    .SelectMany(t => t.ExtractCharacteristics()).Distinct();
                var fragmentStart = fragment?.GetRange()?.Start;
                return(characteristicsInFragment
                       .Where(c => c.Range.IsValue && DiagnosticTools.GetAbsoluteRange(fragmentStart, c.Range.Item).Overlaps(d.Range))
                       .Select(c => ReplaceWith(CharacteristicsAnnotation(c), d.Range)));
            });

            return(suggestionsForOpCharacteristics.ToArray()
                   .Concat(suggestionsForUnitType)
                   .Concat(suggestionsForNOT)
                   .Concat(suggestionsForAND)
                   .Concat(suggestionsForOR));
        }