// 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> /// 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); } }
// 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); }
/// <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; } } }
/// <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); }
/// <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> /// 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() ); } }
/// <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); }
/// <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)}" ); } }
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); }
// 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))); } } }
/// <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); }
/// <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)); }
/// <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); }