/// <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); }
/// <summary> /// Returns the namespace path for the qualified symbol at the given position, or null if there is no qualified /// symbol. /// </summary> /// <exception cref="ArgumentNullException">Thrown when any argument is null.</exception> /// <exception cref="ArgumentException">Thrown when the position is invalid.</exception> private static string GetSymbolNamespacePath(FileContentManager file, Position position) { if (file == null) { throw new ArgumentNullException(nameof(file)); } if (!Utils.IsValidPosition(position)) { throw new ArgumentException(nameof(position)); } var fragment = file.TryGetFragmentAt(position, out _, includeEnd: true); if (fragment == null) { return(null); } var startAt = GetTextIndexFromPosition(fragment, position); var match = Utils.QualifiedSymbolRTL.Match(fragment.Text, startAt); if (match.Success && match.Index + match.Length == startAt && match.Value.LastIndexOf('.') != -1) { return(match.Value.Substring(0, match.Value.LastIndexOf('.'))); } else { return(null); } }
/// <summary> /// Returns all code fragments in the specified file that overlap with the given range. /// Returns an empty sequence if any of the given arguments is null. /// </summary> private static IEnumerable <CodeFragment> FragmentsOverlappingWithRange(this FileContentManager file, LSP.Range range) { if (file == null || range?.Start == null || range.End == null) { return(Enumerable.Empty <CodeFragment>()); } var(start, end) = (range.Start.Line, range.End.Line); var fragAtStart = file.TryGetFragmentAt(range.Start, out var _, includeEnd: true); var inRange = file.GetTokenizedLine(start).Select(t => t.WithUpdatedLineNumber(start)).Where(ContextBuilder.TokensAfter(range.Start)); // does not include fragAtStart inRange = start == end ? inRange.Where(ContextBuilder.TokensStartingBefore(range.End)) : inRange.Concat(file.GetTokenizedLines(start + 1, end - start - 1).SelectMany((x, i) => x.Select(t => t.WithUpdatedLineNumber(start + 1 + i)))) .Concat(file.GetTokenizedLine(end).Select(t => t.WithUpdatedLineNumber(end)).Where(ContextBuilder.TokensStartingBefore(range.End))); var fragments = ImmutableArray.CreateBuilder <CodeFragment>(); if (fragAtStart != null) { fragments.Add(fragAtStart); } fragments.AddRange(inRange); return(fragments.ToImmutableArray()); }
/// <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)); }
/// <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)); }
/// <summary> /// Returns the signature help information for a call expression if there is such an expression at the specified position. /// 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 call expression exists at the specified position at this time, /// or if no signature help information can be provided for the call expression at the specified position. /// </summary> public static SignatureHelp?SignatureHelp( this FileContentManager file, CompilationUnit compilation, Position?position, MarkupKind format = MarkupKind.PlainText) { // getting the relevant token (if any) var fragment = file?.TryGetFragmentAt(position, out var _, includeEnd: true); if (file is null || position is null || fragment?.Kind == null || compilation == null) { return(null); } var fragmentStart = fragment.Range.Start; // getting the overlapping call expressions (if any), and determine the header of the called callable bool OverlapsWithPosition(Range symRange) => (fragmentStart + symRange).ContainsEnd(position); var overlappingEx = fragment.Kind.CallExpressions().Where(ex => ex.Range.IsValue && OverlapsWithPosition(ex.Range.Item)).ToList(); if (!overlappingEx.Any()) { return(null); } overlappingEx.Sort((ex1, ex2) => // for nested call expressions, the last expressions (by range) is always the closest one { var(x, y) = (ex1.Range.Item, ex2.Range.Item); int result = x.Start.CompareTo(y.Start); return(result == 0 ? x.End.CompareTo(y.End) : result); }); var nsName = file.TryGetNamespaceAt(position); var(method, args) = overlappingEx.Last().Expression is QsExpressionKind <QsExpression, QsSymbol, QsType> .CallLikeExpression c ? (c.Item1, c.Item2) : (null, null); if (nsName == null || method == null || args == null) { return(null); } // getting the called identifier as well as what functors have been applied to it List <QsFunctor> FunctorApplications(ref QsExpression ex) { var(next, inner) = ex.Expression is QsExpressionKind <QsExpression, QsSymbol, QsType> .AdjointApplication adj ? (QsFunctor.Adjoint, adj.Item) : ex.Expression is QsExpressionKind <QsExpression, QsSymbol, QsType> .ControlledApplication ctl ? (QsFunctor.Controlled, ctl.Item) : (null, null); var fs = inner == null ? new List <QsFunctor>() : FunctorApplications(ref inner); if (next != null) { fs.Add(next); } ex = inner ?? ex; return(fs); } var functors = FunctorApplications(ref method); var id = method.Expression as QsExpressionKind <QsExpression, QsSymbol, QsType> .Identifier; if (id == null) { return(null); } // extracting and adapting the relevant information for the called callable ResolutionResult <CallableDeclarationHeader> .Found?methodDecl = null; if (id.Item1.Symbol is QsSymbolKind <QsSymbol> .Symbol sym) { methodDecl = compilation.GlobalSymbols.TryResolveAndGetCallable( sym.Item, nsName, file.FileName) as ResolutionResult <CallableDeclarationHeader> .Found; } else if (id.Item1.Symbol is QsSymbolKind <QsSymbol> .QualifiedSymbol qualSym) { methodDecl = compilation.GlobalSymbols.TryGetCallable( new QsQualifiedName(qualSym.Item1, qualSym.Item2), nsName, file.FileName) as ResolutionResult <CallableDeclarationHeader> .Found; } if (methodDecl == null) { return(null); } var(documentation, argTuple) = (methodDecl.Item.Documentation, methodDecl.Item.ArgumentTuple); var nrCtlApplications = functors.Where(f => f.Equals(QsFunctor.Controlled)).Count(); while (nrCtlApplications-- > 0) { var ctlQsName = QsLocalSymbol.NewValidName(nrCtlApplications == 0 ? "cs" : $"cs{nrCtlApplications}"); argTuple = SyntaxGenerator.WithControlQubits(argTuple, QsNullable <Position> .Null, ctlQsName, QsNullable <Range> .Null); } // now that we now what callable is called we need to check which argument should come next bool BeforePosition(Range symRange) => fragmentStart + symRange.End < position; IEnumerable <(Range?, string?)> ExtractParameterRanges( QsExpression?ex, QsTuple <LocalVariableDeclaration <QsLocalSymbol> > decl) { var @null = ((Range?)null, (string?)null); IEnumerable <(Range?, string?)> SingleItem(string paramName) { var arg = ex?.Range == null ? ((Range?)null, paramName) : ex.Range.IsValue ? (ex.Range.Item, paramName) : @null; // no signature help if there are invalid expressions return(new[] { arg }); } if (decl is QsTuple <LocalVariableDeclaration <QsLocalSymbol> > .QsTupleItem dItem) { return(SingleItem(dItem.Item.VariableName is QsLocalSymbol.ValidName n ? n.Item : "__argName__")); } var declItems = decl as QsTuple <LocalVariableDeclaration <QsLocalSymbol> > .QsTuple; var exItems = ex?.Expression as QsExpressionKind <QsExpression, QsSymbol, QsType> .ValueTuple; if (declItems == null) { return(new[] { @null }); } if (exItems == null && declItems.Item.Length > 1) { return(SingleItem(decl.PrintArgumentTuple())); } var argItems = exItems != null ? exItems.Item.ToImmutableArray <QsExpression?>() : ex == null ? ImmutableArray <QsExpression?> .Empty : ImmutableArray.Create <QsExpression?>(ex); return(argItems.AddRange(Enumerable.Repeat <QsExpression?>(null, declItems.Item.Length - argItems.Length)) .Zip(declItems.Item, (e, d) => (e, d)) .SelectMany(arg => ExtractParameterRanges(arg.Item1, arg.Item2))); } var callArgs = ExtractParameterRanges(args, argTuple).ToArray(); if (id == null || callArgs == null || callArgs.Any(item => item.Item2 == null)) { return(null); // no signature help if there are invalid expressions } // finally we can build the signature help information MarkupContent AsMarkupContent(string str) => new MarkupContent { Kind = format, Value = str }; ParameterInformation AsParameterInfo(string?paramName) => new ParameterInformation { Label = paramName, Documentation = AsMarkupContent(documentation.ParameterDescription(paramName)) }; var signatureLabel = $"{methodDecl.Item.QualifiedName.Name} {argTuple.PrintArgumentTuple()}"; foreach (var f in functors) { if (f.IsAdjoint) { signatureLabel = $"{Keywords.qsAdjointFunctor.id} {signatureLabel}"; } if (f.IsControlled) { signatureLabel = $"{Keywords.qsControlledFunctor.id} {signatureLabel}"; } } var doc = documentation.PrintSummary(format == MarkupKind.Markdown).Trim(); var info = new SignatureInformation { Documentation = AsMarkupContent(doc), Label = signatureLabel, // Note: the label needs to be expressed in a way that the active parameter is detectable Parameters = callArgs.Select(d => d.Item2).Select(AsParameterInfo).ToArray() }; var precedingArgs = callArgs .TakeWhile(item => item.Item1 == null || BeforePosition(item.Item1)) // skip args that have already been typed or - in the case of inner items - are missing .Reverse().SkipWhile(item => item.Item1 == null); // don't count missing, i.e. not yet typed items, of the relevant inner argument tuple return(new SignatureHelp { Signatures = new[] { info }, // since we don't support overloading there is just one signature here ActiveSignature = 0, ActiveParameter = precedingArgs.Count() }); }