From 426677bde848cae463ca6bd2663dc81f07d9e7b3 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Tue, 13 Dec 2016 16:54:42 -0800 Subject: [PATCH] Parse incomplete code differently to better reflect user intent. --- .../CSharp/Portable/Parser/LanguageParser.cs | 47 +++++++++ .../CSharpRecommendationService.cs | 96 +++++++------------ 2 files changed, 82 insertions(+), 61 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index 0d7876c9b4222..629291df59887 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -7010,6 +7010,53 @@ private bool IsPossibleLocalDeclarationStatement(bool allowAnyExpression) return typedIdentifier.Value; } + // It's common to have code like the following: + // + // Task. + // await Task.Delay() + // + // In this case we don't want to parse this as as a local declaration like: + // + // Task.await Task + // + // This does not represent user intent, and it causes all sorts of problems to higher + // layers. This is because both the parse tree is strage, and the symbol tables have + // entries that throw things off (like a bogus 'Task' local). + // + // Note that we explicitly do this check when we see that the code spreads over multiple + // lines. We don't want this if the user has actually written "X.Y z" + if (tk == SyntaxKind.IdentifierToken) + { + var token1 = PeekToken(1); + if (token1.Kind == SyntaxKind.DotToken && + token1.TrailingTrivia.Any((int)SyntaxKind.EndOfLineTrivia)) + { + if (PeekToken(2).Kind == SyntaxKind.IdentifierToken && + PeekToken(3).Kind == SyntaxKind.IdentifierToken) + { + // We have something like: + // + // X. + // Y z + // + // This is only a local declaration if we have: + // + // X.Y z; + // X.Y z = ... + // X.Y z, ... + // X.Y z( ... (local function) + // X.Y z GetSymbolsOffOfName( var speculativeResult = GetSymbolsOffOfBoundExpression( context, name, name, speculativeBinding, container, cancellationToken); - // If this speculative binding produced good results, then return them. - if (speculativeResult.Length > 0) - { - return speculativeResult; - } - - // we didn't get any useful speculative results. This can happen in cases like: - // - // Task. - // await Task.Delay() - // - // The problem here is the above is parsed as "Task.await Task", putting a Local - // scope called "Task". This then causes lookup of 'Task' to bind to that local. - // As the local is of type "Task.await", we get no results. - // - // So fallback to just seeing if we can at least bind 'Task' as a type to get - // members off of it here. - var nameBinding = context.SemanticModel.GetSymbolInfo(name, cancellationToken); - container = nameBinding.GetAnySymbol() as INamedTypeSymbol; - - if (container != null) - { - return GetSymbolsOffOfBoundExpression( - context, name, name, nameBinding, container, cancellationToken); - } + return speculativeResult; } - else - { - // We're in a name-only context, since if we were an expression we'd be a + + // We're in a name-only context, since if we were an expression we'd be a // MemberAccessExpressionSyntax. Thus, let's do other namespaces and types. var nameBinding = context.SemanticModel.GetSymbolInfo(name, cancellationToken); var symbol = nameBinding.Symbol as INamespaceOrTypeSymbol; - if (symbol != null) + if (symbol != null) + { + if (context.IsNameOfContext) { - if (context.IsNameOfContext) - { - return context.SemanticModel.LookupSymbols(position: name.SpanStart, container: symbol); - } + return context.SemanticModel.LookupSymbols(position: name.SpanStart, container: symbol); + } - var symbols = context.SemanticModel.LookupNamespacesAndTypes( - position: name.SpanStart, - container: symbol); + var symbols = context.SemanticModel.LookupNamespacesAndTypes( + position: name.SpanStart, + container: symbol); - if (context.IsNamespaceDeclarationNameContext) - { - var declarationSyntax = name.GetAncestorOrThis(); - return symbols.WhereAsArray(s => IsNonIntersectingNamespace(s, declarationSyntax)); - } + if (context.IsNamespaceDeclarationNameContext) + { + var declarationSyntax = name.GetAncestorOrThis(); + return symbols.WhereAsArray(s => IsNonIntersectingNamespace(s, declarationSyntax)); + } - // Filter the types when in a using directive, but not an alias. - // - // Cases: - // using | -- Show namespaces - // using A.| -- Show namespaces - // using static | -- Show namespace and types - // using A = B.| -- Show namespace and types - var usingDirective = name.GetAncestorOrThis(); - if (usingDirective != null && usingDirective.Alias == null) + // Filter the types when in a using directive, but not an alias. + // + // Cases: + // using | -- Show namespaces + // using A.| -- Show namespaces + // using static | -- Show namespace and types + // using A = B.| -- Show namespace and types + var usingDirective = name.GetAncestorOrThis(); + if (usingDirective != null && usingDirective.Alias == null) + { + if (usingDirective.StaticKeyword.IsKind(SyntaxKind.StaticKeyword)) { - if (usingDirective.StaticKeyword.IsKind(SyntaxKind.StaticKeyword)) - { - return symbols.WhereAsArray(s => !s.IsDelegateType() && !s.IsInterfaceType()); - } - else - { - symbols = symbols.WhereAsArray(s => s.IsNamespace()); - } + return symbols.WhereAsArray(s => !s.IsDelegateType() && !s.IsInterfaceType()); } - - if (symbols.Any()) + else { - return symbols; + symbols = symbols.WhereAsArray(s => s.IsNamespace()); } } + + if (symbols.Any()) + { + return symbols; + } } return ImmutableArray.Empty;