in src/Workspaces/CSharp/Portable/Extensions/ContextQuery/SyntaxTreeExtensions.cs [1907:2444]
public static bool IsExpressionContext(
this SyntaxTree syntaxTree,
int position,
SyntaxToken tokenOnLeftOfPosition,
bool attributes,
CancellationToken cancellationToken,
SemanticModel semanticModelOpt = null)
{
// cases:
// var q = |
// var q = a|
// this list is *not* exhaustive.
var token = tokenOnLeftOfPosition.GetPreviousTokenIfTouchingWord(position);
if (token.GetAncestor<ConditionalDirectiveTriviaSyntax>() != null)
{
return false;
}
if (!attributes)
{
if (token.GetAncestor<AttributeListSyntax>() != null)
{
return false;
}
}
if (syntaxTree.IsConstantExpressionContext(position, tokenOnLeftOfPosition, cancellationToken))
{
return true;
}
// no expressions after . :: ->
if (token.IsKind(SyntaxKind.DotToken) ||
token.IsKind(SyntaxKind.ColonColonToken) ||
token.IsKind(SyntaxKind.MinusGreaterThanToken))
{
return false;
}
// Normally you can have any sort of expression after an equals. However, this does not
// apply to a "using Goo = ..." situation.
if (token.IsKind(SyntaxKind.EqualsToken))
{
if (token.Parent.IsKind(SyntaxKind.NameEquals) &&
token.Parent.IsParentKind(SyntaxKind.UsingDirective))
{
return false;
}
}
// q = |
// q -= |
// q *= |
// q += |
// q /= |
// q ^= |
// q %= |
// q &= |
// q |= |
// q <<= |
// q >>= |
if (token.IsKind(SyntaxKind.EqualsToken) ||
token.IsKind(SyntaxKind.MinusEqualsToken) ||
token.IsKind(SyntaxKind.AsteriskEqualsToken) ||
token.IsKind(SyntaxKind.PlusEqualsToken) ||
token.IsKind(SyntaxKind.SlashEqualsToken) ||
token.IsKind(SyntaxKind.ExclamationEqualsToken) ||
token.IsKind(SyntaxKind.CaretEqualsToken) ||
token.IsKind(SyntaxKind.AmpersandEqualsToken) ||
token.IsKind(SyntaxKind.BarEqualsToken) ||
token.IsKind(SyntaxKind.PercentEqualsToken) ||
token.IsKind(SyntaxKind.LessThanLessThanEqualsToken) ||
token.IsKind(SyntaxKind.GreaterThanGreaterThanEqualsToken))
{
return true;
}
// ( |
if (token.IsKind(SyntaxKind.OpenParenToken) &&
token.Parent.IsKind(SyntaxKind.ParenthesizedExpression))
{
return true;
}
// - |
// + |
// ~ |
// ! |
if (token.Parent is PrefixUnaryExpressionSyntax)
{
var prefix = token.Parent as PrefixUnaryExpressionSyntax;
return prefix.OperatorToken == token;
}
// not sure about these:
// ++ |
// -- |
#if false
token.Kind == SyntaxKind.PlusPlusToken ||
token.Kind == SyntaxKind.DashDashToken)
#endif
// await |
if (token.Parent is AwaitExpressionSyntax)
{
var awaitExpression = token.Parent as AwaitExpressionSyntax;
return awaitExpression.AwaitKeyword == token;
}
// Check for binary operators.
// Note:
// - We handle < specially as it can be ambiguous with generics.
// - We handle * specially because it can be ambiguous with pointer types.
// a *
// a /
// a %
// a +
// a -
// a <<
// a >>
// a <
// a >
// a &&
// a ||
// a &
// a |
// a ^
if (token.Parent is BinaryExpressionSyntax)
{
// If the client provided a binding, then check if this is actually generic. If so,
// then this is not an expression context. i.e. if we have "Goo < |" then it could
// be an expression context, or it could be a type context if Goo binds to a type or
// method.
if (semanticModelOpt != null && syntaxTree.IsGenericTypeArgumentContext(position, tokenOnLeftOfPosition, cancellationToken, semanticModelOpt))
{
return false;
}
var binary = token.Parent as BinaryExpressionSyntax;
if (binary.OperatorToken == token)
{
// If this is a multiplication expression and a semantic model was passed in,
// check to see if the expression to the left is a type name. If it is, treat
// this as a pointer type.
if (token.IsKind(SyntaxKind.AsteriskToken) && semanticModelOpt != null)
{
if (binary.Left is TypeSyntax type && type.IsPotentialTypeName(semanticModelOpt, cancellationToken))
{
return false;
}
}
return true;
}
}
// Special case:
// Goo * bar
// Goo ? bar
// This parses as a local decl called bar of type Goo* or Goo?
if (tokenOnLeftOfPosition.IntersectsWith(position) &&
tokenOnLeftOfPosition.IsKind(SyntaxKind.IdentifierToken))
{
var previousToken = tokenOnLeftOfPosition.GetPreviousToken(includeSkipped: true);
if (previousToken.IsKind(SyntaxKind.AsteriskToken) ||
previousToken.IsKind(SyntaxKind.QuestionToken))
{
if (previousToken.Parent.IsKind(SyntaxKind.PointerType) ||
previousToken.Parent.IsKind(SyntaxKind.NullableType))
{
var type = previousToken.Parent as TypeSyntax;
if (type.IsParentKind(SyntaxKind.VariableDeclaration) &&
type.Parent.IsParentKind(SyntaxKind.LocalDeclarationStatement))
{
var declStatement = type.Parent.Parent as LocalDeclarationStatementSyntax;
// note, this doesn't apply for cases where we know it
// absolutely is not multiplication or a conditional expression.
var underlyingType = type is PointerTypeSyntax
? ((PointerTypeSyntax)type).ElementType
: ((NullableTypeSyntax)type).ElementType;
if (!underlyingType.IsPotentialTypeName(semanticModelOpt, cancellationToken))
{
return true;
}
}
}
}
}
// new int[|
// new int[expr, |
if (token.IsKind(SyntaxKind.OpenBracketToken) ||
token.IsKind(SyntaxKind.CommaToken))
{
if (token.Parent.IsKind(SyntaxKind.ArrayRankSpecifier))
{
return true;
}
}
// goo ? |
if (token.IsKind(SyntaxKind.QuestionToken) &&
token.Parent.IsKind(SyntaxKind.ConditionalExpression))
{
// If the condition is simply a TypeSyntax that binds to a type, treat this as a nullable type.
var conditionalExpression = (ConditionalExpressionSyntax)token.Parent;
var type = conditionalExpression.Condition as TypeSyntax;
return type == null
|| !type.IsPotentialTypeName(semanticModelOpt, cancellationToken);
}
// goo ? bar : |
if (token.IsKind(SyntaxKind.ColonToken) &&
token.Parent.IsKind(SyntaxKind.ConditionalExpression))
{
return true;
}
// typeof(|
// default(|
// sizeof(|
if (token.IsKind(SyntaxKind.OpenParenToken))
{
if (token.Parent.IsKind(SyntaxKind.TypeOfExpression, SyntaxKind.DefaultExpression, SyntaxKind.SizeOfExpression))
{
return false;
}
}
// var(|
// var(id, |
// Those are more likely to be deconstruction-declarations being typed than invocations a method "var"
if (token.IsKind(SyntaxKind.OpenParenToken, SyntaxKind.CommaToken) &&
token.IsInvocationOfVarExpression())
{
return false;
}
// Goo(|
// Goo(expr, |
// this[|
// var t = (1, |
// var t = (| , 2)
if (token.IsKind(SyntaxKind.OpenParenToken) ||
token.IsKind(SyntaxKind.OpenBracketToken) ||
token.IsKind(SyntaxKind.CommaToken))
{
if (token.Parent.IsKind(SyntaxKind.ArgumentList, SyntaxKind.BracketedArgumentList, SyntaxKind.TupleExpression))
{
return true;
}
}
// [Goo(|
// [Goo(expr, |
if (attributes)
{
if (token.IsKind(SyntaxKind.OpenParenToken) ||
token.IsKind(SyntaxKind.CommaToken))
{
if (token.Parent.IsKind(SyntaxKind.AttributeArgumentList))
{
return true;
}
}
}
// Goo(ref |
// Goo(in |
// Goo(out |
if (token.IsKind(SyntaxKind.RefKeyword) ||
token.IsKind(SyntaxKind.InKeyword) ||
token.IsKind(SyntaxKind.OutKeyword))
{
if (token.Parent.IsKind(SyntaxKind.Argument))
{
return true;
}
}
// Goo(bar: |
if (token.IsKind(SyntaxKind.ColonToken) &&
token.Parent.IsKind(SyntaxKind.NameColon) &&
token.Parent.IsParentKind(SyntaxKind.Argument))
{
return true;
}
// a => |
if (token.IsKind(SyntaxKind.EqualsGreaterThanToken))
{
return true;
}
// new List<int> { |
// new List<int> { expr, |
if (token.IsKind(SyntaxKind.OpenBraceToken) ||
token.IsKind(SyntaxKind.CommaToken))
{
if (token.Parent is InitializerExpressionSyntax)
{
// The compiler treats the ambiguous case as an object initializer, so we'll say
// expressions are legal here
if (token.Parent.IsKind(SyntaxKind.ObjectInitializerExpression) && token.IsKind(SyntaxKind.OpenBraceToken))
{
// In this position { a$$ =, the user is trying to type an object initializer.
if (!token.IntersectsWith(position) && token.GetNextToken().GetNextToken().IsKind(SyntaxKind.EqualsToken))
{
return false;
}
return true;
}
// Perform a semantic check to determine whether or not the type being created
// can support a collection initializer. If not, this must be an object initializer
// and can't be an expression context.
if (semanticModelOpt != null &&
token.Parent.IsParentKind(SyntaxKind.ObjectCreationExpression))
{
var objectCreation = (ObjectCreationExpressionSyntax)token.Parent.Parent;
var type = semanticModelOpt.GetSymbolInfo(objectCreation.Type, cancellationToken).Symbol as ITypeSymbol;
var containingSymbol = semanticModelOpt.GetEnclosingNamedTypeOrAssembly(position, cancellationToken);
if (type != null && !type.CanSupportCollectionInitializer(containingSymbol))
{
return false;
}
}
return true;
}
}
// for (; |
// for (; ; |
if (token.IsKind(SyntaxKind.SemicolonToken) &&
token.Parent.IsKind(SyntaxKind.ForStatement))
{
var forStatement = (ForStatementSyntax)token.Parent;
if (token == forStatement.FirstSemicolonToken ||
token == forStatement.SecondSemicolonToken)
{
return true;
}
}
// for ( |
if (token.IsKind(SyntaxKind.OpenParenToken) &&
token.Parent.IsKind(SyntaxKind.ForStatement))
{
var forStatement = (ForStatementSyntax)token.Parent;
if (token == forStatement.OpenParenToken)
{
return true;
}
}
// for (; ; Goo(), |
// for ( Goo(), |
if (token.IsKind(SyntaxKind.CommaToken) &&
token.Parent.IsKind(SyntaxKind.ForStatement))
{
return true;
}
// foreach (var v in |
// from a in |
// join b in |
if (token.IsKind(SyntaxKind.InKeyword))
{
if (token.Parent.IsKind(SyntaxKind.ForEachStatement,
SyntaxKind.ForEachVariableStatement,
SyntaxKind.FromClause,
SyntaxKind.JoinClause))
{
return true;
}
}
// join x in y on |
// join x in y on a equals |
if (token.IsKind(SyntaxKind.OnKeyword) ||
token.IsKind(SyntaxKind.EqualsKeyword))
{
if (token.Parent.IsKind(SyntaxKind.JoinClause))
{
return true;
}
}
// where |
if (token.IsKind(SyntaxKind.WhereKeyword) &&
token.Parent.IsKind(SyntaxKind.WhereClause))
{
return true;
}
// orderby |
// orderby a, |
if (token.IsKind(SyntaxKind.OrderByKeyword) ||
token.IsKind(SyntaxKind.CommaToken))
{
if (token.Parent.IsKind(SyntaxKind.OrderByClause))
{
return true;
}
}
// select |
if (token.IsKind(SyntaxKind.SelectKeyword) &&
token.Parent.IsKind(SyntaxKind.SelectClause))
{
return true;
}
// group |
// group expr by |
if (token.IsKind(SyntaxKind.GroupKeyword) ||
token.IsKind(SyntaxKind.ByKeyword))
{
if (token.Parent.IsKind(SyntaxKind.GroupClause))
{
return true;
}
}
// return |
// yield return |
// but not: [return |
if (token.IsKind(SyntaxKind.ReturnKeyword))
{
if (token.GetPreviousToken(includeSkipped: true).Kind() != SyntaxKind.OpenBracketToken)
{
return true;
}
}
// throw |
if (token.IsKind(SyntaxKind.ThrowKeyword))
{
return true;
}
// while ( |
if (token.IsKind(SyntaxKind.OpenParenToken) &&
token.GetPreviousToken(includeSkipped: true).IsKind(SyntaxKind.WhileKeyword))
{
return true;
}
// todo: handle 'for' cases.
// using ( |
if (token.IsKind(SyntaxKind.OpenParenToken) &&
token.GetPreviousToken(includeSkipped: true).IsKind(SyntaxKind.UsingKeyword))
{
return true;
}
// lock ( |
if (token.IsKind(SyntaxKind.OpenParenToken) &&
token.GetPreviousToken(includeSkipped: true).IsKind(SyntaxKind.LockKeyword))
{
return true;
}
// lock ( |
if (token.IsKind(SyntaxKind.OpenParenToken) &&
token.GetPreviousToken(includeSkipped: true).IsKind(SyntaxKind.IfKeyword))
{
return true;
}
// switch ( |
if (token.IsKind(SyntaxKind.OpenParenToken) &&
token.GetPreviousToken(includeSkipped: true).IsKind(SyntaxKind.SwitchKeyword))
{
return true;
}
// checked ( |
if (token.IsKind(SyntaxKind.OpenParenToken) &&
token.GetPreviousToken(includeSkipped: true).IsKind(SyntaxKind.CheckedKeyword))
{
return true;
}
// unchecked ( |
if (token.IsKind(SyntaxKind.OpenParenToken) &&
token.GetPreviousToken(includeSkipped: true).IsKind(SyntaxKind.UncheckedKeyword))
{
return true;
}
// when ( |
if (token.IsKind(SyntaxKind.OpenParenToken) &&
token.GetPreviousToken(includeSkipped: true).IsKind(SyntaxKind.WhenKeyword))
{
return true;
}
// (SomeType) |
if (token.IsAfterPossibleCast())
{
return true;
}
// In anonymous type initializer.
//
// new { | We allow new inside of anonymous object member declarators, so that the user
// can dot into a member afterward. For example:
//
// var a = new { new C().Goo };
if (token.IsKind(SyntaxKind.OpenBraceToken) || token.IsKind(SyntaxKind.CommaToken))
{
if (token.Parent.IsKind(SyntaxKind.AnonymousObjectCreationExpression))
{
return true;
}
}
// $"{ |
// $@"{ |
// $"{x} { |
// $@"{x} { |
if (token.IsKind(SyntaxKind.OpenBraceToken))
{
return token.Parent.IsKind(SyntaxKind.Interpolation)
&& ((InterpolationSyntax)token.Parent).OpenBraceToken == token;
}
return false;
}