/// <summary> /// Most of the above optimizations are not applicable in expression trees as the operator /// must stay a binary operator. We cannot do much beyond constant folding which is done in binder. /// </summary> private BoundExpression RewriteStringConcatInExpressionLambda(SyntaxNode syntax, BinaryOperatorKind operatorKind, BoundExpression loweredLeft, BoundExpression loweredRight, TypeSymbol type) { SpecialMember member = (operatorKind == BinaryOperatorKind.StringConcatenation) ? SpecialMember.System_String__ConcatStringString : SpecialMember.System_String__ConcatObjectObject; var method = UnsafeGetSpecialTypeMethod(syntax, member); Debug.Assert((object)method != null); return(new BoundBinaryOperator(syntax, operatorKind, default(ConstantValue), method, default(LookupResultKind), loweredLeft, loweredRight, type)); }
protected BoundCall MakeQueryInvocation(CSharpSyntaxNode node, BoundExpression receiver, string methodName, ImmutableArray <BoundExpression> args, DiagnosticBag diagnostics) { return(MakeQueryInvocation(node, receiver, methodName, default(SeparatedSyntaxList <TypeSyntax>), default(ImmutableArray <TypeSymbolWithAnnotations>), args, diagnostics)); }
protected BoundCall MakeQueryInvocation(CSharpSyntaxNode node, BoundExpression receiver, string methodName, SeparatedSyntaxList <TypeSyntax> typeArgsSyntax, ImmutableArray <TypeSymbolWithAnnotations> typeArgs, ImmutableArray <BoundExpression> args, DiagnosticBag diagnostics) { // clean up the receiver var ultimateReceiver = receiver; while (ultimateReceiver.Kind == BoundKind.QueryClause) { ultimateReceiver = ((BoundQueryClause)ultimateReceiver).Value; } if ((object)ultimateReceiver.Type == null) { if (ultimateReceiver.HasAnyErrors || node.HasErrors) { // report no additional errors } else if (ultimateReceiver.IsLiteralNull()) { diagnostics.Add(ErrorCode.ERR_NullNotValid, node.Location); } else if (ultimateReceiver.IsLiteralDefault()) { diagnostics.Add(ErrorCode.ERR_DefaultLiteralNotValid, node.Location); } else if (ultimateReceiver.Kind == BoundKind.NamespaceExpression) { diagnostics.Add(ErrorCode.ERR_BadSKunknown, ultimateReceiver.Syntax.Location, ultimateReceiver.Syntax, MessageID.IDS_SK_NAMESPACE.Localize()); } else if (ultimateReceiver.Kind == BoundKind.Lambda || ultimateReceiver.Kind == BoundKind.UnboundLambda) { // Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found. diagnostics.Add(ErrorCode.ERR_QueryNoProvider, node.Location, MessageID.IDS_AnonMethod.Localize(), methodName); } else if (ultimateReceiver.Kind == BoundKind.MethodGroup) { var methodGroup = (BoundMethodGroup)ultimateReceiver; HashSet <DiagnosticInfo> useSiteDiagnostics = null; var resolution = this.ResolveMethodGroup(methodGroup, analyzedArguments: null, isMethodGroupConversion: false, useSiteDiagnostics: ref useSiteDiagnostics); diagnostics.Add(node, useSiteDiagnostics); diagnostics.AddRange(resolution.Diagnostics); if (resolution.HasAnyErrors) { receiver = this.BindMemberAccessBadResult(methodGroup); } else { Debug.Assert(!resolution.IsEmpty); diagnostics.Add(ErrorCode.ERR_QueryNoProvider, node.Location, MessageID.IDS_SK_METHOD.Localize(), methodName); } resolution.Free(); } receiver = new BoundBadExpression(receiver.Syntax, LookupResultKind.NotAValue, ImmutableArray <Symbol> .Empty, ImmutableArray.Create(receiver), CreateErrorType()); } else if (receiver.Type.SpecialType == SpecialType.System_Void) { if (!receiver.HasAnyErrors && !node.HasErrors) { diagnostics.Add(ErrorCode.ERR_QueryNoProvider, node.Location, "void", methodName); } receiver = new BoundBadExpression(receiver.Syntax, LookupResultKind.NotAValue, ImmutableArray <Symbol> .Empty, ImmutableArray.Create(receiver), CreateErrorType()); } return((BoundCall)MakeInvocationExpression( node, receiver, methodName, args, diagnostics, typeArgsSyntax, typeArgs, queryClause: node, // Queries are syntactical rewrites, so we allow fields and properties of delegate types to be invoked, // although no well-known non-generic query method is used atm. allowFieldsAndProperties: true)); }
private void ReduceFrom(FromClauseSyntax from, QueryTranslationState state, DiagnosticBag diagnostics) { var x1 = state.rangeVariable; BoundExpression collectionSelectorLambda; if (from.Type == null) { collectionSelectorLambda = MakeQueryUnboundLambda(state.RangeVariableMap(), x1, from.Expression); } else { collectionSelectorLambda = MakeQueryUnboundLambdaWithCast(state.RangeVariableMap(), x1, from.Expression, from.Type, BindTypeArgument(from.Type, diagnostics)); } var x2 = state.AddRangeVariable(this, from.Identifier, diagnostics); if (state.clauses.IsEmpty() && state.selectOrGroup.IsKind(SyntaxKind.SelectClause)) { var select = (SelectClauseSyntax)state.selectOrGroup; // A query expression with a second from clause followed by a select clause // from x1 in e1 // from x2 in e2 // select v // is translated into // ( e1 ) . SelectMany( x1 => e2 , ( x1 , x2 ) => v ) var resultSelectorLambda = MakeQueryUnboundLambda(state.RangeVariableMap(), ImmutableArray.Create(x1, x2), select.Expression); var invocation = MakeQueryInvocation( from, state.fromExpression, "SelectMany", ImmutableArray.Create(collectionSelectorLambda, resultSelectorLambda), diagnostics); // Adjust the second-to-last parameter to be a query clause (if it was an extension method, an extra parameter was added) BoundExpression castInvocation = (from.Type != null) ? ExtractCastInvocation(invocation) : null; var arguments = invocation.Arguments; invocation = invocation.Update( invocation.ReceiverOpt, invocation.Method, arguments.SetItem(arguments.Length - 2, MakeQueryClause(from, arguments[arguments.Length - 2], x2, invocation, castInvocation))); state.Clear(); state.fromExpression = MakeQueryClause(from, invocation, definedSymbol: x2, queryInvocation: invocation); state.fromExpression = MakeQueryClause(select, state.fromExpression); } else { // A query expression with a second from clause followed by something other than a select clause: // from x1 in e1 // from x2 in e2 // ... // is translated into // from * in ( e1 ) . SelectMany( x1 => e2 , ( x1 , x2 ) => new { x1 , x2 } ) // ... // We use a slightly different translation strategy. We produce // from * in ( e ) . SelectMany ( x1 => e2, ( x1 , x2 ) => new Pair<X1,X2>(x1, x2) ) // Where X1 is the type of x1, and X2 is the type of x2. // Subsequently, x1 (or members of x1, if it is a transparent identifier) // are accessed as TRID.Item1 (or members of that), and x2 is accessed // as TRID.Item2, where TRID is the compiler-generated identifier used // to represent the transparent identifier in the result. var resultSelectorLambda = MakePairLambda(from, state, x1, x2); var invocation = MakeQueryInvocation( from, state.fromExpression, "SelectMany", ImmutableArray.Create(collectionSelectorLambda, resultSelectorLambda), diagnostics); BoundExpression castInvocation = (from.Type != null) ? ExtractCastInvocation(invocation) : null; state.fromExpression = MakeQueryClause(from, invocation, x2, invocation, castInvocation); } }
private BoundExpression MakePair(CSharpSyntaxNode node, string field1Name, BoundExpression field1Value, string field2Name, BoundExpression field2Value, QueryTranslationState state, DiagnosticBag diagnostics) { if (field1Name == field2Name) { // we will generate a diagnostic elsewhere field2Name = state.TransparentRangeVariableName(); field2Value = new BoundBadExpression(field2Value.Syntax, LookupResultKind.Empty, ImmutableArray <Symbol> .Empty, ImmutableArray.Create(field2Value), field2Value.Type, true); } AnonymousTypeDescriptor typeDescriptor = new AnonymousTypeDescriptor( ImmutableArray.Create <AnonymousTypeField>( new AnonymousTypeField(field1Name, field1Value.Syntax.Location, TypeSymbolWithAnnotations.Create(TypeOrError(field1Value))), new AnonymousTypeField(field2Name, field2Value.Syntax.Location, TypeSymbolWithAnnotations.Create(TypeOrError(field2Value))) ), node.Location ); AnonymousTypeManager manager = this.Compilation.AnonymousTypeManager; NamedTypeSymbol anonymousType = manager.ConstructAnonymousTypeSymbol(typeDescriptor); return(MakeConstruction(node, anonymousType, ImmutableArray.Create(field1Value, field2Value), diagnostics)); }
public override BoundExpression InstrumentSwitchStatementExpression(BoundStatement original, BoundExpression rewrittenExpression, SyntheticBoundNodeFactory factory) { return(Previous.InstrumentSwitchStatementExpression(original, rewrittenExpression, factory)); }
private BoundExpression FinalTranslation(QueryTranslationState state, DiagnosticBag diagnostics) { Debug.Assert(state.clauses.IsEmpty()); switch (state.selectOrGroup.Kind()) { case SyntaxKind.SelectClause: { // A query expression of the form // from x in e select v // is translated into // ( e ) . Select ( x => v ) var selectClause = (SelectClauseSyntax)state.selectOrGroup; var x = state.rangeVariable; var e = state.fromExpression; var v = selectClause.Expression; var lambda = MakeQueryUnboundLambda(state.RangeVariableMap(), x, v); var result = MakeQueryInvocation(state.selectOrGroup, e, "Select", lambda, diagnostics); return(MakeQueryClause(selectClause, result, queryInvocation: result)); } case SyntaxKind.GroupClause: { // A query expression of the form // from x in e group v by k // is translated into // ( e ) . GroupBy ( x => k , x => v ) // except when v is the identifier x, the translation is // ( e ) . GroupBy ( x => k ) var groupClause = (GroupClauseSyntax)state.selectOrGroup; var x = state.rangeVariable; var e = state.fromExpression; var v = groupClause.GroupExpression; var k = groupClause.ByExpression; var vId = v as IdentifierNameSyntax; BoundCall result; var lambdaLeft = MakeQueryUnboundLambda(state.RangeVariableMap(), x, k); // this is the unoptimized form (when v is not the identifier x) var d = DiagnosticBag.GetInstance(); BoundExpression lambdaRight = MakeQueryUnboundLambda(state.RangeVariableMap(), x, v); result = MakeQueryInvocation(state.selectOrGroup, e, "GroupBy", ImmutableArray.Create(lambdaLeft, lambdaRight), d); // k and v appear reversed in the invocation, so we reorder their evaluation result = ReverseLastTwoParameterOrder(result); BoundExpression unoptimizedForm = null; if (vId != null && vId.Identifier.ValueText == x.Name) { // The optimized form. We store the unoptimized form for analysis unoptimizedForm = result; result = MakeQueryInvocation(state.selectOrGroup, e, "GroupBy", lambdaLeft, diagnostics); if (unoptimizedForm.HasAnyErrors && !result.HasAnyErrors) { unoptimizedForm = null; } } else { diagnostics.AddRange(d); } d.Free(); return(MakeQueryClause(groupClause, result, queryInvocation: result, unoptimizedForm: unoptimizedForm)); } default: { // there should have been a syntax error if we get here. return(new BoundBadExpression( state.selectOrGroup, LookupResultKind.OverloadResolutionFailure, ImmutableArray <Symbol> .Empty, ImmutableArray.Create(state.fromExpression), state.fromExpression.Type)); } } }
private BoundDeconstructionAssignmentOperator BindDeconstructionAssignment( CSharpSyntaxNode node, ExpressionSyntax left, BoundExpression boundRHS, ArrayBuilder <DeconstructionVariable> checkedVariables, bool resultIsUsed, DiagnosticBag diagnostics) { uint rightEscape = GetValEscape(boundRHS, this.LocalScopeDepth); if ((object)boundRHS.Type == null || boundRHS.Type.IsErrorType()) { // we could still not infer a type for the RHS FailRemainingInferencesAndSetValEscape(checkedVariables, diagnostics, rightEscape); var voidType = GetSpecialType(SpecialType.System_Void, diagnostics, node); var type = boundRHS.Type ?? voidType; return(new BoundDeconstructionAssignmentOperator( node, DeconstructionVariablesAsTuple(left, checkedVariables, diagnostics, ignoreDiagnosticsFromTuple: true), new BoundConversion(boundRHS.Syntax, boundRHS, Conversion.Deconstruction, @checked: false, explicitCastInCode: false, conversionGroupOpt: null, constantValueOpt: null, type: type, hasErrors: true), resultIsUsed, voidType, hasErrors: true)); } Conversion conversion; bool hasErrors = !MakeDeconstructionConversion( boundRHS.Type, node, boundRHS.Syntax, diagnostics, checkedVariables, out conversion); FailRemainingInferencesAndSetValEscape(checkedVariables, diagnostics, rightEscape); var lhsTuple = DeconstructionVariablesAsTuple(left, checkedVariables, diagnostics, ignoreDiagnosticsFromTuple: diagnostics.HasAnyErrors() || !resultIsUsed); TypeSymbol returnType = hasErrors ? CreateErrorType() : lhsTuple.Type; uint leftEscape = GetBroadestValEscape(lhsTuple, this.LocalScopeDepth); boundRHS = ValidateEscape(boundRHS, leftEscape, isByRef: false, diagnostics: diagnostics); var boundConversion = new BoundConversion( boundRHS.Syntax, boundRHS, conversion, @checked: false, explicitCastInCode: false, conversionGroupOpt: null, constantValueOpt: null, type: returnType, hasErrors: hasErrors) { WasCompilerGenerated = true }; return(new BoundDeconstructionAssignmentOperator(node, lhsTuple, boundConversion, resultIsUsed, returnType)); }
/// <summary>When boundRHS is a tuple literal, fix it up by inferring its types.</summary> private BoundExpression FixTupleLiteral(ArrayBuilder <DeconstructionVariable> checkedVariables, BoundExpression boundRHS, CSharpSyntaxNode syntax, DiagnosticBag diagnostics) { if (boundRHS.Kind == BoundKind.TupleLiteral) { // Let's fix the literal up by figuring out its type // For declarations, that means merging type information from the LHS and RHS // For assignments, only the LHS side matters since it is necessarily typed // If we already have diagnostics at this point, it is not worth collecting likely duplicate diagnostics from making the merged type bool hadErrors = diagnostics.HasAnyErrors(); TypeSymbol mergedTupleType = MakeMergedTupleType(checkedVariables, (BoundTupleLiteral)boundRHS, syntax, Compilation, hadErrors ? null : diagnostics); if ((object)mergedTupleType != null) { boundRHS = GenerateConversionForAssignment(mergedTupleType, boundRHS, diagnostics); } } else if ((object)boundRHS.Type == null) { Error(diagnostics, ErrorCode.ERR_DeconstructRequiresExpression, boundRHS.Syntax); } return(boundRHS); }
private BoundStatement RewriteForStatementWithoutInnerLocals( BoundLoopStatement original, ImmutableArray <LocalSymbol> outerLocals, BoundStatement rewrittenInitializer, BoundExpression rewrittenCondition, BoundStatement rewrittenIncrement, BoundStatement rewrittenBody, BoundStatement afterBreak, GeneratedLabelSymbol breakLabel, GeneratedLabelSymbol continueLabel, bool hasErrors) { Debug.Assert(original.Kind == BoundKind.ForStatement || original.Kind == BoundKind.ForEachStatement); Debug.Assert(rewrittenBody != null); // The sequence point behavior exhibited here is different from that of the native compiler. In the native // compiler, if you have something like // // for([|int i = 0, j = 0|]; ; [|i++, j++|]) // // then all the initializers are treated as a single sequence point, as are // all the loop incrementors. // // We now make each one individually a sequence point: // // for([|int i = 0|], [|j = 0|]; ; [|i++|], [|j++|]) // // If we decide that we want to preserve the native compiler stepping behavior // then we'll need to be a bit fancy here. The initializer and increment statements // can contain lambdas whose bodies need to have sequence points inserted, so we // need to make sure we visit the children. But we'll also need to make sure that // we do not generate one sequence point for each statement in the initializers // and the incrementors. SyntaxNode syntax = original.Syntax; var statementBuilder = ArrayBuilder <BoundStatement> .GetInstance(); if (rewrittenInitializer != null) { statementBuilder.Add(rewrittenInitializer); } var startLabel = new GeneratedLabelSymbol("start"); // for (initializer; condition; increment) // body; // // becomes the following (with block added for locals) // // { // initializer; // goto end; // start: // body; // continue: // increment; // end: // GotoIfTrue condition start; // break: // } var endLabel = new GeneratedLabelSymbol("end"); // initializer; // goto end; BoundStatement gotoEnd = new BoundGotoStatement(syntax, endLabel); if (this.Instrument) { // Mark the initial jump as hidden. // We do it to tell that this is not a part of previous statement. // This jump may be a target of another jump (for example if loops are nested) and that will make // impression of the previous statement being re-executed gotoEnd = new BoundSequencePoint(null, gotoEnd); } statementBuilder.Add(gotoEnd); // start: // body; statementBuilder.Add(new BoundLabelStatement(syntax, startLabel)); statementBuilder.Add(rewrittenBody); // continue: // increment; statementBuilder.Add(new BoundLabelStatement(syntax, continueLabel)); if (rewrittenIncrement != null) { statementBuilder.Add(rewrittenIncrement); } // end: // GotoIfTrue condition start; statementBuilder.Add(new BoundLabelStatement(syntax, endLabel)); BoundStatement branchBack = null; if (rewrittenCondition != null) { branchBack = new BoundConditionalGoto(rewrittenCondition.Syntax, rewrittenCondition, true, startLabel); } else { branchBack = new BoundGotoStatement(syntax, startLabel); } if (this.Instrument) { switch (original.Kind) { case BoundKind.ForEachStatement: branchBack = _instrumenter.InstrumentForEachStatementConditionalGotoStart((BoundForEachStatement)original, branchBack); break; default: throw ExceptionUtilities.UnexpectedValue(original.Kind); } } statementBuilder.Add(branchBack); // break: statementBuilder.Add(new BoundLabelStatement(syntax, breakLabel)); if (afterBreak != null) { statementBuilder.Add(afterBreak); } var statements = statementBuilder.ToImmutableAndFree(); return(new BoundBlock(syntax, outerLocals, statements, hasErrors)); }
private BoundStatement RewriteForStatement( BoundForStatement node, BoundStatement rewrittenInitializer, BoundExpression rewrittenCondition, BoundStatement rewrittenIncrement, BoundStatement rewrittenBody) { if (node.InnerLocals.IsEmpty) { return(RewriteForStatementWithoutInnerLocals( node, node.OuterLocals, rewrittenInitializer, rewrittenCondition, rewrittenIncrement, rewrittenBody, null, node.BreakLabel, node.ContinueLabel, node.HasErrors)); } // We need to enter inner_scope-block from the top, that is where an instance of a display class will be created // if any local is captured within a lambda. // for (initializer; condition; increment) // body; // // becomes the following (with block added for locals) // // { // initializer; // start: // { // GotoIfFalse condition break; // body; // continue: // increment; // goto start; // } // break: // } Debug.Assert(rewrittenBody != null); SyntaxNode syntax = node.Syntax; var statementBuilder = ArrayBuilder <BoundStatement> .GetInstance(); // initializer; if (rewrittenInitializer != null) { statementBuilder.Add(rewrittenInitializer); } var startLabel = new GeneratedLabelSymbol("start"); // start: BoundStatement startLabelStatement = new BoundLabelStatement(syntax, startLabel); if (Instrument) { startLabelStatement = new BoundSequencePoint(null, startLabelStatement); } statementBuilder.Add(startLabelStatement); var blockBuilder = ArrayBuilder <BoundStatement> .GetInstance(); // GotoIfFalse condition break; if (rewrittenCondition != null) { BoundStatement ifNotConditionGotoBreak = new BoundConditionalGoto(rewrittenCondition.Syntax, rewrittenCondition, false, node.BreakLabel); blockBuilder.Add(ifNotConditionGotoBreak); } // body; blockBuilder.Add(rewrittenBody); // continue: // increment; blockBuilder.Add(new BoundLabelStatement(syntax, node.ContinueLabel)); if (rewrittenIncrement != null) { blockBuilder.Add(rewrittenIncrement); } // goto start; blockBuilder.Add(new BoundGotoStatement(syntax, startLabel)); statementBuilder.Add(new BoundBlock(syntax, node.InnerLocals, blockBuilder.ToImmutableAndFree())); // break: statementBuilder.Add(new BoundLabelStatement(syntax, node.BreakLabel)); var statements = statementBuilder.ToImmutableAndFree(); return(new BoundBlock(syntax, node.OuterLocals, statements, node.HasErrors)); }
public ExpressionAndDiagnostics(BoundExpression expression, ImmutableArray <Diagnostic> diagnostics) { this.Expression = expression; this.Diagnostics = diagnostics; }
/// <summary> /// Checks whether the expression represents a boxing conversion of a special value type. /// If it does, it tries to return a string-based representation instead in order /// to avoid allocations. If it can't, the original expression is returned. /// </summary> private BoundExpression ConvertConcatExprToStringIfPossible(SyntaxNode syntax, BoundExpression expr) { if (expr.Kind == BoundKind.Conversion) { BoundConversion conv = (BoundConversion)expr; if (conv.ConversionKind == ConversionKind.Boxing) { BoundExpression operand = conv.Operand; if (operand != null) { // Is the expression a literal char? If so, we can // simply make it a literal string instead and avoid any // allocations for converting the char to a string at run time. if (operand.Kind == BoundKind.Literal) { ConstantValue cv = ((BoundLiteral)operand).ConstantValue; if (cv != null && cv.SpecialType == SpecialType.System_Char) { return(_factory.StringLiteral(cv.CharValue.ToString())); } } // Can the expression be optimized with a ToString call? // If so, we can synthesize a ToString call to avoid boxing. if (ConcatExprCanBeOptimizedWithToString(operand.Type)) { var toString = UnsafeGetSpecialTypeMethod(syntax, SpecialMember.System_Object__ToString); var type = (NamedTypeSymbol)operand.Type; var toStringMembers = type.GetMembers(toString.Name); foreach (var member in toStringMembers) { var toStringMethod = member as MethodSymbol; if (toStringMethod.GetLeastOverriddenMethod(type) == (object)toString) { return(BoundCall.Synthesized(syntax, operand, toStringMethod)); } } } } } } // Optimization not possible; just return the original expression. return(expr); }
/// <summary> /// The strategy of this rewrite is to do rewrite "locally". /// We analyze arguments of the concat in a shallow fashion assuming that /// lowering and optimizations (including this one) is already done for the arguments. /// Based on the arguments we select the most appropriate pattern for the current node. /// /// NOTE: it is not guaranteed that the node that we chose will be the most optimal since we have only /// local information - i.e. we look at the arguments, but we do not know about siblings. /// When we move to the parent, the node may be rewritten by this or some another optimization. /// /// Example: /// result = ( "abc" + "def" + null ?? expr1 + "moo" + "baz" ) + expr2 /// /// Will rewrite into: /// result = Concat("abcdef", expr2) /// /// However there will be transient nodes like Concat(expr1 + "moo") that will not be present in the /// resulting tree. /// /// </summary> private BoundExpression RewriteStringConcatenation(SyntaxNode syntax, BinaryOperatorKind operatorKind, BoundExpression loweredLeft, BoundExpression loweredRight, TypeSymbol type) { Debug.Assert( operatorKind == BinaryOperatorKind.StringConcatenation || operatorKind == BinaryOperatorKind.StringAndObjectConcatenation || operatorKind == BinaryOperatorKind.ObjectAndStringConcatenation); if (_inExpressionLambda) { return(RewriteStringConcatInExpressionLambda(syntax, operatorKind, loweredLeft, loweredRight, type)); } // avoid run time boxing and ToString operations if we can reasonably convert to a string at compile time loweredLeft = ConvertConcatExprToStringIfPossible(syntax, loweredLeft); loweredRight = ConvertConcatExprToStringIfPossible(syntax, loweredRight); // try fold two args without flattening. var folded = TryFoldTwoConcatOperands(syntax, loweredLeft, loweredRight); if (folded != null) { return(folded); } // flatten and merge - ( expr1 + "A" ) + ("B" + expr2) ===> (expr1 + "AB" + expr2) ArrayBuilder <BoundExpression> leftFlattened = ArrayBuilder <BoundExpression> .GetInstance(); ArrayBuilder <BoundExpression> rightFlattened = ArrayBuilder <BoundExpression> .GetInstance(); FlattenConcatArg(loweredLeft, leftFlattened); FlattenConcatArg(loweredRight, rightFlattened); if (leftFlattened.Any() && rightFlattened.Any()) { folded = TryFoldTwoConcatOperands(syntax, leftFlattened.Last(), rightFlattened.First()); if (folded != null) { rightFlattened[0] = folded; leftFlattened.RemoveLast(); } } leftFlattened.AddRange(rightFlattened); rightFlattened.Free(); BoundExpression result; switch (leftFlattened.Count) { case 0: result = _factory.StringLiteral(string.Empty); break; case 1: result = leftFlattened[0]; break; case 2: var left = leftFlattened[0]; var right = leftFlattened[1]; result = RewriteStringConcatenationTwoExprs(syntax, left, right); break; case 3: var first = leftFlattened[0]; var second = leftFlattened[1]; var third = leftFlattened[2]; result = RewriteStringConcatenationThreeExprs(syntax, first, second, third); break; default: result = RewriteStringConcatenationManyExprs(syntax, leftFlattened.ToImmutable()); break; } leftFlattened.Free(); return(result); }
public override BoundStatement InstrumentSwitchWhenClauseConditionalGotoBody(BoundExpression original, BoundStatement ifConditionGotoBody) { return(Previous.InstrumentSwitchWhenClauseConditionalGotoBody(original, ifConditionGotoBody)); }
/// <summary> /// Recursively builds a Conversion object with Kind=Deconstruction including information about any necessary /// Deconstruct method and any element-wise conversion. /// /// Note that the variables may either be plain or nested variables. /// The variables may be updated with inferred types if they didn't have types initially. /// Returns false if there was an error. /// </summary> private bool MakeDeconstructionConversion( TypeSymbol type, SyntaxNode syntax, SyntaxNode rightSyntax, DiagnosticBag diagnostics, ArrayBuilder <DeconstructionVariable> variables, out Conversion conversion) { Debug.Assert((object)type != null); ImmutableArray <TypeSymbol> tupleOrDeconstructedTypes; conversion = Conversion.Deconstruction; // Figure out the deconstruct method (if one is required) and determine the types we get from the RHS at this level var deconstructMethod = default(DeconstructMethodInfo); if (type.IsTupleType) { // tuple literal such as `(1, 2)`, `(null, null)`, `(x.P, y.M())` tupleOrDeconstructedTypes = type.TupleElementTypes.SelectAsArray(TypeMap.AsTypeSymbol); SetInferredTypes(variables, tupleOrDeconstructedTypes, diagnostics); if (variables.Count != tupleOrDeconstructedTypes.Length) { Error(diagnostics, ErrorCode.ERR_DeconstructWrongCardinality, syntax, tupleOrDeconstructedTypes.Length, variables.Count); return(false); } } else { if (variables.Count < 2) { Error(diagnostics, ErrorCode.ERR_DeconstructTooFewElements, syntax); return(false); } var inputPlaceholder = new BoundDeconstructValuePlaceholder(syntax, this.LocalScopeDepth, type); BoundExpression deconstructInvocation = MakeDeconstructInvocationExpression(variables.Count, inputPlaceholder, rightSyntax, diagnostics, outPlaceholders: out ImmutableArray <BoundDeconstructValuePlaceholder> outPlaceholders, out _); if (deconstructInvocation.HasAnyErrors) { return(false); } deconstructMethod = new DeconstructMethodInfo(deconstructInvocation, inputPlaceholder, outPlaceholders); tupleOrDeconstructedTypes = outPlaceholders.SelectAsArray(p => p.Type); SetInferredTypes(variables, tupleOrDeconstructedTypes, diagnostics); } // Figure out whether those types will need conversions, including further deconstructions bool hasErrors = false; int count = variables.Count; var nestedConversions = ArrayBuilder <Conversion> .GetInstance(count); for (int i = 0; i < count; i++) { var variable = variables[i]; Conversion nestedConversion; if (variable.HasNestedVariables) { var elementSyntax = syntax.Kind() == SyntaxKind.TupleExpression ? ((TupleExpressionSyntax)syntax).Arguments[i] : syntax; hasErrors |= !MakeDeconstructionConversion(tupleOrDeconstructedTypes[i], syntax, rightSyntax, diagnostics, variable.NestedVariables, out nestedConversion); } else { var single = variable.Single; HashSet <DiagnosticInfo> useSiteDiagnostics = null; nestedConversion = this.Conversions.ClassifyConversionFromType(tupleOrDeconstructedTypes[i], single.Type, ref useSiteDiagnostics); diagnostics.Add(single.Syntax, useSiteDiagnostics); if (!nestedConversion.IsImplicit) { hasErrors = true; GenerateImplicitConversionError(diagnostics, Compilation, single.Syntax, nestedConversion, tupleOrDeconstructedTypes[i], single.Type); } } nestedConversions.Add(nestedConversion); } conversion = new Conversion(ConversionKind.Deconstruction, deconstructMethod, nestedConversions.ToImmutableAndFree()); return(!hasErrors); }
public override BoundExpression InstrumentCatchClauseFilter(BoundCatchBlock original, BoundExpression rewrittenFilter, SyntheticBoundNodeFactory factory) { return(Previous.InstrumentCatchClauseFilter(original, rewrittenFilter, factory)); }
internal DeconstructionVariable(BoundExpression variable, SyntaxNode syntax) { Single = variable; NestedVariables = null; Syntax = (CSharpSyntaxNode)syntax; }
public override BoundExpression InstrumentDoStatementCondition(BoundDoStatement original, BoundExpression rewrittenCondition, SyntheticBoundNodeFactory factory) { return(Previous.InstrumentDoStatementCondition(original, rewrittenCondition, factory)); }
internal DeconstructionVariable(ArrayBuilder <DeconstructionVariable> variables, SyntaxNode syntax) { Single = null; NestedVariables = variables; Syntax = (CSharpSyntaxNode)syntax; }
private void ReduceJoin(JoinClauseSyntax join, QueryTranslationState state, DiagnosticBag diagnostics) { var inExpression = BindValue(join.InExpression, diagnostics, BindValueKind.RValue); BoundExpression castInvocation = null; if (join.Type != null) { // A join clause that explicitly specifies a range variable type // join T x in e on k1 equals k2 // is translated into // join x in ( e ) . Cast < T > ( ) on k1 equals k2 var castType = BindTypeArgument(join.Type, diagnostics); castInvocation = MakeQueryInvocation(join, inExpression, "Cast", join.Type, castType, diagnostics); inExpression = castInvocation; } var outerKeySelectorLambda = MakeQueryUnboundLambda(state.RangeVariableMap(), state.rangeVariable, join.LeftExpression); var x1 = state.rangeVariable; var x2 = state.AddRangeVariable(this, join.Identifier, diagnostics); var innerKeySelectorLambda = MakeQueryUnboundLambda(QueryTranslationState.RangeVariableMap(x2), x2, join.RightExpression); if (state.clauses.IsEmpty() && state.selectOrGroup.Kind() == SyntaxKind.SelectClause) { var select = (SelectClauseSyntax)state.selectOrGroup; BoundCall invocation; if (join.Into == null) { // A query expression with a join clause without an into followed by a select clause // from x1 in e1 // join x2 in e2 on k1 equals k2 // select v // is translated into // ( e1 ) . Join( e2 , x1 => k1 , x2 => k2 , ( x1 , x2 ) => v ) var resultSelectorLambda = MakeQueryUnboundLambda(state.RangeVariableMap(), ImmutableArray.Create(x1, x2), select.Expression); invocation = MakeQueryInvocation( join, state.fromExpression, "Join", ImmutableArray.Create(inExpression, outerKeySelectorLambda, innerKeySelectorLambda, resultSelectorLambda), diagnostics); } else { // A query expression with a join clause with an into followed by a select clause // from x1 in e1 // join x2 in e2 on k1 equals k2 into g // select v // is translated into // ( e1 ) . GroupJoin( e2 , x1 => k1 , x2 => k2 , ( x1 , g ) => v ) state.allRangeVariables[x2].Free(); state.allRangeVariables.Remove(x2); var g = state.AddRangeVariable(this, join.Into.Identifier, diagnostics); var resultSelectorLambda = MakeQueryUnboundLambda(state.RangeVariableMap(), ImmutableArray.Create(x1, g), select.Expression); invocation = MakeQueryInvocation( join, state.fromExpression, "GroupJoin", ImmutableArray.Create(inExpression, outerKeySelectorLambda, innerKeySelectorLambda, resultSelectorLambda), diagnostics); // record the into clause in the bound tree var arguments = invocation.Arguments; arguments = arguments.SetItem(arguments.Length - 1, MakeQueryClause(join.Into, arguments[arguments.Length - 1], g)); invocation = invocation.Update(invocation.ReceiverOpt, invocation.Method, arguments); } state.Clear(); // this completes the whole query state.fromExpression = MakeQueryClause(join, invocation, x2, invocation, castInvocation); state.fromExpression = MakeQueryClause(select, state.fromExpression); } else { BoundCall invocation; if (join.Into == null) { // A query expression with a join clause without an into followed by something other than a select clause // from x1 in e1 // join x2 in e2 on k1 equals k2 // ... // is translated into // from * in ( e1 ) . Join( // e2 , x1 => k1 , x2 => k2 , ( x1 , x2 ) => new { x1 , x2 }) // ... var resultSelectorLambda = MakePairLambda(join, state, x1, x2); invocation = MakeQueryInvocation( join, state.fromExpression, "Join", ImmutableArray.Create(inExpression, outerKeySelectorLambda, innerKeySelectorLambda, resultSelectorLambda), diagnostics); } else { // A query expression with a join clause with an into followed by something other than a select clause // from x1 in e1 // join x2 in e2 on k1 equals k2 into g // ... // is translated into // from * in ( e1 ) . GroupJoin( // e2 , x1 => k1 , x2 => k2 , ( x1 , g ) => new { x1 , g }) // ... state.allRangeVariables[x2].Free(); state.allRangeVariables.Remove(x2); var g = state.AddRangeVariable(this, join.Into.Identifier, diagnostics); var resultSelectorLambda = MakePairLambda(join, state, x1, g); invocation = MakeQueryInvocation( join, state.fromExpression, "GroupJoin", ImmutableArray.Create(inExpression, outerKeySelectorLambda, innerKeySelectorLambda, resultSelectorLambda), diagnostics); var arguments = invocation.Arguments; arguments = arguments.SetItem(arguments.Length - 1, MakeQueryClause(join.Into, arguments[arguments.Length - 1], g)); invocation = invocation.Update(invocation.ReceiverOpt, invocation.Method, arguments); } state.fromExpression = MakeQueryClause(join, invocation, x2, invocation, castInvocation); } }
/// <summary> /// For cases where the RHS of a deconstruction-declaration is a tuple literal, we merge type information from both the LHS and RHS. /// For cases where the RHS of a deconstruction-assignment is a tuple literal, the type information from the LHS determines the merged type, since all variables have a type. /// Returns null if a merged tuple type could not be fabricated. /// </summary> private static TypeSymbol MakeMergedTupleType(ArrayBuilder <DeconstructionVariable> lhsVariables, BoundTupleLiteral rhsLiteral, CSharpSyntaxNode syntax, CSharpCompilation compilation, DiagnosticBag diagnostics) { int leftLength = lhsVariables.Count; int rightLength = rhsLiteral.Arguments.Length; var typesBuilder = ArrayBuilder <TypeSymbolWithAnnotations> .GetInstance(leftLength); var locationsBuilder = ArrayBuilder <Location> .GetInstance(leftLength); for (int i = 0; i < rightLength; i++) { BoundExpression element = rhsLiteral.Arguments[i]; TypeSymbol mergedType = element.Type; if (i < leftLength) { var variable = lhsVariables[i]; if (variable.HasNestedVariables) { if (element.Kind == BoundKind.TupleLiteral) { // (variables) on the left and (elements) on the right mergedType = MakeMergedTupleType(variable.NestedVariables, (BoundTupleLiteral)element, syntax, compilation, diagnostics); } else if ((object)mergedType == null) { // (variables) on the left and null on the right Error(diagnostics, ErrorCode.ERR_DeconstructRequiresExpression, element.Syntax); } } else { if ((object)variable.Single.Type != null) { // typed-variable on the left mergedType = variable.Single.Type; } } } else { if ((object)mergedType == null) { // a typeless element on the right, matching no variable on the left Error(diagnostics, ErrorCode.ERR_DeconstructRequiresExpression, element.Syntax); } } typesBuilder.Add(TypeSymbolWithAnnotations.Create(mergedType)); locationsBuilder.Add(element.Syntax.Location); } if (typesBuilder.Any(t => t.IsNull)) { typesBuilder.Free(); locationsBuilder.Free(); return(null); } // The tuple created here is not identical to the one created by // DeconstructionVariablesAsTuple. It represents a smaller // tree of types used for figuring out natural types in tuple literal. return(TupleTypeSymbol.Create( locationOpt: null, elementTypes: typesBuilder.ToImmutableAndFree(), elementLocations: locationsBuilder.ToImmutableAndFree(), elementNames: default(ImmutableArray <string>), compilation: compilation, diagnostics: diagnostics, shouldCheckConstraints: true, errorPositions: default(ImmutableArray <bool>), syntax: syntax)); }
private BoundBlock CreateLambdaBlockForQueryClause(ExpressionSyntax expression, BoundExpression result, DiagnosticBag diagnostics) { var locals = this.GetDeclaredLocalsForScope(expression); if (locals.Any()) { CheckFeatureAvailability(expression, MessageID.IDS_FeatureExpressionVariablesInQueriesAndInitializers, diagnostics, locals[0].Locations[0]); } return(this.CreateBlockFromExpression(expression, locals, RefKind.None, result, expression, diagnostics)); }
/// <summary> /// The left represents a tree of L-values. The structure of right can be missing parts of the tree on the left. /// The conversion holds nested conversions and deconstruction information, which matches the tree from the left, /// and it provides the information to fill in the missing parts of the tree from the right and convert it to /// the tree from the left. /// /// A bound sequence is returned which has different phases of side-effects: /// - the initialization phase includes side-effects from the left, followed by evaluations of the right /// - the deconstruction phase includes all the invocations of Deconstruct methods and tuple element accesses below a Deconstruct call /// - the conversion phase /// - the assignment phase /// </summary> private BoundExpression RewriteDeconstruction(BoundTupleExpression left, Conversion conversion, BoundExpression right, bool isUsed) { var lhsTemps = ArrayBuilder <LocalSymbol> .GetInstance(); var lhsEffects = ArrayBuilder <BoundExpression> .GetInstance(); ArrayBuilder <Binder.DeconstructionVariable> lhsTargets = GetAssignmentTargetsAndSideEffects(left, lhsTemps, lhsEffects); BoundExpression result = RewriteDeconstruction(lhsTargets, conversion, left.Type, right, isUsed); Binder.DeconstructionVariable.FreeDeconstructionVariables(lhsTargets); if (result is null) { lhsTemps.Free(); lhsEffects.Free(); return(null); } return(_factory.Sequence(lhsTemps.ToImmutableAndFree(), lhsEffects.ToImmutableAndFree(), result)); }
private TypeSymbol TypeOrError(BoundExpression e) { return(e.Type ?? CreateErrorType()); }
/// <summary> /// This method find the set of applicable user-defined and lifted conversion operators, u. /// The set consists of the user-defined and lifted implicit conversion operators declared by /// the classes and structs in d that convert from a type encompassing source to a type encompassed by target. /// However if allowAnyTarget is true, then it considers all operators that convert from a type encompassing source /// to any target. This flag must be set only if we are computing user defined conversions from a given source /// type to any target type. /// </summary> /// <remarks> /// Currently allowAnyTarget flag is only set to true by <see cref="AnalyzeImplicitUserDefinedConversionForV6SwitchGoverningType"/>, /// where we must consider user defined implicit conversions from the type of the switch expression to /// any of the possible switch governing types. /// </remarks> private void ComputeApplicableUserDefinedImplicitConversionSet( BoundExpression sourceExpression, TypeSymbol source, TypeSymbol target, ArrayBuilder <NamedTypeSymbol> d, ArrayBuilder <UserDefinedConversionAnalysis> u, ref HashSet <DiagnosticInfo> useSiteDiagnostics, bool allowAnyTarget = false) { Debug.Assert(sourceExpression != null || (object)source != null); Debug.Assert(((object)target != null) == !allowAnyTarget); Debug.Assert(d != null); Debug.Assert(u != null); // SPEC: Find the set of applicable user-defined and lifted conversion operators, U. // SPEC: The set consists of the user-defined and lifted implicit conversion operators // SPEC: declared by the classes and structs in D that convert from a type encompassing // SPEC: E to a type encompassed by T. If U is empty, the conversion is undefined and // SPEC: a compile-time error occurs. // SPEC: Give a user-defined conversion operator that converts from a non-nullable // SPEC: value type S to a non-nullable value type T, a lifted conversion operator // SPEC: exists that converts from S? to T?. // DELIBERATE SPEC VIOLATION: // // The spec here essentially says that we add an applicable "regular" conversion and // an applicable lifted conversion, if there is one, to the candidate set, and then // let them duke it out to determine which one is "best". // // This is not at all what the native compiler does, and attempting to implement // the specification, or slight variations on it, produces too many backwards-compatibility // breaking changes. // // The native compiler deviates from the specification in two major ways here. // First, it does not add *both* the regular and lifted forms to the candidate set. // Second, the way it characterizes a "lifted" form is very, very different from // how the specification characterizes a lifted form. // // An operation, in this case, X-->Y, is properly said to be "lifted" to X?-->Y? via // the rule that X?-->Y? matches the behavior of X-->Y for non-null X, and converts // null X to null Y otherwise. // // The native compiler, by contrast, takes the existing operator and "lifts" either // the operator's parameter type or the operator's return type to nullable. For // example, a conversion from X?-->Y would be "lifted" to X?-->Y? by making the // conversion from X? to Y, and then from Y to Y?. No "lifting" semantics // are imposed; we do not check to see if the X? is null. This operator is not // actually "lifted" at all; rather, an implicit conversion is applied to the // output. **The native compiler considers the result type Y? of that standard implicit // conversion to be the result type of the "lifted" conversion**, rather than // properly considering Y to be the result type of the conversion for the purposes // of computing the best output type. // // MOREOVER: the native compiler actually *does* implement nullable lifting semantics // in the case where the input type of the user-defined conversion is a non-nullable // value type and the output type is a nullable value type **or pointer type, or // reference type**. This is an enormous departure from the specification; the // native compiler will take a user-defined conversion from X-->Y? or X-->C and "lift" // it to a conversion from X?-->Y? or X?-->C that has nullable semantics. // // This is quite confusing. In this code we will classify the conversion as either // "normal" or "lifted" on the basis of *whether or not special lifting semantics // are to be applied*. That is, whether or not a later rewriting pass is going to // need to insert a check to see if the source expression is null, and decide // whether or not to call the underlying unlifted conversion or produce a null // value without calling the unlifted conversion. // DELIBERATE SPEC VIOLATION (See bug 17021) // The specification defines a type U as "encompassing" a type V // if there is a standard implicit conversion from U to V, and // neither are interface types. // // The intention of this language is to ensure that we do not allow user-defined // conversions that involve interfaces. We have a reasonable expectation that a // conversion that involves an interface is one that preserves referential identity, // and user-defined conversions usually do not. // // Now, suppose we have a standard conversion from Alpha to Beta, a user-defined // conversion from Beta to Gamma, and a standard conversion from Gamma to Delta. // The specification allows the implicit conversion from Alpha to Delta only if // Beta encompasses Alpha and Delta encompasses Gamma. And therefore, none of them // can be interface types, de jure. // // However, the dev10 compiler only checks Alpha and Delta to see if they are interfaces, // and allows Beta and Gamma to be interfaces. // // So what's the big deal there? It's not legal to define a user-defined conversion where // the input or output types are interfaces, right? // // It is not legal to define such a conversion, no, but it is legal to create one via generic // construction. If we have a conversion from T to C<T>, then C<I> has a conversion from I to C<I>. // // The dev10 compiler fails to check for this situation. This means that, // you can convert from int to C<IComparable> because int implements IComparable, but cannot // convert from IComparable to C<IComparable>! // // Unfortunately, we know of several real programs that rely upon this bug, so we are going // to reproduce it here. if ((object)source != null && source.IsInterfaceType() || (object)target != null && target.IsInterfaceType()) { return; } foreach (NamedTypeSymbol declaringType in d) { foreach (MethodSymbol op in declaringType.GetOperators(WellKnownMemberNames.ImplicitConversionName)) { // We might have a bad operator and be in an error recovery situation. Ignore it. if (op.ReturnsVoid || op.ParameterCount != 1) { continue; } TypeSymbol convertsFrom = op.ParameterTypes[0].TypeSymbol; TypeSymbol convertsTo = op.ReturnType.TypeSymbol; Conversion fromConversion = EncompassingImplicitConversion(sourceExpression, source, convertsFrom, ref useSiteDiagnostics); Conversion toConversion = allowAnyTarget ? Conversion.Identity : EncompassingImplicitConversion(null, convertsTo, target, ref useSiteDiagnostics); if (fromConversion.Exists && toConversion.Exists) { // There is an additional spec violation in the native compiler. Suppose // we have a conversion from X-->Y and are asked to do "Y? y = new X();" Clearly // the intention is to convert from X-->Y via the implicit conversion, and then // stick a standard implicit conversion from Y-->Y? on the back end. **In this // situation, the native compiler treats the conversion as though it were // actually X-->Y? in source for the purposes of determining the best target // type of an operator. // // We perpetuate this fiction here. if ((object)target != null && target.IsNullableType() && convertsTo.IsNonNullableValueType()) { convertsTo = MakeNullableType(convertsTo); toConversion = allowAnyTarget ? Conversion.Identity : EncompassingImplicitConversion(null, convertsTo, target, ref useSiteDiagnostics); } u.Add(UserDefinedConversionAnalysis.Normal(op, fromConversion, toConversion, convertsFrom, convertsTo)); } else if ((object)source != null && source.IsNullableType() && convertsFrom.IsNonNullableValueType() && (allowAnyTarget || target.CanBeAssignedNull())) { // As mentioned above, here we diverge from the specification, in two ways. // First, we only check for the lifted form if the normal form was inapplicable. // Second, we are supposed to apply lifting semantics only if the conversion // parameter and return types are *both* non-nullable value types. // // In fact the native compiler determines whether to check for a lifted form on // the basis of: // // * Is the type we are ultimately converting from a nullable value type? // * Is the parameter type of the conversion a non-nullable value type? // * Is the type we are ultimately converting to a nullable value type, // pointer type, or reference type? // // If the answer to all those questions is "yes" then we lift to nullable // and see if the resulting operator is applicable. TypeSymbol nullableFrom = MakeNullableType(convertsFrom); TypeSymbol nullableTo = convertsTo.IsNonNullableValueType() ? MakeNullableType(convertsTo) : convertsTo; Conversion liftedFromConversion = EncompassingImplicitConversion(sourceExpression, source, nullableFrom, ref useSiteDiagnostics); Conversion liftedToConversion = !allowAnyTarget? EncompassingImplicitConversion(null, nullableTo, target, ref useSiteDiagnostics) : Conversion.Identity; if (liftedFromConversion.Exists && liftedToConversion.Exists) { u.Add(UserDefinedConversionAnalysis.Lifted(op, liftedFromConversion, liftedToConversion, nullableFrom, nullableTo)); } } } } }
protected BoundCall MakeQueryInvocation(CSharpSyntaxNode node, BoundExpression receiver, string methodName, TypeSyntax typeArgSyntax, TypeSymbolWithAnnotations typeArg, DiagnosticBag diagnostics) { return(MakeQueryInvocation(node, receiver, methodName, new SeparatedSyntaxList <TypeSyntax>(new SyntaxNodeOrTokenList(typeArgSyntax, 0)), ImmutableArray.Create(typeArg), ImmutableArray <BoundExpression> .Empty, diagnostics)); }
/// <remarks> /// NOTE: Keep this method in sync with <see cref="AnalyzeImplicitUserDefinedConversionForV6SwitchGoverningType"/>. /// </remarks> private UserDefinedConversionResult AnalyzeImplicitUserDefinedConversions( BoundExpression sourceExpression, TypeSymbol source, TypeSymbol target, ref HashSet <DiagnosticInfo> useSiteDiagnostics) { Debug.Assert(sourceExpression != null || (object)source != null); Debug.Assert((object)target != null); // User-defined conversions that involve generics can be quite strange. There // are two basic problems: first, that generic user-defined conversions can be // "shadowed" by built-in conversions, and second, that generic user-defined // conversions can make conversions that would never have been legal user-defined // conversions if declared non-generically. I call this latter kind of conversion // a "suspicious" conversion. // // The shadowed conversions are easily dealt with: // // SPEC: If a predefined implicit conversion exists from a type S to type T, // SPEC: all user-defined conversions, implicit or explicit, are ignored. // SPEC: If a predefined explicit conversion exists from a type S to type T, // SPEC: any user-defined explicit conversion from S to T are ignored. // // The rule above can come into play in cases like: // // sealed class C<T> { public static implicit operator T(C<T> c) { ... } } // C<object> c = whatever; // object o = c; // // The built-in implicit conversion from C<object> to object must shadow // the user-defined implicit conversion. // // The caller of this method checks for user-defined conversions *after* // predefined implicit conversions, so we already know that if we got here, // there was no predefined implicit conversion. // // Note that a user-defined *implicit* conversion may win over a built-in // *explicit* conversion by the rule given above. That is, if we created // an implicit conversion from T to C<T>, then the user-defined implicit // conversion from object to C<object> could be valid, even though that // would be "replacing" a built-in explicit conversion with a user-defined // implicit conversion. This is one of the "suspicious" conversions, // as it would not be legal to declare a user-defined conversion from // object in a non-generic type. // // The way the native compiler handles suspicious conversions involving // interfaces is neither sensible nor in line with the rules in the // specification. It is not clear at this time whether we should be exactly // matching the native compiler, the specification, or neither, in Roslyn. // Spec (6.4.4 User-defined implicit conversions) // A user-defined implicit conversion from an expression E to type T is processed as follows: // SPEC: Find the set of types D from which user-defined conversion operators... var d = ArrayBuilder <NamedTypeSymbol> .GetInstance(); ComputeUserDefinedImplicitConversionTypeSet(source, target, d, ref useSiteDiagnostics); // SPEC: Find the set of applicable user-defined and lifted conversion operators, U... var ubuild = ArrayBuilder <UserDefinedConversionAnalysis> .GetInstance(); ComputeApplicableUserDefinedImplicitConversionSet(sourceExpression, source, target, d, ubuild, ref useSiteDiagnostics); d.Free(); ImmutableArray <UserDefinedConversionAnalysis> u = ubuild.ToImmutableAndFree(); // SPEC: If U is empty, the conversion is undefined and a compile-time error occurs. if (u.Length == 0) { return(UserDefinedConversionResult.NoApplicableOperators(u)); } // SPEC: Find the most specific source type SX of the operators in U... TypeSymbol sx = MostSpecificSourceTypeForImplicitUserDefinedConversion(u, source, ref useSiteDiagnostics); if ((object)sx == null) { return(UserDefinedConversionResult.NoBestSourceType(u)); } // SPEC: Find the most specific target type TX of the operators in U... TypeSymbol tx = MostSpecificTargetTypeForImplicitUserDefinedConversion(u, target, ref useSiteDiagnostics); if ((object)tx == null) { return(UserDefinedConversionResult.NoBestTargetType(u)); } int?best = MostSpecificConversionOperator(sx, tx, u); if (best == null) { return(UserDefinedConversionResult.Ambiguous(u)); } return(UserDefinedConversionResult.Valid(u, best.Value)); }
private BoundExpression HoistExpression( BoundExpression expr, AwaitExpressionSyntax awaitSyntaxOpt, int syntaxOffset, RefKind refKind, ArrayBuilder <BoundExpression> sideEffects, ArrayBuilder <StateMachineFieldSymbol> hoistedFields, ref bool needsSacrificialEvaluation) { switch (expr.Kind) { case BoundKind.ArrayAccess: { var array = (BoundArrayAccess)expr; BoundExpression expression = HoistExpression(array.Expression, awaitSyntaxOpt, syntaxOffset, RefKind.None, sideEffects, hoistedFields, ref needsSacrificialEvaluation); var indices = ArrayBuilder <BoundExpression> .GetInstance(); foreach (var index in array.Indices) { indices.Add(HoistExpression(index, awaitSyntaxOpt, syntaxOffset, RefKind.None, sideEffects, hoistedFields, ref needsSacrificialEvaluation)); } needsSacrificialEvaluation = true; // need to force array index out of bounds exceptions return(array.Update(expression, indices.ToImmutableAndFree(), array.Type)); } case BoundKind.FieldAccess: { var field = (BoundFieldAccess)expr; if (field.FieldSymbol.IsStatic) { // the address of a static field, and the value of a readonly static field, is stable if (refKind != RefKind.None || field.FieldSymbol.IsLet) { return(expr); } goto default; } if (refKind == RefKind.None) { goto default; } var isFieldOfStruct = !field.FieldSymbol.ContainingType.IsReferenceType; var receiver = HoistExpression(field.ReceiverOpt, awaitSyntaxOpt, syntaxOffset, isFieldOfStruct ? refKind : RefKind.None, sideEffects, hoistedFields, ref needsSacrificialEvaluation); if (receiver.Kind != BoundKind.ThisReference && !isFieldOfStruct) { needsSacrificialEvaluation = true; // need the null check in field receiver } return(F.Field(receiver, field.FieldSymbol)); } case BoundKind.ThisReference: case BoundKind.BaseReference: case BoundKind.DefaultExpression: return(expr); case BoundKind.Call: var call = (BoundCall)expr; // NOTE: There are two kinds of 'In' arguments that we may see at this point: // - `RefKindExtensions.StrictIn` (originally specified with 'In' modifier) // - `RefKind.In` (specified with no modifiers and matched an 'In' parameter) // // It is allowed to spill ordinary `In` arguments by value if reference-preserving spilling is not possible. // The "strict" ones do not permit implicit copying, so the same situation should result in an error. if (refKind != RefKind.None && refKind != RefKind.In) { Debug.Assert(call.Method.RefKind != RefKind.None); F.Diagnostics.Add(ErrorCode.ERR_RefReturningCallAndAwait, F.Syntax.Location, call.Method); } // method call is not referentially transparent, we can only spill the result value. refKind = RefKind.None; goto default; case BoundKind.ConditionalOperator: var conditional = (BoundConditionalOperator)expr; // NOTE: There are two kinds of 'In' arguments that we may see at this point: // - `RefKindExtensions.StrictIn` (originally specified with 'In' modifier) // - `RefKind.In` (specified with no modifiers and matched an 'In' parameter) // // It is allowed to spill ordinary `In` arguments by value if reference-preserving spilling is not possible. // The "strict" ones do not permit implicit copying, so the same situation should result in an error. if (refKind != RefKind.None && refKind != RefKind.RefReadOnly) { Debug.Assert(conditional.IsRef); F.Diagnostics.Add(ErrorCode.ERR_RefConditionalAndAwait, F.Syntax.Location); } // conditional expr is not referentially transparent, we can only spill the result value. refKind = RefKind.None; goto default; default: if (expr.ConstantValue != null) { return(expr); } if (refKind != RefKind.None) { throw ExceptionUtilities.UnexpectedValue(expr.Kind); } TypeSymbol fieldType = expr.Type; StateMachineFieldSymbol hoistedField; if (F.Compilation.Options.OptimizationLevel == OptimizationLevel.Debug) { const SynthesizedLocalKind kind = SynthesizedLocalKind.AwaitByRefSpill; Debug.Assert(awaitSyntaxOpt != null); int ordinal = _synthesizedLocalOrdinals.AssignLocalOrdinal(kind, syntaxOffset); var id = new LocalDebugId(syntaxOffset, ordinal); // Editing await expression is not allowed. Thus all spilled fields will be present in the previous state machine. // However, it may happen that the type changes, in which case we need to allocate a new slot. int slotIndex; if (slotAllocatorOpt == null || !slotAllocatorOpt.TryGetPreviousHoistedLocalSlotIndex( awaitSyntaxOpt, F.ModuleBuilderOpt.Translate(fieldType, awaitSyntaxOpt, Diagnostics), kind, id, Diagnostics, out slotIndex)) { slotIndex = _nextFreeHoistedLocalSlot++; } string fieldName = GeneratedNames.MakeHoistedLocalFieldName(kind, slotIndex); hoistedField = F.StateMachineField(expr.Type, fieldName, new LocalSlotDebugInfo(kind, id), slotIndex); } else { hoistedField = GetOrAllocateReusableHoistedField(fieldType, reused: out _); } hoistedFields.Add(hoistedField); var replacement = F.Field(F.This(), hoistedField); sideEffects.Add(F.AssignmentExpression(replacement, expr)); return(replacement); } }
private BoundExpression RewriteStringConcatenationThreeExprs(SyntaxNode syntax, BoundExpression loweredFirst, BoundExpression loweredSecond, BoundExpression loweredThird) { SpecialMember member = (loweredFirst.Type.SpecialType == SpecialType.System_String && loweredSecond.Type.SpecialType == SpecialType.System_String && loweredThird.Type.SpecialType == SpecialType.System_String) ? SpecialMember.System_String__ConcatStringStringString : SpecialMember.System_String__ConcatObjectObjectObject; var method = UnsafeGetSpecialTypeMethod(syntax, member); Debug.Assert((object)method != null); return(BoundCall.Synthesized(syntax, null, method, ImmutableArray.Create(loweredFirst, loweredSecond, loweredThird))); }