public override SyntaxNode VisitArgument(ArgumentSyntax node) { _cancellationToken.ThrowIfCancellationRequested(); var newArgument = (ArgumentSyntax)base.VisitArgument(node); var argumentType = _semanticModel.GetTypeInfo(node.Expression).ConvertedType; if (argumentType != null && !IsPassedToDelegateCreationExpression(node, argumentType)) { var specAnalyzer = new SpeculationAnalyzer(node.Expression, newArgument.Expression, _semanticModel, _cancellationToken); var speculativeSemanticModel = specAnalyzer.SpeculativeSemanticModel; var speculatedExpession = specAnalyzer.ReplacedExpression; bool wasCastAdded; var newArgumentExpression = speculatedExpession.CastIfPossible(argumentType, speculatedExpession.SpanStart, speculativeSemanticModel, out wasCastAdded); if (wasCastAdded) { return(newArgument.WithExpression(newArgumentExpression)); } } return(newArgument); }
private static bool CanRemoveTypeFromParameter( SyntaxNode node, SemanticModel semanticModel, CancellationToken cancellationToken) { // We reduce any the parameters that are contained inside ParameterList if (node != null && node.IsParentKind(SyntaxKind.ParameterList) && node.Parent.IsParentKind(SyntaxKind.ParenthesizedLambdaExpression)) { var parameterSyntax = (ParameterSyntax)node; if (parameterSyntax.Type != null) { var annotation = new SyntaxAnnotation(); var newParameterSyntax = parameterSyntax.WithType(null).WithAdditionalAnnotations(annotation); var oldLambda = node.FirstAncestorOrSelf<ParenthesizedLambdaExpressionSyntax>(); var newLambda = oldLambda.ReplaceNode(parameterSyntax, newParameterSyntax); var speculationAnalyzer = new SpeculationAnalyzer(oldLambda, newLambda, semanticModel, cancellationToken); newParameterSyntax = (ParameterSyntax)speculationAnalyzer.ReplacedExpression.GetAnnotatedNodesAndTokens(annotation).First(); var oldSymbol = semanticModel.GetDeclaredSymbol(parameterSyntax, cancellationToken); var newSymbol = speculationAnalyzer.SpeculativeSemanticModel.GetDeclaredSymbol(newParameterSyntax, cancellationToken); if (oldSymbol != null && newSymbol != null && oldSymbol.Type == newSymbol.Type) { return !speculationAnalyzer.ReplacementChangesSemantics(); } } } return false; }
private static bool CanRemoveTypeFromParameter( SyntaxNode node, SemanticModel semanticModel, CancellationToken cancellationToken) { // We reduce any the parameters that are contained inside ParameterList if (node != null && node.IsParentKind(SyntaxKind.ParameterList) && node.Parent.IsParentKind(SyntaxKind.ParenthesizedLambdaExpression)) { var parameterSyntax = (ParameterSyntax)node; if (parameterSyntax.Type != null) { var annotation = new SyntaxAnnotation(); var newParameterSyntax = parameterSyntax.WithType(null).WithAdditionalAnnotations(annotation); var oldLambda = node.FirstAncestorOrSelf <ParenthesizedLambdaExpressionSyntax>(); var newLambda = oldLambda.ReplaceNode(parameterSyntax, newParameterSyntax); var speculationAnalyzer = new SpeculationAnalyzer(oldLambda, newLambda, semanticModel, cancellationToken); newParameterSyntax = (ParameterSyntax)speculationAnalyzer.ReplacedExpression.GetAnnotatedNodesAndTokens(annotation).First(); var oldSymbol = semanticModel.GetDeclaredSymbol(parameterSyntax, cancellationToken); var newSymbol = speculationAnalyzer.SpeculativeSemanticModel.GetDeclaredSymbol(newParameterSyntax, cancellationToken); if (oldSymbol != null && newSymbol != null && oldSymbol.Type == newSymbol.Type) { return(!speculationAnalyzer.ReplacementChangesSemantics()); } } } return(false); }
internal override IMethodSymbol GetDelegatingConstructor( SemanticDocument document, ObjectCreationExpressionSyntax objectCreation, INamedTypeSymbol namedType, ISet <IMethodSymbol> candidates, CancellationToken cancellationToken) { var model = document.SemanticModel; var oldNode = objectCreation .AncestorsAndSelf(ascendOutOfTrivia: false) .Where(node => SpeculationAnalyzer.CanSpeculateOnNode(node)) .LastOrDefault(); var typeNameToReplace = objectCreation.Type; var newTypeName = namedType.GenerateTypeSyntax(); var newObjectCreation = objectCreation.WithType(newTypeName).WithAdditionalAnnotations(s_annotation); var newNode = oldNode.ReplaceNode(objectCreation, newObjectCreation); var speculativeModel = SpeculationAnalyzer.CreateSpeculativeSemanticModelForNode(oldNode, newNode, model); if (speculativeModel != null) { newObjectCreation = (ObjectCreationExpressionSyntax)newNode.GetAnnotatedNodes(s_annotation).Single(); var symbolInfo = speculativeModel.GetSymbolInfo(newObjectCreation, cancellationToken); var parameterTypes = newObjectCreation.ArgumentList.Arguments.Select( a => a.DetermineParameterType(speculativeModel, cancellationToken)).ToList(); return(GenerateConstructorHelpers.GetDelegatingConstructor( document, symbolInfo, candidates, namedType, parameterTypes)); } return(null); }
protected override SemanticModel GetSpeculativeSemanticModel(ref SyntaxNode nodeToSpeculate, SemanticModel originalSemanticModel, SyntaxNode originalNode) { var syntaxNodeToSpeculate = nodeToSpeculate; Contract.ThrowIfNull(syntaxNodeToSpeculate); Contract.ThrowIfFalse(SpeculationAnalyzer.CanSpeculateOnNode(nodeToSpeculate)); return(SpeculationAnalyzer.CreateSpeculativeSemanticModelForNode(originalNode, syntaxNodeToSpeculate, (SemanticModel)originalSemanticModel)); }
private static bool CanReplaceWithDefaultLiteralSlow(DefaultExpressionSyntax defaultExpression, SemanticModel semanticModel, CancellationToken cancellationToken) { var speculationAnalyzer = new SpeculationAnalyzer( defaultExpression, s_defaultLiteralExpression, semanticModel, cancellationToken, skipVerificationForReplacedNode: false, failOnOverloadResolutionFailuresInOriginalCode: true); return(!speculationAnalyzer.ReplacementChangesSemantics()); }
public override SyntaxNode Visit(SyntaxNode node) { if (node == null) { return(node); } if (_isNodeOrTokenOutsideSimplifySpans(node)) { if (_simplifyAllDescendants) { // One of the ancestor node is within a simplification span, but this node is outside all simplification spans. // Add DontSimplifyAnnotation to node to ensure it doesn't get simplified. return(node.WithAdditionalAnnotations( SimplificationHelpers.DontSimplifyAnnotation )); } else { return(node); } } var savedSimplifyAllDescendants = _simplifyAllDescendants; _simplifyAllDescendants = _simplifyAllDescendants || node.HasAnnotation(Simplifier.Annotation); if (!_insideSpeculatedNode && SpeculationAnalyzer.CanSpeculateOnNode(node)) { if ( _simplifyAllDescendants || node.DescendantNodesAndTokens( s_containsAnnotations, descendIntoTrivia: true ) .Any(s_hasSimplifierAnnotation) ) { _insideSpeculatedNode = true; var rewrittenNode = base.Visit(node); _nodesAndTokensToReduce.Add( new NodeOrTokenToReduce(rewrittenNode, _simplifyAllDescendants, node) ); _insideSpeculatedNode = false; } } else if (node.ContainsAnnotations || savedSimplifyAllDescendants) { node = base.Visit(node); } _simplifyAllDescendants = savedSimplifyAllDescendants; return(node); }
public static bool CanReplaceWithReducedName(this NameSyntax name, TypeSyntax reducedName, SemanticModel semanticModel, CancellationToken cancellationToken) { var speculationAnalyzer = new SpeculationAnalyzer(name, reducedName, semanticModel, cancellationToken); if (speculationAnalyzer.ReplacementChangesSemantics()) { return(false); } return(CanReplaceWithReducedNameInContext(name, reducedName, semanticModel, cancellationToken)); }
/// <summary> /// Adds to <paramref name="targetType"/> if it does not contain an anonymous /// type and binds to the same type at the given <paramref name="position"/>. /// </summary> public static ExpressionSyntax CastIfPossible( this ExpressionSyntax expression, ITypeSymbol targetType, int position, SemanticModel semanticModel, CancellationToken cancellationToken) { if (targetType.ContainsAnonymousType()) { return(expression); } if (targetType.IsSystemVoid()) { return(expression); } if (targetType.Kind == SymbolKind.DynamicType) { targetType = semanticModel.Compilation.GetSpecialType(SpecialType.System_Object); } var typeSyntax = targetType.GenerateTypeSyntax(); var type = semanticModel.GetSpeculativeTypeInfo( position, typeSyntax, SpeculativeBindingOption.BindAsTypeOrNamespace).Type; if (!targetType.Equals(type)) { return(expression); } var castExpression = expression.Cast(targetType); // Ensure that inserting the cast doesn't change the semantics. var specAnalyzer = new SpeculationAnalyzer(expression, castExpression, semanticModel, cancellationToken); var speculativeSemanticModel = specAnalyzer.SpeculativeSemanticModel; if (speculativeSemanticModel == null) { return(expression); } var speculatedCastExpression = (CastExpressionSyntax)specAnalyzer.ReplacedExpression; if (!CastSimplifier.IsUnnecessaryCast(speculatedCastExpression, speculativeSemanticModel, cancellationToken)) { return(expression); } return(castExpression); }
public static bool CanReplaceWithReducedName( this MemberAccessExpressionSyntax memberAccess, ExpressionSyntax reducedName, SemanticModel semanticModel, CancellationToken cancellationToken) { if (!IsThisOrTypeOrNamespace(memberAccess, semanticModel)) { return(false); } var speculationAnalyzer = new SpeculationAnalyzer(memberAccess, reducedName, semanticModel, cancellationToken); if (!speculationAnalyzer.SymbolsForOriginalAndReplacedNodesAreCompatible() || speculationAnalyzer.ReplacementChangesSemantics()) { return(false); } if (WillConflictWithExistingLocal(memberAccess, reducedName)) { return(false); } if (IsMemberAccessADynamicInvocation(memberAccess, semanticModel)) { return(false); } if (memberAccess.AccessMethodWithDynamicArgumentInsideStructConstructor(semanticModel)) { return(false); } if (memberAccess.Expression.Kind() == SyntaxKind.BaseExpression) { var enclosingNamedType = semanticModel.GetEnclosingNamedType(memberAccess.SpanStart, cancellationToken); var symbol = semanticModel.GetSymbolInfo(memberAccess.Name).Symbol; if (enclosingNamedType != null && !enclosingNamedType.IsSealed && symbol != null && symbol.IsOverridable()) { return(false); } } var invalidTransformation1 = ParserWouldTreatExpressionAsCast(reducedName, memberAccess); return(!invalidTransformation1); }
private static Conversion GetSpeculatedExpressionToOuterTypeConversion(ExpressionSyntax speculatedExpression, SpeculationAnalyzer speculationAnalyzer, CancellationToken cancellationToken) { var typeInfo = speculationAnalyzer.SpeculativeSemanticModel.GetTypeInfo(speculatedExpression, cancellationToken); var conversion = speculationAnalyzer.SpeculativeSemanticModel.GetConversion(speculatedExpression, cancellationToken); if (!conversion.IsIdentity) { return(conversion); } var speculatedExpressionOuterType = GetOuterCastType(speculatedExpression, speculationAnalyzer.SpeculativeSemanticModel, out var discarded) ?? typeInfo.ConvertedType; if (speculatedExpressionOuterType == null) { return(default);
private SyntaxToken SimplifyIdentifierToken( SyntaxToken token, SemanticModel semanticModel, OptionSet optionSet, CancellationToken cancellationToken) { var unescapedIdentifier = token.ValueText; var enclosingXmlNameAttr = token.GetAncestors(n => n is XmlNameAttributeSyntax).FirstOrDefault(); // always escape keywords if (SyntaxFacts.GetKeywordKind(unescapedIdentifier) != SyntaxKind.None && enclosingXmlNameAttr == null) { return CreateNewIdentifierTokenFromToken(token, escape: true); } // Escape the Await Identifier if within the Single Line Lambda & Multi Line Context // and async method var parent = token.Parent; if (SyntaxFacts.GetContextualKeywordKind(unescapedIdentifier) == SyntaxKind.AwaitKeyword) { var enclosingLambdaExpression = parent.GetAncestorsOrThis(n => (n is SimpleLambdaExpressionSyntax || n is ParenthesizedLambdaExpressionSyntax)).FirstOrDefault(); if (enclosingLambdaExpression != null) { if (enclosingLambdaExpression is SimpleLambdaExpressionSyntax) { if (((SimpleLambdaExpressionSyntax)enclosingLambdaExpression).AsyncKeyword.Kind() == SyntaxKind.AsyncKeyword) { return token; } } if (enclosingLambdaExpression is ParenthesizedLambdaExpressionSyntax) { if (((ParenthesizedLambdaExpressionSyntax)enclosingLambdaExpression).AsyncKeyword.Kind() == SyntaxKind.AsyncKeyword) { return token; } } } var enclosingMethodBlock = parent.GetAncestorsOrThis(n => n is MethodDeclarationSyntax).FirstOrDefault(); if (enclosingMethodBlock != null && ((MethodDeclarationSyntax)enclosingMethodBlock).Modifiers.Any(n => n.Kind() == SyntaxKind.AsyncKeyword)) { return token; } } // within a query all contextual query keywords need to be escaped, even if they appear in a non query context. if (token.GetAncestors(n => n is QueryExpressionSyntax).Any()) { switch (SyntaxFacts.GetContextualKeywordKind(unescapedIdentifier)) { case SyntaxKind.FromKeyword: case SyntaxKind.WhereKeyword: case SyntaxKind.SelectKeyword: case SyntaxKind.GroupKeyword: case SyntaxKind.IntoKeyword: case SyntaxKind.OrderByKeyword: case SyntaxKind.JoinKeyword: case SyntaxKind.LetKeyword: case SyntaxKind.InKeyword: case SyntaxKind.OnKeyword: case SyntaxKind.EqualsKeyword: case SyntaxKind.ByKeyword: case SyntaxKind.AscendingKeyword: case SyntaxKind.DescendingKeyword: return CreateNewIdentifierTokenFromToken(token, escape: true); } } var result = token.Kind() == SyntaxKind.IdentifierToken ? CreateNewIdentifierTokenFromToken(token, escape: false) : token; // we can't remove the escaping if this would change the semantic. This can happen in cases // where there are two attribute declarations one with and and one without the attribute // suffix. if (SyntaxFacts.IsAttributeName(parent)) { var expression = (SimpleNameSyntax)parent; var newExpression = expression.WithIdentifier(result); var speculationAnalyzer = new SpeculationAnalyzer(expression, newExpression, semanticModel, cancellationToken); if (speculationAnalyzer.ReplacementChangesSemantics()) { return CreateNewIdentifierTokenFromToken(token, escape: true); } } // TODO: handle crefs and param names of xml doc comments. // crefs have the same escaping rules than csharp, param names do not allow escaping in Dev11, but // we may want to change that for Roslyn (Bug 17984, " Could treat '@' specially in <param>, <typeparam>, etc") return result; }
private void AnalyzeSyntax(SyntaxNodeAnalysisContext context, INamedTypeSymbol?expressionType) { var cancellationToken = context.CancellationToken; var semanticModel = context.SemanticModel; var syntaxTree = semanticModel.SyntaxTree; var preference = context.GetCSharpAnalyzerOptions().PreferMethodGroupConversion; if (preference.Notification.Severity == ReportDiagnostic.Suppress) { // User doesn't care about this rule. return; } var anonymousFunction = (AnonymousFunctionExpressionSyntax)context.Node; // Syntax checks first. // Don't simplify static lambdas. The user made them explicitly static to make it clear it must only cause // a single allocation for the cached delegate. If we get rid of the lambda (and thus the static-keyword) it // won't be clear anymore if the member-group-conversion allocation is cached or not. if (anonymousFunction.Modifiers.Any(SyntaxKind.StaticKeyword)) { return; } if (!TryGetAnonymousFunctionInvocation(anonymousFunction, out var invocation, out var wasAwaited)) { return; } // If we had an async function, but we didn't await the expression inside then we can't convert this. The // underlying value was wrapped into a task, and that won't work if directly referencing the function. if (wasAwaited != anonymousFunction.Modifiers.Any(SyntaxKind.AsyncKeyword)) { return; } // We have to have an invocation in the lambda like `() => X()` or `() => expr.X()`. var invokedExpression = invocation.Expression; if (invokedExpression is not SimpleNameSyntax and not MemberAccessExpressionSyntax) { return; } // lambda and invocation have to agree on number of parameters. var parameters = GetParameters(anonymousFunction); if (parameters.Count != invocation.ArgumentList.Arguments.Count) { return; } // parameters must be passed 1:1 from lambda to invocation. for (int i = 0, n = parameters.Count; i < n; i++) { var parameter = parameters[i]; var argument = invocation.ArgumentList.Arguments[i]; if (argument.Expression is not IdentifierNameSyntax argumentIdentifier) { return; } if (parameter.Identifier.ValueText != argumentIdentifier.Identifier.ValueText) { return; } } // if we have `() => new C().X()` then converting to `new C().X` very much changes the meaning. if (MayHaveSideEffects(invokedExpression)) { return; } // Looks like a reasonable candidate to simplify. Now switch to semantics to check for sure. if (CSharpSemanticFacts.Instance.IsInExpressionTree(semanticModel, anonymousFunction, expressionType, cancellationToken)) { return; } // If we have `object obj = x => Goo(x);` we don't want to simplify. The compiler warns if you write // `object obj = Goo;` because of the conversion to a non-delegate type. While we could insert a cast here // to make this work, that goes against the spirit of this analyzer/fixer just removing code. var lambdaTypeInfo = semanticModel.GetTypeInfo(anonymousFunction, cancellationToken); if (lambdaTypeInfo.ConvertedType == null || lambdaTypeInfo.ConvertedType.SpecialType is SpecialType.System_Object) { return; } var lambdaSymbolInfo = semanticModel.GetSymbolInfo(anonymousFunction, cancellationToken); if (lambdaSymbolInfo.Symbol is not IMethodSymbol lambdaMethod) { return; } var invokedSymbolInfo = semanticModel.GetSymbolInfo(invokedExpression, cancellationToken); if (invokedSymbolInfo.Symbol is not IMethodSymbol invokedMethod) { return; } // If we're calling a generic method, we have to have supplied type arguments. They cannot be inferred once // we remove the arguments during simplification. var invokedTypeArguments = invokedExpression.GetRightmostName() is GenericNameSyntax genericName ? genericName.TypeArgumentList.Arguments : default; if (invokedMethod.TypeArguments.Length != invokedTypeArguments.Count) { return; } // Methods have to be complimentary. That means the same number of parameters, with proper // co-contravariance for the parameters and return type. if (lambdaMethod.Parameters.Length != invokedMethod.Parameters.Length) { return; } var compilation = semanticModel.Compilation; // Must be able to convert the invoked method return type to the lambda's return type. if (!IsIdentityOrImplicitConversion(compilation, invokedMethod.ReturnType, lambdaMethod.ReturnType)) { return; } for (int i = 0, n = lambdaMethod.Parameters.Length; i < n; i++) { var lambdaParameter = lambdaMethod.Parameters[i]; var invokedParameter = invokedMethod.Parameters[i]; if (lambdaParameter.RefKind != invokedParameter.RefKind) { return; } // All the lambda parameters must be convertible to the invoked method parameters. if (!IsIdentityOrImplicitConversion(compilation, lambdaParameter.Type, invokedParameter.Type)) { return; } } // Semantically, this looks good to go. Now, do an actual speculative replacement to ensure that the // non-invoked method reference refers to the same method symbol, and that it converts to the same type that // the lambda was. var analyzer = new SpeculationAnalyzer(anonymousFunction, invokedExpression, semanticModel, cancellationToken); var rewrittenExpression = analyzer.ReplacedExpression; var rewrittenSemanticModel = analyzer.SpeculativeSemanticModel; var rewrittenSymbolInfo = rewrittenSemanticModel.GetSymbolInfo(rewrittenExpression, cancellationToken); if (rewrittenSymbolInfo.Symbol is not IMethodSymbol rewrittenMethod || !invokedMethod.Equals(rewrittenMethod)) { return; } var rewrittenConvertedType = rewrittenSemanticModel.GetTypeInfo(rewrittenExpression, cancellationToken).ConvertedType; if (!lambdaTypeInfo.ConvertedType.Equals(rewrittenConvertedType)) { return; } if (OverloadsChanged( semanticModel, anonymousFunction.GetRequiredParent(), rewrittenSemanticModel, rewrittenExpression.GetRequiredParent(), cancellationToken)) { return; } var startReportSpan = TextSpan.FromBounds(anonymousFunction.SpanStart, invokedExpression.SpanStart); var endReportSpan = TextSpan.FromBounds(invokedExpression.Span.End, anonymousFunction.Span.End); context.ReportDiagnostic(DiagnosticHelper.CreateWithLocationTags( Descriptor, syntaxTree.GetLocation(startReportSpan), preference.Notification.Severity, additionalLocations: ImmutableArray.Create(anonymousFunction.GetLocation()), additionalUnnecessaryLocations: ImmutableArray.Create( syntaxTree.GetLocation(startReportSpan), syntaxTree.GetLocation(endReportSpan)))); }
public override SyntaxNode VisitArgument(ArgumentSyntax node) { _cancellationToken.ThrowIfCancellationRequested(); var newArgument = (ArgumentSyntax)base.VisitArgument(node); var argumentType = _semanticModel.GetTypeInfo(node.Expression).ConvertedType; if (argumentType != null && !IsPassedToDelegateCreationExpression(node, argumentType)) { var specAnalyzer = new SpeculationAnalyzer(node.Expression, newArgument.Expression, _semanticModel, _cancellationToken); var speculativeSemanticModel = specAnalyzer.SpeculativeSemanticModel; var speculatedExpession = specAnalyzer.ReplacedExpression; bool wasCastAdded; var newArgumentExpression = speculatedExpession.CastIfPossible(argumentType, speculatedExpession.SpanStart, speculativeSemanticModel, out wasCastAdded); if (wasCastAdded) { return newArgument.WithExpression(newArgumentExpression); } } return newArgument; }
private static bool IsUnnecessaryCast( ExpressionSyntax castNode, ExpressionSyntax castedExpressionNode, SemanticModel semanticModel, CancellationToken cancellationToken) { var speculationAnalyzer = new SpeculationAnalyzer(castNode, castedExpressionNode, semanticModel, cancellationToken, skipVerificationForReplacedNode: true, failOnOverloadResolutionFailuresInOriginalCode: true); // First, check to see if the node ultimately parenting this cast has any // syntax errors. If so, we bail. if (speculationAnalyzer.SemanticRootOfOriginalExpression.ContainsDiagnostics) { return(false); } var castTypeInfo = semanticModel.GetTypeInfo(castNode, cancellationToken); var castType = castTypeInfo.Type; // Case: // 1 . Console.WriteLine(await (dynamic)task); Any Dynamic Cast will not be removed. if (castType == null || castType.Kind == SymbolKind.DynamicType || castType.IsErrorType()) { return(false); } var expressionTypeInfo = semanticModel.GetTypeInfo(castedExpressionNode, cancellationToken); var expressionType = expressionTypeInfo.Type; if (EnumCastDefinitelyCantBeRemoved(castNode, expressionType, castType)) { return(false); } // We do not remove any cast on // 1. Dynamic Expressions // 2. If there is any other argument which is dynamic // 3. Dynamic Invocation // 4. Assignment to dynamic if ((expressionType != null && (expressionType.IsErrorType() || expressionType.Kind == SymbolKind.DynamicType)) || IsDynamicInvocation(castNode, semanticModel, cancellationToken) || IsDynamicAssignment(castNode, semanticModel, cancellationToken)) { return(false); } if (PointerCastDefinitelyCantBeRemoved(castNode, castedExpressionNode)) { return(false); } if (CastPassedToParamsArrayDefinitelyCantBeRemoved(castNode, castType, semanticModel, cancellationToken)) { return(false); } // A casts to object can always be removed from an expression inside of an interpolation, since it'll be converted to object // in order to call string.Format(...) anyway. if (castType?.SpecialType == SpecialType.System_Object && castNode.WalkUpParentheses().IsParentKind(SyntaxKind.Interpolation)) { return(true); } if (speculationAnalyzer.ReplacementChangesSemantics()) { return(false); } var expressionToCastType = semanticModel.ClassifyConversion(castNode.SpanStart, castedExpressionNode, castType, isExplicitInSource: true); var outerType = GetOuterCastType(castNode, semanticModel, out var parentIsOrAsExpression) ?? castTypeInfo.ConvertedType; // Simple case: If the conversion from the inner expression to the cast type is identity, // the cast can be removed. if (expressionToCastType.IsIdentity) { // Simple case: Is this an identity cast to another cast? If so, we're safe to remove it. if (castedExpressionNode.WalkDownParentheses().IsKind(SyntaxKind.CastExpression)) { return(true); } // Required explicit cast for reference comparison. // Cast removal causes warning CS0252 (Possible unintended reference comparison). // object x = string.Intern("Hi!"); // (object)x == "Hi!" if (IsRequiredCastForReferenceEqualityComparison(outerType, castNode, semanticModel, out var other)) { var otherToOuterType = semanticModel.ClassifyConversion(other, outerType); if (otherToOuterType.IsImplicit && otherToOuterType.IsReference) { return(false); } } if (SameSizedFloatingPointCastMustBePreserved( semanticModel, castNode, castedExpressionNode, expressionType, castType, cancellationToken)) { return(false); } return(true); } Debug.Assert(!expressionToCastType.IsIdentity); if (expressionToCastType.IsExplicit) { // Explicit reference conversions can cause an exception or data loss, hence can never be removed. if (expressionToCastType.IsReference) { return(false); } // Unboxing conversions can cause a null ref exception, hence can never be removed. if (expressionToCastType.IsUnboxing) { return(false); } // Don't remove any explicit numeric casts. // https://github.com/dotnet/roslyn/issues/2987 tracks improving on this conservative approach. if (expressionToCastType.IsNumeric) { return(false); } } if (expressionToCastType.IsPointer || expressionToCastType.IsIntPtr) { // Don't remove any non-identity pointer or IntPtr conversions. // https://github.com/dotnet/roslyn/issues/2987 tracks improving on this conservative approach. return(expressionType != null && expressionType.Equals(outerType)); } if (expressionToCastType.IsInterpolatedString) { // interpolation casts are necessary to preserve semantics if our destination type is not itself // FormattableString or some interface of FormattableString. return(castType.Equals(castTypeInfo.ConvertedType) || ImmutableArray <ITypeSymbol> .CastUp(castType.AllInterfaces).Contains(castTypeInfo.ConvertedType)); } if (castedExpressionNode.WalkDownParentheses().IsKind(SyntaxKind.DefaultLiteralExpression) && !castType.Equals(outerType) && outerType.IsNullable()) { // We have a cast like `(T?)(X)default`. We can't remove the inner cast as it effects what value // 'default' means in this context. return(false); } if (parentIsOrAsExpression) { // Note: speculationAnalyzer.ReplacementChangesSemantics() ensures that the parenting is or as expression are not broken. // Here we just need to ensure that the original cast expression doesn't invoke a user defined operator. return(!expressionToCastType.IsUserDefined); } if (outerType != null) { var castToOuterType = semanticModel.ClassifyConversion(castNode.SpanStart, castNode, outerType); var expressionToOuterType = GetSpeculatedExpressionToOuterTypeConversion(speculationAnalyzer.ReplacedExpression, speculationAnalyzer, cancellationToken); // if the conversion to the outer type doesn't exist, then we shouldn't offer, except for anonymous functions which can't be reasoned about the same way (see below) if (!expressionToOuterType.Exists && !expressionToOuterType.IsAnonymousFunction) { return(false); } // CONSIDER: Anonymous function conversions cannot be compared from different semantic models as lambda symbol comparison requires syntax tree equality. Should this be a compiler bug? // For now, just revert back to computing expressionToOuterType using the original semantic model. if (expressionToOuterType.IsAnonymousFunction) { expressionToOuterType = semanticModel.ClassifyConversion(castNode.SpanStart, castedExpressionNode, outerType); } // If there is an user-defined conversion from the expression to the cast type or the cast // to the outer type, we need to make sure that the same user-defined conversion will be // called if the cast is removed. if (castToOuterType.IsUserDefined || expressionToCastType.IsUserDefined) { return(!expressionToOuterType.IsExplicit && (HaveSameUserDefinedConversion(expressionToCastType, expressionToOuterType) || HaveSameUserDefinedConversion(castToOuterType, expressionToOuterType)) && UserDefinedConversionIsAllowed(castNode, semanticModel)); } else if (expressionToOuterType.IsUserDefined) { return(false); } if (expressionToCastType.IsExplicit && expressionToOuterType.IsExplicit) { return(false); } // Required explicit cast for reference comparison. // Cast removal causes warning CS0252 (Possible unintended reference comparison). // object x = string.Intern("Hi!"); // x == (object)"Hi!" if (expressionToCastType.IsImplicit && expressionToCastType.IsReference && castToOuterType.IsIdentity && IsRequiredCastForReferenceEqualityComparison(outerType, castNode, semanticModel, out var other)) { return(false); } // If the conversion from the expression to the cast type is implicit numeric or constant // and the conversion from the expression to the outer type is identity, we'll go ahead // and remove the cast. if (expressionToOuterType.IsIdentity && expressionToCastType.IsImplicit && (expressionToCastType.IsNumeric || expressionToCastType.IsConstantExpression)) { // Some implicit numeric conversions can cause loss of precision and must not be removed. return(!IsRequiredImplicitNumericConversion(expressionType, castType)); } if (!castToOuterType.IsBoxing && castToOuterType == expressionToOuterType) { if (castToOuterType.IsNullable) { // Even though both the nullable conversions (castToOuterType and expressionToOuterType) are equal, we can guarantee no data loss only if there is an // implicit conversion from expression type to cast type and expression type is non-nullable. For example, consider the cast removal "(float?)" for below: // Console.WriteLine((int)(float?)(int?)2147483647); // Prints -2147483648 // castToOuterType: ExplicitNullable // expressionToOuterType: ExplicitNullable // expressionToCastType: ImplicitNullable // We should not remove the cast to "float?". // However, cast to "int?" is unnecessary and should be removable. return(expressionToCastType.IsImplicit && !expressionType.IsNullable()); } else if (expressionToCastType.IsImplicit && expressionToCastType.IsNumeric && !castToOuterType.IsIdentity) { // Some implicit numeric conversions can cause loss of precision and must not be removed. return(!IsRequiredImplicitNumericConversion(expressionType, castType)); } return(true); } if (castToOuterType.IsIdentity && !expressionToCastType.IsUnboxing && expressionToCastType == expressionToOuterType) { return(true); } // Special case: It's possible to have useless casts inside delegate creation expressions. // For example: new Func<string, bool>((Predicate<object>)(y => true)). if (IsInDelegateCreationExpression(castNode, semanticModel)) { if (expressionToCastType.IsAnonymousFunction && expressionToOuterType.IsAnonymousFunction) { return(!speculationAnalyzer.ReplacementChangesSemanticsOfUnchangedLambda(castedExpressionNode, speculationAnalyzer.ReplacedExpression)); } if (expressionToCastType.IsMethodGroup && expressionToOuterType.IsMethodGroup) { return(true); } } // Case : // 1. IList<object> y = (IList<dynamic>)new List<object>() if (expressionToCastType.IsExplicit && castToOuterType.IsExplicit && expressionToOuterType.IsImplicit) { // If both expressionToCastType and castToOuterType are numeric, then this is a required cast as one of the conversions leads to loss of precision. // Cast removal can change program behavior. return(!(expressionToCastType.IsNumeric && castToOuterType.IsNumeric)); } // Case : // 2. object y = (ValueType)1; if (expressionToCastType.IsBoxing && expressionToOuterType.IsBoxing && castToOuterType.IsImplicit) { return(true); } // Case : // 3. object y = (NullableValueType)null; if ((!castToOuterType.IsBoxing || expressionToCastType.IsNullLiteral) && castToOuterType.IsImplicit && expressionToCastType.IsImplicit && expressionToOuterType.IsImplicit) { if (expressionToOuterType.IsAnonymousFunction) { return(expressionToCastType.IsAnonymousFunction && !speculationAnalyzer.ReplacementChangesSemanticsOfUnchangedLambda(castedExpressionNode, speculationAnalyzer.ReplacedExpression)); } return(true); } } return(false); }
private static SyntaxToken SimplifyIdentifierToken( SyntaxToken token, SemanticModel semanticModel, OptionSet optionSet, CancellationToken cancellationToken) { var unescapedIdentifier = token.ValueText; var enclosingXmlNameAttr = token.GetAncestors(n => n is XmlNameAttributeSyntax).FirstOrDefault(); // always escape keywords if (SyntaxFacts.GetKeywordKind(unescapedIdentifier) != SyntaxKind.None && enclosingXmlNameAttr == null) { return(CreateNewIdentifierTokenFromToken(token, escape: true)); } // Escape the Await Identifier if within the Single Line Lambda & Multi Line Context // and async method var parent = token.Parent; if (SyntaxFacts.GetContextualKeywordKind(unescapedIdentifier) == SyntaxKind.AwaitKeyword) { var enclosingLambdaExpression = parent.GetAncestorsOrThis(n => (n is SimpleLambdaExpressionSyntax || n is ParenthesizedLambdaExpressionSyntax)).FirstOrDefault(); if (enclosingLambdaExpression != null) { if (enclosingLambdaExpression is SimpleLambdaExpressionSyntax simpleLambda) { if (simpleLambda.AsyncKeyword.Kind() == SyntaxKind.AsyncKeyword) { return(token); } } if (enclosingLambdaExpression is ParenthesizedLambdaExpressionSyntax parenLamdba) { if (parenLamdba.AsyncKeyword.Kind() == SyntaxKind.AsyncKeyword) { return(token); } } } var enclosingMethodBlock = parent.GetAncestorsOrThis(n => n is MethodDeclarationSyntax).FirstOrDefault(); if (enclosingMethodBlock != null && ((MethodDeclarationSyntax)enclosingMethodBlock).Modifiers.Any(n => n.Kind() == SyntaxKind.AsyncKeyword)) { return(token); } } // within a query all contextual query keywords need to be escaped, even if they appear in a non query context. if (token.GetAncestors(n => n is QueryExpressionSyntax).Any()) { switch (SyntaxFacts.GetContextualKeywordKind(unescapedIdentifier)) { case SyntaxKind.FromKeyword: case SyntaxKind.WhereKeyword: case SyntaxKind.SelectKeyword: case SyntaxKind.GroupKeyword: case SyntaxKind.IntoKeyword: case SyntaxKind.OrderByKeyword: case SyntaxKind.JoinKeyword: case SyntaxKind.LetKeyword: case SyntaxKind.InKeyword: case SyntaxKind.OnKeyword: case SyntaxKind.EqualsKeyword: case SyntaxKind.ByKeyword: case SyntaxKind.AscendingKeyword: case SyntaxKind.DescendingKeyword: return(CreateNewIdentifierTokenFromToken(token, escape: true)); } } var result = token.Kind() == SyntaxKind.IdentifierToken ? CreateNewIdentifierTokenFromToken(token, escape: false) : token; // we can't remove the escaping if this would change the semantic. This can happen in cases // where there are two attribute declarations: one with and one without the attribute // suffix. if (SyntaxFacts.IsAttributeName(parent)) { var expression = (SimpleNameSyntax)parent; var newExpression = expression.WithIdentifier(result); var speculationAnalyzer = new SpeculationAnalyzer(expression, newExpression, semanticModel, cancellationToken); if (speculationAnalyzer.ReplacementChangesSemantics()) { return(CreateNewIdentifierTokenFromToken(token, escape: true)); } } // TODO: handle crefs and param names of xml doc comments. // crefs have the same escaping rules than csharp, param names do not allow escaping in Dev11, but // we may want to change that for Roslyn (Bug 17984, " Could treat '@' specially in <param>, <typeparam>, etc") return(result); }
public static bool IsUnnecessaryCast(this CastExpressionSyntax cast, SemanticModel semanticModel, CancellationToken cancellationToken) { var speculationAnalyzer = new SpeculationAnalyzer(cast, cast.Expression, semanticModel, cancellationToken, skipVerificationForReplacedNode: true, failOnOverloadResolutionFailuresInOriginalCode: true); // First, check to see if the node ultimately parenting this cast has any // syntax errors. If so, we bail. if (speculationAnalyzer.SemanticRootOfOriginalExpression.ContainsDiagnostics) { return(false); } var castTypeInfo = semanticModel.GetTypeInfo(cast, cancellationToken); var castType = castTypeInfo.Type; // Case: // 1 . Console.WriteLine(await (dynamic)task); Any Dynamic Cast will not be removed. if (castType == null || castType.Kind == SymbolKind.DynamicType || castType.IsErrorType()) { return(false); } var expressionTypeInfo = semanticModel.GetTypeInfo(cast.Expression, cancellationToken); var expressionType = expressionTypeInfo.Type; // We do not remove any cast on // 1. Dynamic Expressions // 2. If there is any other argument which is dynamic // 3. Dynamic Invocation if ((expressionType != null && (expressionType.IsErrorType() || expressionType.Kind == SymbolKind.DynamicType)) || IsDynamicInvocation(cast, semanticModel, cancellationToken)) { return(false); } if (PointerCastDefinitelyCantBeRemoved(cast)) { return(false); } if (CastPassedToParamsArrayDefinitelyCantBeRemoved(cast, castType, semanticModel, cancellationToken)) { return(false); } if (speculationAnalyzer.ReplacementChangesSemantics()) { return(false); } var expressionToCastType = semanticModel.ClassifyConversion(cast.SpanStart, cast.Expression, castType, isExplicitInSource: true); bool parentIsOrAsExpression; var outerType = GetOuterCastType(cast, semanticModel, out parentIsOrAsExpression) ?? castTypeInfo.ConvertedType; // Simple case: If the conversion from the inner expression to the cast type is identity, // the cast can be removed. if (expressionToCastType.IsIdentity) { // Required explicit cast for reference comparison. // Cast removal causes warning CS0252 (Possible unintended reference comparison). // object x = string.Intern("Hi!"); // (object)x == "Hi!" ExpressionSyntax other; if (IsRequiredCastForReferenceEqualityComparison(outerType, cast, semanticModel, out other)) { var otherToOuterType = semanticModel.ClassifyConversion(other, outerType); if (otherToOuterType.IsImplicit && otherToOuterType.IsReference) { return(false); } } return(true); } if (parentIsOrAsExpression) { // Note: speculationAnalyzer.ReplacementChangesSemantics() ensures that the parenting is or as expression are not broken. // Here we just need to ensure that the original cast expression doesn't invoke a user defined operator. return(!expressionToCastType.IsUserDefined); } if (outerType != null) { var castToOuterType = semanticModel.ClassifyConversion(cast.SpanStart, cast, outerType); var expressionToOuterType = GetSpeculatedExpressionToOuterTypeConversion(speculationAnalyzer.ReplacedExpression, speculationAnalyzer, cancellationToken); // CONSIDER: Anonymous function conversions cannot be compared from different semantic models as lambda symbol comparison requires syntax tree equality. Should this be a compiler bug? // For now, just revert back to computing expressionToOuterType using the original semantic model. if (expressionToOuterType.IsAnonymousFunction) { expressionToOuterType = semanticModel.ClassifyConversion(cast.SpanStart, cast.Expression, outerType); } // If there is an user-defined conversion from the expression to the cast type or the cast // to the outer type, we need to make sure that the same user-defined conversion will be // called if the cast is removed. if (castToOuterType.IsUserDefined || expressionToCastType.IsUserDefined) { return(!expressionToOuterType.IsExplicit && (HaveSameUserDefinedConversion(expressionToCastType, expressionToOuterType) || HaveSameUserDefinedConversion(castToOuterType, expressionToOuterType)) && UserDefinedConversionIsAllowed(cast, semanticModel)); } else if (expressionToOuterType.IsUserDefined) { return(false); } if (expressionToCastType.IsExplicit && expressionToOuterType.IsExplicit) { return(false); } // Required explicit cast for reference comparison. // Cast removal causes warning CS0252 (Possible unintended reference comparison). // object x = string.Intern("Hi!"); // x == (object)"Hi!" ExpressionSyntax other; if (expressionToCastType.IsImplicit && expressionToCastType.IsReference && castToOuterType.IsIdentity && IsRequiredCastForReferenceEqualityComparison(outerType, cast, semanticModel, out other)) { return(false); } // If the conversion from the expression to the cast type is implicit numeric or constant // and the conversion from the expression to the outer type is identity, we'll go ahead // and remove the cast. if (expressionToOuterType.IsIdentity && expressionToCastType.IsImplicit && (expressionToCastType.IsNumeric || expressionToCastType.IsConstantExpression)) { return(true); } if (!castToOuterType.IsBoxing && castToOuterType == expressionToOuterType) { if (castToOuterType.IsNullable) { // Even though both the nullable conversions (castToOuterType and expressionToOuterType) are equal, we can guarantee no data loss only if there is an // implicit conversion from expression type to cast type and expression type is non-nullable. For example, consider the cast removal "(float?)" for below: // Console.WriteLine((int)(float?)(int?)2147483647); // Prints -2147483648 // castToOuterType: ExplicitNullable // expressionToOuterType: ExplicitNullable // expressionToCastType: ImplicitNullable // We should not remove the cast to "float?". // However, cast to "int?" is unnecessary and should be removable. return(expressionToCastType.IsImplicit && ((ITypeSymbol)expressionType.OriginalDefinition).SpecialType != SpecialType.System_Nullable_T); } else if (expressionToCastType.IsImplicit && expressionToCastType.IsNumeric && !castToOuterType.IsIdentity) { // Some implicit numeric conversions can cause loss of precision and must not be removed. return(!IsRequiredImplicitNumericConversion(expressionType, castType)); } return(true); } if (castToOuterType.IsIdentity && !expressionToCastType.IsUnboxing && expressionToCastType == expressionToOuterType) { return(true); } // Special case: It's possible to have useless casts inside delegate creation expressions. // For example: new Func<string, bool>((Predicate<object>)(y => true)). if (IsInDelegateCreationExpression(cast, semanticModel)) { if (expressionToCastType.IsAnonymousFunction && expressionToOuterType.IsAnonymousFunction) { return(!speculationAnalyzer.ReplacementChangesSemanticsOfUnchangedLambda(cast.Expression, speculationAnalyzer.ReplacedExpression)); } if (expressionToCastType.IsMethodGroup && expressionToOuterType.IsMethodGroup) { return(true); } } // Case : // 1. IList<object> y = (IList<dynamic>)new List<object>() if (expressionToCastType.IsExplicit && castToOuterType.IsExplicit && expressionToOuterType.IsImplicit) { return(true); } // Case : // 2. object y = (ValueType)1; if (expressionToCastType.IsBoxing && expressionToOuterType.IsBoxing && castToOuterType.IsImplicit) { return(true); } // Case : // 3. object y = (NullableValueType)null; if ((!castToOuterType.IsBoxing || expressionToCastType.IsNullLiteral) && castToOuterType.IsImplicit && expressionToCastType.IsImplicit && expressionToOuterType.IsImplicit) { if (expressionToOuterType.IsAnonymousFunction) { return(expressionToCastType.IsAnonymousFunction && !speculationAnalyzer.ReplacementChangesSemanticsOfUnchangedLambda(cast.Expression, speculationAnalyzer.ReplacedExpression)); } return(true); } // case : // 4. baseType x; // baseType y = (DerivedType)x; if (expressionToOuterType.IsIdentity && castToOuterType.IsImplicit && castToOuterType.IsReference) { return(true); } } return(false); }
private static Conversion GetSpeculatedExpressionToOuterTypeConversion(ExpressionSyntax speculatedExpression, SpeculationAnalyzer speculationAnalyzer, CancellationToken cancellationToken) { var typeInfo = speculationAnalyzer.SpeculativeSemanticModel.GetTypeInfo(speculatedExpression, cancellationToken); var conversion = speculationAnalyzer.SpeculativeSemanticModel.GetConversion(speculatedExpression, cancellationToken); if (!conversion.IsIdentity) { return conversion; } bool discarded; var speculatedExpressionOuterType = GetOuterCastType(speculatedExpression, speculationAnalyzer.SpeculativeSemanticModel, out discarded) ?? typeInfo.ConvertedType; if (speculatedExpressionOuterType == null) { return default(Conversion); } return speculationAnalyzer.SpeculativeSemanticModel.ClassifyConversion(speculatedExpression, speculatedExpressionOuterType); }
public static bool IsUnnecessaryCast(this CastExpressionSyntax cast, SemanticModel semanticModel, CancellationToken cancellationToken) { var speculationAnalyzer = new SpeculationAnalyzer(cast, cast.Expression, semanticModel, cancellationToken, skipVerificationForReplacedNode: true, failOnOverloadResolutionFailuresInOriginalCode: true); // First, check to see if the node ultimately parenting this cast has any // syntax errors. If so, we bail. if (speculationAnalyzer.SemanticRootOfOriginalExpression.ContainsDiagnostics) { return false; } var castTypeInfo = semanticModel.GetTypeInfo(cast, cancellationToken); var castType = castTypeInfo.Type; // Case: // 1 . Console.WriteLine(await (dynamic)task); Any Dynamic Cast will not be removed. if (castType == null || castType.Kind == SymbolKind.DynamicType || castType.IsErrorType()) { return false; } var expressionTypeInfo = semanticModel.GetTypeInfo(cast.Expression, cancellationToken); var expressionType = expressionTypeInfo.Type; // We do not remove any cast on // 1. Dynamic Expressions // 2. If there is any other argument which is dynamic // 3. Dynamic Invocation if ((expressionType != null && (expressionType.IsErrorType() || expressionType.Kind == SymbolKind.DynamicType)) || IsDynamicInvocation(cast, semanticModel, cancellationToken)) { return false; } if (PointerCastDefinitelyCantBeRemoved(cast)) { return false; } if (CastPassedToParamsArrayDefinitelyCantBeRemoved(cast, castType, semanticModel, cancellationToken)) { return false; } if (speculationAnalyzer.ReplacementChangesSemantics()) { return false; } var expressionToCastType = semanticModel.ClassifyConversion(cast.SpanStart, cast.Expression, castType, isExplicitInSource: true); bool parentIsOrAsExpression; var outerType = GetOuterCastType(cast, semanticModel, out parentIsOrAsExpression) ?? castTypeInfo.ConvertedType; // Simple case: If the conversion from the inner expression to the cast type is identity, // the cast can be removed. if (expressionToCastType.IsIdentity) { // Required explicit cast for reference comparison. // Cast removal causes warning CS0252 (Possible unintended reference comparison). // object x = string.Intern("Hi!"); // (object)x == "Hi!" ExpressionSyntax other; if (IsRequiredCastForReferenceEqualityComparison(outerType, cast, semanticModel, out other)) { var otherToOuterType = semanticModel.ClassifyConversion(other, outerType); if (otherToOuterType.IsImplicit && otherToOuterType.IsReference) { return false; } } return true; } else if (expressionToCastType.IsExplicit && expressionToCastType.IsReference) { // Explicit reference conversions can cause an exception or data loss, hence can never be removed. return false; } else if (expressionToCastType.IsExplicit && expressionToCastType.IsNumeric && IsInExplicitCheckedOrUncheckedContext(cast)) { // Don't remove any explicit numeric casts in explicit checked/unchecked context. // https://github.com/dotnet/roslyn/issues/2987 tracks improving on this conservative approach. return false; } else if (expressionToCastType.IsPointer) { // Don't remove any non-identity pointer conversions. // https://github.com/dotnet/roslyn/issues/2987 tracks improving on this conservative approach. return expressionType != null && expressionType.Equals(outerType); } if (parentIsOrAsExpression) { // Note: speculationAnalyzer.ReplacementChangesSemantics() ensures that the parenting is or as expression are not broken. // Here we just need to ensure that the original cast expression doesn't invoke a user defined operator. return !expressionToCastType.IsUserDefined; } if (outerType != null) { var castToOuterType = semanticModel.ClassifyConversion(cast.SpanStart, cast, outerType); var expressionToOuterType = GetSpeculatedExpressionToOuterTypeConversion(speculationAnalyzer.ReplacedExpression, speculationAnalyzer, cancellationToken); // CONSIDER: Anonymous function conversions cannot be compared from different semantic models as lambda symbol comparison requires syntax tree equality. Should this be a compiler bug? // For now, just revert back to computing expressionToOuterType using the original semantic model. if (expressionToOuterType.IsAnonymousFunction) { expressionToOuterType = semanticModel.ClassifyConversion(cast.SpanStart, cast.Expression, outerType); } // If there is an user-defined conversion from the expression to the cast type or the cast // to the outer type, we need to make sure that the same user-defined conversion will be // called if the cast is removed. if (castToOuterType.IsUserDefined || expressionToCastType.IsUserDefined) { return !expressionToOuterType.IsExplicit && (HaveSameUserDefinedConversion(expressionToCastType, expressionToOuterType) || HaveSameUserDefinedConversion(castToOuterType, expressionToOuterType)) && UserDefinedConversionIsAllowed(cast, semanticModel); } else if (expressionToOuterType.IsUserDefined) { return false; } if (expressionToCastType.IsExplicit && expressionToOuterType.IsExplicit) { return false; } // Required explicit cast for reference comparison. // Cast removal causes warning CS0252 (Possible unintended reference comparison). // object x = string.Intern("Hi!"); // x == (object)"Hi!" ExpressionSyntax other; if (expressionToCastType.IsImplicit && expressionToCastType.IsReference && castToOuterType.IsIdentity && IsRequiredCastForReferenceEqualityComparison(outerType, cast, semanticModel, out other)) { return false; } // If the conversion from the expression to the cast type is implicit numeric or constant // and the conversion from the expression to the outer type is identity, we'll go ahead // and remove the cast. if (expressionToOuterType.IsIdentity && expressionToCastType.IsImplicit && (expressionToCastType.IsNumeric || expressionToCastType.IsConstantExpression)) { return true; } if (!castToOuterType.IsBoxing && castToOuterType == expressionToOuterType) { if (castToOuterType.IsNullable) { // Even though both the nullable conversions (castToOuterType and expressionToOuterType) are equal, we can guarantee no data loss only if there is an // implicit conversion from expression type to cast type and expression type is non-nullable. For example, consider the cast removal "(float?)" for below: // Console.WriteLine((int)(float?)(int?)2147483647); // Prints -2147483648 // castToOuterType: ExplicitNullable // expressionToOuterType: ExplicitNullable // expressionToCastType: ImplicitNullable // We should not remove the cast to "float?". // However, cast to "int?" is unnecessary and should be removable. return expressionToCastType.IsImplicit && !((ITypeSymbol)expressionType).IsNullable(); } else if (expressionToCastType.IsImplicit && expressionToCastType.IsNumeric && !castToOuterType.IsIdentity) { // Some implicit numeric conversions can cause loss of precision and must not be removed. return !IsRequiredImplicitNumericConversion(expressionType, castType); } return true; } if (castToOuterType.IsIdentity && !expressionToCastType.IsUnboxing && expressionToCastType == expressionToOuterType) { return true; } // Special case: It's possible to have useless casts inside delegate creation expressions. // For example: new Func<string, bool>((Predicate<object>)(y => true)). if (IsInDelegateCreationExpression(cast, semanticModel)) { if (expressionToCastType.IsAnonymousFunction && expressionToOuterType.IsAnonymousFunction) { return !speculationAnalyzer.ReplacementChangesSemanticsOfUnchangedLambda(cast.Expression, speculationAnalyzer.ReplacedExpression); } if (expressionToCastType.IsMethodGroup && expressionToOuterType.IsMethodGroup) { return true; } } // Case : // 1. IList<object> y = (IList<dynamic>)new List<object>() if (expressionToCastType.IsExplicit && castToOuterType.IsExplicit && expressionToOuterType.IsImplicit) { // If both expressionToCastType and castToOuterType are numeric, then this is a required cast as one of the conversions leads to loss of precision. // Cast removal can change program behavior. return !(expressionToCastType.IsNumeric && castToOuterType.IsNumeric); } // Case : // 2. object y = (ValueType)1; if (expressionToCastType.IsBoxing && expressionToOuterType.IsBoxing && castToOuterType.IsImplicit) { return true; } // Case : // 3. object y = (NullableValueType)null; if ((!castToOuterType.IsBoxing || expressionToCastType.IsNullLiteral) && castToOuterType.IsImplicit && expressionToCastType.IsImplicit && expressionToOuterType.IsImplicit) { if (expressionToOuterType.IsAnonymousFunction) { return expressionToCastType.IsAnonymousFunction && !speculationAnalyzer.ReplacementChangesSemanticsOfUnchangedLambda(cast.Expression, speculationAnalyzer.ReplacedExpression); } return true; } } return false; }
private static bool CanReplaceWithReducedName(this NameSyntax name, TypeSyntax reducedName, SemanticModel semanticModel, CancellationToken cancellationToken) { var speculationAnalyzer = new SpeculationAnalyzer(name, reducedName, semanticModel, cancellationToken); if (speculationAnalyzer.ReplacementChangesSemantics()) { return false; } return CanReplaceWithReducedNameInContext(name, reducedName, semanticModel, cancellationToken); }
private static bool ReplacementChangesSemantics(ExpressionSyntax originalExpression, ExpressionSyntax replacedExpression, SemanticModel semanticModel) { var speculationAnalyzer = new SpeculationAnalyzer(originalExpression, replacedExpression, semanticModel, CancellationToken.None); return speculationAnalyzer.ReplacementChangesSemantics(); }
protected override async Task <Document> AddImportAsync( SyntaxNode contextNode, INamespaceOrTypeSymbol namespaceSymbol, Document document, bool placeSystemNamespaceFirst, CancellationToken cancellationToken) { var root = GetCompilationUnitSyntaxNode(contextNode, cancellationToken); var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var simpleUsingDirective = GetUsingDirective(root, namespaceSymbol, semanticModel, fullyQualify: false); var externAliasUsingDirective = GetExternAliasUsingDirective(root, namespaceSymbol, semanticModel); if (externAliasUsingDirective != null) { root = root.AddExterns( externAliasUsingDirective .WithAdditionalAnnotations(Formatter.Annotation)); } if (simpleUsingDirective != null) { // Because of the way usings can be nested inside of namespace declarations, // we need to check if the usings must be fully qualified so as not to be // ambiguous with the containing namespace. if (UsingsAreContainedInNamespace(contextNode)) { // When we add usings we try and place them, as best we can, where the user // wants them according to their settings. This means we can't just add the fully- // qualified usings and expect the simplifier to take care of it, the usings have to be // simplified before we attempt to add them to the document. // You might be tempted to think that we could call // AddUsings -> Simplifier -> SortUsings // But this will clobber the users using settings without asking. Instead we create a new // Document and check if our using can be simplified. Worst case we need to back out the // fully qualified change and reapply with the simple name. var fullyQualifiedUsingDirective = GetUsingDirective(root, namespaceSymbol, semanticModel, fullyQualify: true); SyntaxNode newRoot = root.AddUsingDirective( fullyQualifiedUsingDirective, contextNode, placeSystemNamespaceFirst, Formatter.Annotation); var newDocument = document.WithSyntaxRoot(newRoot); var newSemanticModel = await newDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); newRoot = await newDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var newUsing = newRoot .DescendantNodes().OfType <UsingDirectiveSyntax>().Where(uds => uds.IsEquivalentTo(fullyQualifiedUsingDirective, topLevel: true)).Single(); var speculationAnalyzer = new SpeculationAnalyzer(newUsing.Name, simpleUsingDirective.Name, newSemanticModel, cancellationToken); if (speculationAnalyzer.ReplacementChangesSemantics()) { // Not fully qualifying the using causes to refer to a different namespace so we need to keep it as is. return(newDocument); } else { // It does not matter if it is fully qualified or simple so lets return the simple name. return(document.WithSyntaxRoot(root.AddUsingDirective( simpleUsingDirective, contextNode, placeSystemNamespaceFirst, Formatter.Annotation))); } } else { // simple form return(document.WithSyntaxRoot(root.AddUsingDirective( simpleUsingDirective, contextNode, placeSystemNamespaceFirst, Formatter.Annotation))); } } return(document.WithSyntaxRoot(root)); }
private static bool CanReplaceWithReducedName( this MemberAccessExpressionSyntax memberAccess, ExpressionSyntax reducedName, SemanticModel semanticModel, CancellationToken cancellationToken) { if (!IsThisOrTypeOrNamespace(memberAccess, semanticModel)) { return false; } var speculationAnalyzer = new SpeculationAnalyzer(memberAccess, reducedName, semanticModel, cancellationToken); if (!speculationAnalyzer.SymbolsForOriginalAndReplacedNodesAreCompatible() || speculationAnalyzer.ReplacementChangesSemantics()) { return false; } if (WillConflictWithExistingLocal(memberAccess, reducedName)) { return false; } if (IsMemberAccessADynamicInvocation(memberAccess, semanticModel)) { return false; } if (memberAccess.AccessMethodWithDynamicArgumentInsideStructConstructor(semanticModel)) { return false; } if (memberAccess.Expression.Kind() == SyntaxKind.BaseExpression) { var enclosingNamedType = semanticModel.GetEnclosingNamedType(memberAccess.SpanStart, cancellationToken); var symbol = semanticModel.GetSymbolInfo(memberAccess.Name, cancellationToken).Symbol; if (enclosingNamedType != null && !enclosingNamedType.IsSealed && symbol != null && symbol.IsOverridable()) { return false; } } var invalidTransformation1 = ParserWouldTreatExpressionAsCast(reducedName, memberAccess); return !invalidTransformation1; }
private static async Task <Document> DetectSemanticConflicts( Document inlinedDocument, SemanticModel newSemanticModelForInlinedDocument, SemanticModel semanticModelBeforeInline, SymbolInfo originalInitializerSymbolInfo, CancellationToken cancellationToken) { // In this method we detect if inlining the expression introduced the following semantic change: // The symbol info associated with any of the inlined expressions does not match the symbol info for original initializer expression prior to inline. // If any semantic changes were introduced by inlining, we update the document with conflict annotations. // Otherwise we return the given inlined document without any changes. var syntaxRootBeforeInline = await semanticModelBeforeInline.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); // Get all the identifier nodes which were replaced with inlined expression. var originalIdentifierNodes = FindReferenceAnnotatedNodes(syntaxRootBeforeInline); if (originalIdentifierNodes.IsEmpty()) { // No conflicts return(inlinedDocument); } // Get all the inlined expression nodes. var syntaxRootAfterInline = await inlinedDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var inlinedExprNodes = syntaxRootAfterInline.GetAnnotatedNodesAndTokens(ExpressionToInlineAnnotation); Debug.Assert(originalIdentifierNodes.Count() == inlinedExprNodes.Count()); var originalNodesEnum = originalIdentifierNodes.GetEnumerator(); var inlinedNodesEnum = inlinedExprNodes.GetEnumerator(); Dictionary <SyntaxNode, SyntaxNode> replacementNodesWithChangedSemantics = null; while (originalNodesEnum.MoveNext()) { inlinedNodesEnum.MoveNext(); var originalNode = originalNodesEnum.Current; // expressionToInline is Parenthesized prior to replacement, so get the parenting parenthesized expression. var inlinedNode = (ExpressionSyntax)inlinedNodesEnum.Current.AsNode().Parent; Debug.Assert(inlinedNode.IsKind(SyntaxKind.ParenthesizedExpression)); // inlinedNode is the expanded form of the actual initializer expression in the original document. // We have annotated the inner initializer with a special syntax annotation "InitializerAnnotation". // Get this annotated node and compute the symbol info for this node in the inlined document. var innerInitializerInInlineNode = (ExpressionSyntax)inlinedNode.GetAnnotatedNodesAndTokens(InitializerAnnotation).First().AsNode(); var newInializerSymbolInfo = newSemanticModelForInlinedDocument.GetSymbolInfo(innerInitializerInInlineNode, cancellationToken); // Verification: The symbol info associated with any of the inlined expressions does not match the symbol info for original initializer expression prior to inline. if (!SpeculationAnalyzer.SymbolInfosAreCompatible(originalInitializerSymbolInfo, newInializerSymbolInfo, performEquivalenceCheck: true)) { newInializerSymbolInfo = newSemanticModelForInlinedDocument.GetSymbolInfo(inlinedNode, cancellationToken); if (!SpeculationAnalyzer.SymbolInfosAreCompatible(originalInitializerSymbolInfo, newInializerSymbolInfo, performEquivalenceCheck: true)) { if (replacementNodesWithChangedSemantics == null) { replacementNodesWithChangedSemantics = new Dictionary <SyntaxNode, SyntaxNode>(); } replacementNodesWithChangedSemantics.Add(inlinedNode, originalNode); } } } if (replacementNodesWithChangedSemantics == null) { // No conflicts. return(inlinedDocument); } // Replace the conflicting inlined nodes with the original nodes annotated with conflict annotation. Func <SyntaxNode, SyntaxNode, SyntaxNode> conflictAnnotationAdder = (SyntaxNode oldNode, SyntaxNode newNode) => newNode.WithAdditionalAnnotations(ConflictAnnotation.Create(CSharpFeaturesResources.ConflictsDetected)); return(await inlinedDocument.ReplaceNodesAsync(replacementNodesWithChangedSemantics.Keys, conflictAnnotationAdder, cancellationToken).ConfigureAwait(false)); }
/// <summary> /// Adds to <paramref name="targetType"/> if it does not contain an anonymous /// type and binds to the same type at the given <paramref name="position"/>. /// </summary> public static ExpressionSyntax CastIfPossible( this ExpressionSyntax expression, ITypeSymbol targetType, int position, SemanticModel semanticModel, out bool wasCastAdded) { wasCastAdded = false; if (targetType.ContainsAnonymousType()) { return expression; } if (targetType.Kind == SymbolKind.DynamicType) { targetType = semanticModel.Compilation.GetSpecialType(SpecialType.System_Object); } var typeSyntax = targetType.GenerateTypeSyntax(); var type = semanticModel.GetSpeculativeTypeInfo( position, typeSyntax, SpeculativeBindingOption.BindAsTypeOrNamespace).Type; if (!targetType.Equals(type)) { return expression; } var castExpression = expression.Cast(targetType); // Ensure that inserting the cast doesn't change the semantics. var specAnalyzer = new SpeculationAnalyzer(expression, castExpression, semanticModel, CancellationToken.None); var speculativeSemanticModel = specAnalyzer.SpeculativeSemanticModel; if (speculativeSemanticModel == null) { return expression; } var speculatedCastExpression = (CastExpressionSyntax)specAnalyzer.ReplacedExpression; if (!speculatedCastExpression.IsUnnecessaryCast(speculativeSemanticModel, CancellationToken.None)) { return expression; } wasCastAdded = true; return castExpression; }
protected static bool ReplacementChangesSemantics(ExpressionSyntax originalExpression, ExpressionSyntax replacedExpression, SemanticModel semanticModel) { var speculationAnalyzer = new SpeculationAnalyzer(originalExpression, replacedExpression, semanticModel, CancellationToken.None); return speculationAnalyzer.ReplacementChangesSemantics(); }
internal override IMethodSymbol GetDelegatingConstructor( State state, SemanticDocument document, int argumentCount, INamedTypeSymbol namedType, ISet <IMethodSymbol> candidates, CancellationToken cancellationToken) { var oldToken = state.Token; var tokenKind = oldToken.Kind(); if (state.IsConstructorInitializerGeneration) { SyntaxToken thisOrBaseKeyword; SyntaxKind newCtorInitializerKind; if (tokenKind != SyntaxKind.BaseKeyword && state.TypeToGenerateIn == namedType) { thisOrBaseKeyword = SyntaxFactory.Token(SyntaxKind.ThisKeyword); newCtorInitializerKind = SyntaxKind.ThisConstructorInitializer; } else { thisOrBaseKeyword = SyntaxFactory.Token(SyntaxKind.BaseKeyword); newCtorInitializerKind = SyntaxKind.BaseConstructorInitializer; } var ctorInitializer = (ConstructorInitializerSyntax)oldToken.Parent; var oldArgumentList = ctorInitializer.ArgumentList; var newArgumentList = GetNewArgumentList(oldArgumentList, argumentCount); var newCtorInitializer = SyntaxFactory.ConstructorInitializer(newCtorInitializerKind, ctorInitializer.ColonToken, thisOrBaseKeyword, newArgumentList); SemanticModel speculativeModel; if (document.SemanticModel.TryGetSpeculativeSemanticModel(ctorInitializer.Span.Start, newCtorInitializer, out speculativeModel)) { var symbolInfo = speculativeModel.GetSymbolInfo(newCtorInitializer, cancellationToken); return(GenerateConstructorHelpers.GetDelegatingConstructor( document, symbolInfo, candidates, namedType, state.ParameterTypes)); } } else { var oldNode = oldToken.Parent .AncestorsAndSelf(ascendOutOfTrivia: false) .Where(node => SpeculationAnalyzer.CanSpeculateOnNode(node)) .LastOrDefault(); var typeNameToReplace = (TypeSyntax)oldToken.Parent; TypeSyntax newTypeName; if (namedType != state.TypeToGenerateIn) { while (true) { var parentType = typeNameToReplace.Parent as TypeSyntax; if (parentType == null) { break; } typeNameToReplace = parentType; } newTypeName = namedType.GenerateTypeSyntax().WithAdditionalAnnotations(s_annotation); } else { newTypeName = typeNameToReplace.WithAdditionalAnnotations(s_annotation); } var newNode = oldNode.ReplaceNode(typeNameToReplace, newTypeName); newTypeName = (TypeSyntax)newNode.GetAnnotatedNodes(s_annotation).Single(); var oldArgumentList = (ArgumentListSyntax)newTypeName.Parent.ChildNodes().FirstOrDefault(n => n is ArgumentListSyntax); if (oldArgumentList != null) { var newArgumentList = GetNewArgumentList(oldArgumentList, argumentCount); if (newArgumentList != oldArgumentList) { newNode = newNode.ReplaceNode(oldArgumentList, newArgumentList); newTypeName = (TypeSyntax)newNode.GetAnnotatedNodes(s_annotation).Single(); } } var speculativeModel = SpeculationAnalyzer.CreateSpeculativeSemanticModelForNode(oldNode, newNode, document.SemanticModel); if (speculativeModel != null) { var symbolInfo = speculativeModel.GetSymbolInfo(newTypeName.Parent, cancellationToken); return(GenerateConstructorHelpers.GetDelegatingConstructor( document, symbolInfo, candidates, namedType, state.ParameterTypes)); } } return(null); }
protected override async Task<Document> AddImportAsync( SyntaxNode contextNode, INamespaceOrTypeSymbol namespaceSymbol, Document document, bool placeSystemNamespaceFirst, CancellationToken cancellationToken) { var root = GetCompilationUnitSyntaxNode(contextNode, cancellationToken); var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var simpleUsingDirective = GetUsingDirective(root, namespaceSymbol, semanticModel, fullyQualify: false); var externAliasUsingDirective = GetExternAliasUsingDirective(root, namespaceSymbol, semanticModel); if (externAliasUsingDirective != null) { root = root.AddExterns( externAliasUsingDirective .WithAdditionalAnnotations(Formatter.Annotation)); } if (simpleUsingDirective != null) { // Because of the way usings can be nested inside of namespace declarations, // we need to check if the usings must be fully qualified so as not to be // ambiguous with the containing namespace. if (UsingsAreContainedInNamespace(contextNode)) { // When we add usings we try and place them, as best we can, where the user // wants them according to their settings. This means we can't just add the fully- // qualified usings and expect the simplifier to take care of it, the usings have to be // simplified before we attempt to add them to the document. // You might be tempted to think that we could call // AddUsings -> Simplifier -> SortUsings // But this will clobber the users using settings without asking. Instead we create a new // Document and check if our using can be simplified. Worst case we need to back out the // fully qualified change and reapply with the simple name. var fullyQualifiedUsingDirective = GetUsingDirective(root, namespaceSymbol, semanticModel, fullyQualify: true); SyntaxNode newRoot = root.AddUsingDirective( fullyQualifiedUsingDirective, contextNode, placeSystemNamespaceFirst, Formatter.Annotation); var newDocument = document.WithSyntaxRoot(newRoot); var newSemanticModel = await newDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); newRoot = await newDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var newUsing = newRoot .DescendantNodes().OfType<UsingDirectiveSyntax>().Where(uds => uds.IsEquivalentTo(fullyQualifiedUsingDirective, topLevel: true)).Single(); var speculationAnalyzer = new SpeculationAnalyzer(newUsing.Name, simpleUsingDirective.Name, newSemanticModel, cancellationToken); if (speculationAnalyzer.ReplacementChangesSemantics()) { // Not fully qualifying the using causes to refer to a different namespace so we need to keep it as is. return newDocument; } else { // It does not matter if it is fully qualified or simple so lets return the simple name. return document.WithSyntaxRoot(root.AddUsingDirective( simpleUsingDirective, contextNode, placeSystemNamespaceFirst, Formatter.Annotation)); } } else { // simple form return document.WithSyntaxRoot(root.AddUsingDirective( simpleUsingDirective, contextNode, placeSystemNamespaceFirst, Formatter.Annotation)); } } return document.WithSyntaxRoot(root); }