Skip to content

Commit

Permalink
Parse incomplete code differently to better reflect user intent.
Browse files Browse the repository at this point in the history
  • Loading branch information
CyrusNajmabadi committed Dec 14, 2016
1 parent 0d05f0a commit 426677b
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 61 deletions.
47 changes: 47 additions & 0 deletions src/Compilers/CSharp/Portable/Parser/LanguageParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<W... (local function)
//
var token4Kind = PeekToken(4).Kind;
return token4Kind == SyntaxKind.SemicolonToken ||
token4Kind == SyntaxKind.EqualsToken ||
token4Kind == SyntaxKind.CommaToken ||
token4Kind == SyntaxKind.OpenParenToken ||
token4Kind == SyntaxKind.LessThanToken;
}
}
}

var resetPoint = this.GetResetPoint();
try
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,81 +314,55 @@ private static ImmutableArray<ISymbol> 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<NamespaceDeclarationSyntax>();
return symbols.WhereAsArray(s => IsNonIntersectingNamespace(s, declarationSyntax));
}
if (context.IsNamespaceDeclarationNameContext)
{
var declarationSyntax = name.GetAncestorOrThis<NamespaceDeclarationSyntax>();
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<UsingDirectiveSyntax>();
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<UsingDirectiveSyntax>();
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<ISymbol>.Empty;
Expand Down

0 comments on commit 426677b

Please sign in to comment.