public static DecisionTree Create(BoundExpression expression, TypeSymbol type, Symbol enclosingSymbol) { Debug.Assert(expression.Type == type); LocalSymbol temp = null; if (expression.ConstantValue == null) { // Unless it is a constant, the decision tree acts on a copy of the input expression. // We create a temp to represent that copy. Lowering will assign into this temp. temp = new SynthesizedLocal(enclosingSymbol as MethodSymbol, type, SynthesizedLocalKind.PatternMatchingTemp, expression.Syntax, false, RefKind.None); expression = new BoundLocal(expression.Syntax, temp, null, type); } if (expression.Type.CanBeAssignedNull()) { // We need the ByType decision tree to separate null from non-null values. // Note that, for the purpose of the decision tree (and subsumption), we // ignore the fact that the input may be a constant, and therefore always // or never null. return new ByType(expression, type, temp); } else { // If it is a (e.g. builtin) value type, we can switch on its (constant) values. // If it isn't a builtin, in practice we will only use the Default part of the // ByValue. return new ByValue(expression, type, temp); } }
public Conversion ClassifyConversionFromExpression(BoundExpression sourceExpression, TypeSymbol destination, ref HashSet<DiagnosticInfo> useSiteDiagnostics) { Debug.Assert(sourceExpression != null); Debug.Assert((object)destination != null); return ClassifyConversionFromExpression(sourceExpression, sourceExpression.Type, destination, ref useSiteDiagnostics); }
/// <summary> /// Lower the given decision tree into the given statement builder. /// </summary> public void LowerDecisionTree(BoundExpression expression, DecisionTree decisionTree, ArrayBuilder<BoundStatement> loweredDecisionTree) { var oldLoweredDecisionTree = this._loweredDecisionTree; this._loweredDecisionTree = loweredDecisionTree; LowerDecisionTree(expression, decisionTree); this._loweredDecisionTree = oldLoweredDecisionTree; }
/// <summary> /// If a ref variable is to be spilled, sometimes that causes us to need to spill /// the thing the ref variable was initialized with. For example, if the variable /// was initialized with "structVariable.field", then the struct variable needs to /// be spilled. This method adds to the spill set things that need to be spilled /// based on the given refInitializer expression. /// </summary> private void AddSpillsForRef(BoundExpression refInitializer, IEnumerable<CSharpSyntaxNode> locations) { while (true) { if (refInitializer == null) return; switch (refInitializer.Kind) { case BoundKind.Local: var local = (BoundLocal)refInitializer; if (!variablesCaptured.ContainsKey(local.LocalSymbol)) { foreach (var loc in locations) variablesCaptured.Add(local.LocalSymbol, loc); if (local.LocalSymbol.RefKind != RefKind.None) { refInitializer = refLocalInitializers[local.LocalSymbol]; continue; } } return; case BoundKind.FieldAccess: var field = (BoundFieldAccess)refInitializer; if (!field.FieldSymbol.IsStatic && field.FieldSymbol.ContainingType.IsValueType) { refInitializer = field.ReceiverOpt; continue; } return; default: return; } } }
private BoundExpression MakePropertyGetAccess( SyntaxNode syntax, BoundExpression rewrittenReceiver, PropertySymbol property, ImmutableArray<BoundExpression> rewrittenArguments, MethodSymbol getMethodOpt = null, BoundPropertyAccess oldNodeOpt = null) { if (_inExpressionLambda && rewrittenArguments.IsEmpty) { return oldNodeOpt != null ? oldNodeOpt.Update(rewrittenReceiver, property, LookupResultKind.Viable, property.Type) : new BoundPropertyAccess(syntax, rewrittenReceiver, property, LookupResultKind.Viable, property.Type); } else { var getMethod = getMethodOpt ?? property.GetOwnOrInheritedGetMethod(); Debug.Assert((object)getMethod != null); Debug.Assert(getMethod.ParameterCount == rewrittenArguments.Length); Debug.Assert(((object)getMethodOpt == null) || ReferenceEquals(getMethod, getMethodOpt)); return BoundCall.Synthesized( syntax, rewrittenReceiver, getMethod, rewrittenArguments); } }
// Rewrite collection initializer add method calls: // 2) new List<int> { 1 }; // ~ private void AddCollectionInitializers(ref ArrayBuilder<BoundExpression> dynamicSiteInitializers, ArrayBuilder<BoundExpression> result, BoundExpression rewrittenReceiver, ImmutableArray<BoundExpression> initializers) { Debug.Assert(rewrittenReceiver != null || _inExpressionLambda); foreach (var initializer in initializers) { // In general bound initializers may contain bad expressions or element initializers. // We don't lower them if they contain errors, so it's safe to assume an element initializer. BoundExpression rewrittenInitializer; if (initializer.Kind == BoundKind.CollectionElementInitializer) { rewrittenInitializer = MakeCollectionInitializer(rewrittenReceiver, (BoundCollectionElementInitializer)initializer); } else { Debug.Assert(!_inExpressionLambda); Debug.Assert(initializer.Kind == BoundKind.DynamicCollectionElementInitializer); rewrittenInitializer = MakeDynamicCollectionInitializer(rewrittenReceiver, (BoundDynamicCollectionElementInitializer)initializer); } // the call to Add may be omitted if (rewrittenInitializer != null) { result.Add(rewrittenInitializer); } } }
private BoundExpression MakeFieldAccess( CSharpSyntaxNode syntax, BoundExpression rewrittenReceiver, FieldSymbol fieldSymbol, ConstantValue constantValueOpt, LookupResultKind resultKind, TypeSymbol type, BoundFieldAccess oldNodeOpt = null) { if (fieldSymbol.IsTupleField) { return MakeTupleFieldAccess(syntax, fieldSymbol, rewrittenReceiver, constantValueOpt, resultKind); } BoundExpression result = oldNodeOpt != null ? oldNodeOpt.Update(rewrittenReceiver, fieldSymbol, constantValueOpt, resultKind, type) : new BoundFieldAccess(syntax, rewrittenReceiver, fieldSymbol, constantValueOpt, resultKind, type); if (fieldSymbol.IsFixed) { // a reference to a fixed buffer is translated into its address result = new BoundConversion(syntax, new BoundAddressOfOperator(syntax, result, syntax != null && SyntaxFacts.IsFixedStatementExpression(syntax), type, false), new Conversion(ConversionKind.PointerToPointer), false, false, default(ConstantValue), type, false); } return result; }
/// <summary> /// Spill an expression list with a receiver (e.g. array access, method call), where at least one of the /// receiver or the arguments contains an await expression. /// </summary> private Tuple<BoundExpression, ReadOnlyArray<BoundExpression>> SpillExpressionsWithReceiver( BoundExpression receiverOpt, ReadOnlyArray<BoundExpression> expressions, SpillBuilder spillBuilder, ReadOnlyArray<RefKind> refKindsOpt) { if (receiverOpt == null) { return Tuple.Create(default(BoundExpression), SpillExpressionList(spillBuilder, expressions)); } // We have a non-null receiver, and an expression of the form: // receiver[index1, index2, ..., indexN] // or: // receiver(arg1, arg2, ... argN) // Build a list containing the receiver and all expressions (in that order) var allExpressions = ReadOnlyArray<BoundExpression>.CreateFrom(receiverOpt).Concat(expressions); var allRefKinds = (refKindsOpt != null) ? ReadOnlyArray<RefKind>.CreateFrom(RefKind.None).Concat(refKindsOpt) : ReadOnlyArray<RefKind>.Empty; // Spill the expressions (and possibly the receiver): var allSpilledExpressions = SpillExpressionList(spillBuilder, allExpressions, allRefKinds); var spilledReceiver = allSpilledExpressions.First(); var spilledArguments = allSpilledExpressions.RemoveFirst(); return Tuple.Create(spilledReceiver, spilledArguments); }
private BoundExpression VisitUnusedExpression(BoundExpression expression) { if (expression.HasErrors) { return expression; } switch (expression.Kind) { case BoundKind.AssignmentOperator: // Avoid extra temporary by indicating the expression value is not used. var assignmentOperator = (BoundAssignmentOperator)expression; return VisitAssignmentOperator(assignmentOperator, used: false); case BoundKind.CompoundAssignmentOperator: var compoundAssignmentOperator = (BoundCompoundAssignmentOperator)expression; return VisitCompoundAssignmentOperator(compoundAssignmentOperator, false); case BoundKind.Call: var call = (BoundCall)expression; if (call.Method.CallsAreOmitted(call.SyntaxTree)) { return null; } break; case BoundKind.DynamicInvocation: // TODO (tomat): circumvents logic in VisitExpression... return VisitDynamicInvocation((BoundDynamicInvocation)expression, resultDiscarded: true); } return VisitExpression(expression); }
internal BoundSpillSequence2 Update(BoundExpression value) { var result = new BoundSpillSequence2(value); result.locals = this.locals; result.statements = this.statements; return result; }
private BoundExpression SelectField(SimpleNameSyntax node, BoundExpression receiver, string name, DiagnosticBag diagnostics) { var receiverType = receiver.Type as NamedTypeSymbol; if ((object)receiverType == null || !receiverType.IsAnonymousType) { // We only construct transparent query variables using anonymous types, so if we're trying to navigate through // some other type, we must have some hinky query API where the types don't match up as expected. // We should report this as an error of some sort. // TODO: DevDiv #737822 - reword error message and add test. var info = new CSDiagnosticInfo(ErrorCode.ERR_UnsupportedTransparentIdentifierAccess, name, receiver.ExpressionSymbol ?? receiverType); Error(diagnostics, info, node); return new BoundBadExpression( node, LookupResultKind.Empty, ImmutableArray.Create<Symbol>(receiver.ExpressionSymbol), ImmutableArray.Create<BoundNode>(receiver), new ExtendedErrorTypeSymbol(this.Compilation, "", 0, info)); } LookupResult lookupResult = LookupResult.GetInstance(); LookupOptions options = LookupOptions.MustBeInstance; HashSet<DiagnosticInfo> useSiteDiagnostics = null; LookupMembersWithFallback(lookupResult, receiver.Type, name, 0, ref useSiteDiagnostics, basesBeingResolved: null, options: options); diagnostics.Add(node, useSiteDiagnostics); var result = BindMemberOfType(node, node, name, 0, receiver, default(SeparatedSyntaxList<TypeSyntax>), default(ImmutableArray<TypeSymbol>), lookupResult, BoundMethodGroupFlags.None, diagnostics); result.WasCompilerGenerated = true; lookupResult.Free(); return result; }
private void CheckReceiverIfField(BoundExpression receiverOpt) { if (receiverOpt != null && receiverOpt.Kind == BoundKind.FieldAccess) { CheckFieldAsReceiver((BoundFieldAccess)receiverOpt); } }
private BoundExpression MakeIsDeclarationPattern(BoundDeclarationPattern loweredPattern, BoundExpression loweredInput) { Debug.Assert(((object)loweredPattern.Variable == null && loweredPattern.VariableAccess.Kind == BoundKind.DiscardedExpression) || loweredPattern.Variable.GetTypeOrReturnType() == loweredPattern.DeclaredType.Type); if (loweredPattern.IsVar) { var result = _factory.Literal(true); if (loweredPattern.VariableAccess.Kind == BoundKind.DiscardedExpression) { return result; } Debug.Assert((object)loweredPattern.Variable != null && loweredInput.Type == loweredPattern.Variable.GetTypeOrReturnType()); var assignment = _factory.AssignmentExpression(loweredPattern.VariableAccess, loweredInput); return _factory.MakeSequence(assignment, result); } if (loweredPattern.VariableAccess.Kind == BoundKind.DiscardedExpression) { LocalSymbol temp; BoundLocal discard = _factory.MakeTempForDiscard((BoundDiscardedExpression)loweredPattern.VariableAccess, out temp); return _factory.Sequence(ImmutableArray.Create(temp), sideEffects: ImmutableArray<BoundExpression>.Empty, result: MakeIsDeclarationPattern(loweredPattern.Syntax, loweredInput, discard, requiresNullTest: true)); } return MakeIsDeclarationPattern(loweredPattern.Syntax, loweredInput, loweredPattern.VariableAccess, requiresNullTest: true); }
private static BoundExpression RewriteConditionalOperator( CSharpSyntaxNode syntax, BoundExpression rewrittenCondition, BoundExpression rewrittenConsequence, BoundExpression rewrittenAlternative, ConstantValue constantValueOpt, TypeSymbol rewrittenType) { // NOTE: This optimization assumes that a constant has no side effects. In the future we // might wish to represent nodes that are known to the optimizer as having constant // values as a sequence of side effects and a constant value; in that case the result // of this should be a sequence containing the side effect and the consequence or alternative. ConstantValue conditionConstantValue = rewrittenCondition.ConstantValue; if (conditionConstantValue == ConstantValue.True) { return rewrittenConsequence; } else if (conditionConstantValue == ConstantValue.False) { return rewrittenAlternative; } else { return new BoundConditionalOperator( syntax, rewrittenCondition, rewrittenConsequence, rewrittenAlternative, constantValueOpt, rewrittenType); } }
public LoweredDynamicOperation(SyntheticBoundNodeFactory factory, BoundExpression siteInitialization, BoundExpression siteInvocation, TypeSymbol resultType) { this.Factory = factory; this.resultType = resultType; this.SiteInitialization = siteInitialization; this.SiteInvocation = siteInvocation; }
internal SubsumptionDiagnosticBuilder(Symbol enclosingSymbol, Conversions conversions, BoundExpression expression) : base(enclosingSymbol, conversions) { _subsumptionTree = DecisionTree.Create(expression, expression.Type, enclosingSymbol); }
private void PopulateHelper(BoundExpression receiverOpt, LookupResultKind resultKind, DiagnosticInfo error) { VerifyClear(); this.Receiver = receiverOpt; this.Error = error; this.ResultKind = resultKind; }
internal BinderWithConditionalReceiver(Binder next, BoundExpression receiverExpression) : base(next) { Debug.Assert(receiverExpression != null); _receiverExpression = receiverExpression; }
private BoundStatement RewriteWhileStatement( BoundLoopStatement loop, BoundExpression rewrittenCondition, BoundStatement rewrittenBody, GeneratedLabelSymbol breakLabel, GeneratedLabelSymbol continueLabel, bool hasErrors) { Debug.Assert(loop.Kind == BoundKind.WhileStatement || loop.Kind == BoundKind.ForEachStatement); // while (condition) // body; // // becomes // // goto continue; // start: // { // body // continue: // GotoIfTrue condition start; // } // break: SyntaxNode syntax = loop.Syntax; var startLabel = new GeneratedLabelSymbol("start"); BoundStatement ifConditionGotoStart = new BoundConditionalGoto(rewrittenCondition.Syntax, rewrittenCondition, true, startLabel); BoundStatement gotoContinue = new BoundGotoStatement(syntax, continueLabel); if (this.Instrument && !loop.WasCompilerGenerated) { switch (loop.Kind) { case BoundKind.WhileStatement: ifConditionGotoStart = _instrumenter.InstrumentWhileStatementConditionalGotoStartOrBreak((BoundWhileStatement)loop, ifConditionGotoStart); break; case BoundKind.ForEachStatement: ifConditionGotoStart = _instrumenter.InstrumentForEachStatementConditionalGotoStart((BoundForEachStatement)loop, ifConditionGotoStart); break; default: throw ExceptionUtilities.UnexpectedValue(loop.Kind); } // 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 would give the // impression that the previous statement is being re-executed. gotoContinue = new BoundSequencePoint(null, gotoContinue); } return BoundStatementList.Synthesized(syntax, hasErrors, gotoContinue, new BoundLabelStatement(syntax, startLabel), rewrittenBody, new BoundLabelStatement(syntax, continueLabel), ifConditionGotoStart, new BoundLabelStatement(syntax, breakLabel)); }
private static BoundStatement RewriteIfStatement( CSharpSyntaxNode syntax, ImmutableArray<LocalSymbol> locals, BoundExpression rewrittenCondition, BoundStatement rewrittenConsequence, BoundStatement rewrittenAlternativeOpt, bool hasErrors) { var afterif = new GeneratedLabelSymbol("afterif"); var builder = ArrayBuilder<BoundStatement>.GetInstance(); if (rewrittenAlternativeOpt == null) { // if (condition) // consequence; // // becomes // // GotoIfFalse condition afterif; // consequence; // afterif: builder.Add(new BoundConditionalGoto(rewrittenCondition.Syntax, rewrittenCondition, false, afterif)); builder.Add(rewrittenConsequence); } else { // if (condition) // consequence; // else // alternative // // becomes // // GotoIfFalse condition alt; // consequence // goto afterif; // alt: // alternative; // afterif: var alt = new GeneratedLabelSymbol("alternative"); builder.Add(new BoundConditionalGoto(rewrittenCondition.Syntax, rewrittenCondition, false, alt)); builder.Add(rewrittenConsequence); builder.Add(new BoundGotoStatement(syntax, afterif)); builder.Add(new BoundLabelStatement(syntax, alt)); builder.Add(rewrittenAlternativeOpt); } builder.Add(new BoundLabelStatement(syntax, afterif)); if (!locals.IsDefaultOrEmpty) { return new BoundBlock(syntax, locals, builder.ToImmutableAndFree(), hasErrors); } return new BoundStatementList(syntax, builder.ToImmutableAndFree(), hasErrors); }
protected BoundExpression CreateConversion( BoundExpression source, Conversion conversion, TypeSymbol destination, DiagnosticBag diagnostics) { return CreateConversion(source.Syntax, source, conversion, isCast: false, destination: destination, diagnostics: diagnostics); }
private BoundNode SpillAssignmentOperator(BoundAssignmentOperator node, BoundExpression left, BoundSpillSequence right) { var spillBuilder = new SpillBuilder(); var spilledLeftNode = SpillLValue(left, spillBuilder); var innerSpill = node.Update(spilledLeftNode, right.Value, node.RefKind, node.Type); spillBuilder.AddSpill(right); return spillBuilder.BuildSequenceAndFree(F, innerSpill); }
public LoweredDynamicOperation(SyntheticBoundNodeFactory factory, BoundExpression siteInitialization, BoundExpression siteInvocation, TypeSymbol resultType, ImmutableArray<LocalSymbol> temps) { _factory = factory; _resultType = resultType; _temps = temps; this.SiteInitialization = siteInitialization; this.SiteInvocation = siteInvocation; }
protected override void WriteArgument(BoundExpression arg, RefKind refKind, MethodSymbol method) { // ref parameter does not "always" assign. if (refKind == RefKind.Out) { Assign(arg, value: null); } }
public DecisionTree(BoundExpression expression, TypeSymbol type, LocalSymbol temp) { this.Expression = expression; this.Type = type; this.Temp = temp; Debug.Assert(this.Expression != null); Debug.Assert(this.Type != null); }
public BoundFieldAccess Update( BoundExpression receiver, FieldSymbol fieldSymbol, ConstantValue constantValueOpt, LookupResultKind resultKind, TypeSymbol typeSymbol) { return this.Update(receiver, fieldSymbol, constantValueOpt, resultKind, this.IsByValue, typeSymbol); }
internal void PopulateWithSingleMethod( BoundExpression receiverOpt, MethodSymbol method, LookupResultKind resultKind = LookupResultKind.Viable, DiagnosticInfo error = null) { this.PopulateHelper(receiverOpt, resultKind, error); this.Methods.Add(method); }
private BoundExpression MakeUnaryOperator( UnaryOperatorKind kind, CSharpSyntaxNode syntax, MethodSymbol method, BoundExpression loweredOperand, TypeSymbol type) { return MakeUnaryOperator(null, kind, syntax, method, loweredOperand, type); }
protected override void VisitPatternSwitchSection(BoundPatternSwitchSection node, BoundExpression switchExpression, bool isLastSection) { foreach (var label in node.SwitchLabels) { NoteDeclaredPatternVariables(label.Pattern); } base.VisitPatternSwitchSection(node, switchExpression, isLastSection); }
public BoundFieldAccess( CSharpSyntaxNode syntax, BoundExpression receiver, FieldSymbol fieldSymbol, ConstantValue constantValueOpt, bool hasErrors = false) : this(syntax, receiver, fieldSymbol, constantValueOpt, LookupResultKind.Viable, fieldSymbol.Type, hasErrors) { }
private BoundStatement GenerateAwaitOnCompleted( TypeSymbol loweredAwaiterType, LocalSymbol awaiterTemp ) { // this.builder.AwaitOnCompleted<TAwaiter,TSM>(ref $awaiterTemp, ref this) // or // this.builder.AwaitOnCompleted<TAwaiter,TSM>(ref $awaiterArrayTemp[0], ref this) LocalSymbol thisTemp = (F.CurrentType.TypeKind == TypeKind.Class) ? F.SynthesizedLocal(F.CurrentType) : null; var discardedUseSiteInfo = CompoundUseSiteInfo <AssemblySymbol> .Discarded; var useUnsafeOnCompleted = F.Compilation.Conversions.ClassifyImplicitConversionFromType( loweredAwaiterType, F.Compilation.GetWellKnownType( WellKnownType.System_Runtime_CompilerServices_ICriticalNotifyCompletion ), ref discardedUseSiteInfo ).IsImplicit; var onCompleted = ( useUnsafeOnCompleted ? _asyncMethodBuilderMemberCollection.AwaitUnsafeOnCompleted : _asyncMethodBuilderMemberCollection.AwaitOnCompleted ).Construct(loweredAwaiterType, F.This().Type); if (_asyncMethodBuilderMemberCollection.CheckGenericMethodConstraints) { onCompleted.CheckConstraints( new ConstraintsHelper.CheckConstraintsArgs( F.Compilation, F.Compilation.Conversions, includeNullability: false, F.Syntax.Location, this.Diagnostics ) ); } BoundExpression result = F.Call( F.Field(F.This(), _asyncMethodBuilderField), onCompleted, F.Local(awaiterTemp), F.This(thisTemp) ); if (thisTemp != null) { result = F.Sequence( ImmutableArray.Create(thisTemp), ImmutableArray.Create <BoundExpression>( F.AssignmentExpression(F.Local(thisTemp), F.This()) ), result ); } return(F.ExpressionStatement(result)); }
private BoundExpression LowerLiftedUnaryOperator( UnaryOperatorKind kind, SyntaxNode syntax, MethodSymbol method, BoundExpression loweredOperand, TypeSymbol type) { // First, an optimization. If we know that the operand is always null then // we can simply lower to the alternative. BoundExpression optimized = OptimizeLiftedUnaryOperator(kind, syntax, method, loweredOperand, type); if (optimized != null) { return(optimized); } // We do not know whether the operand is null or non-null, so we generate: // // S? temp = operand; // R? r = temp.HasValue ? // new R?(OP(temp.GetValueOrDefault())) : // default(R?); BoundAssignmentOperator tempAssignment; BoundLocal boundTemp = _factory.StoreToTemp(loweredOperand, out tempAssignment); MethodSymbol getValueOrDefault = UnsafeGetNullableMethod(syntax, boundTemp.Type, SpecialMember.System_Nullable_T_GetValueOrDefault); // temp.HasValue BoundExpression condition = MakeNullableHasValue(syntax, boundTemp); // temp.GetValueOrDefault() BoundExpression call_GetValueOrDefault = BoundCall.Synthesized(syntax, boundTemp, getValueOrDefault); // new R?(temp.GetValueOrDefault()) BoundExpression consequence = GetLiftedUnaryOperatorConsequence(kind, syntax, method, type, call_GetValueOrDefault); // default(R?) BoundExpression alternative = new BoundDefaultExpression(syntax, null, type); // temp.HasValue ? // new R?(OP(temp.GetValueOrDefault())) : // default(R?); BoundExpression conditionalExpression = RewriteConditionalOperator( syntax: syntax, rewrittenCondition: condition, rewrittenConsequence: consequence, rewrittenAlternative: alternative, constantValueOpt: null, rewrittenType: type); // temp = operand; // temp.HasValue ? // new R?(OP(temp.GetValueOrDefault())) : // default(R?); return(new BoundSequence( syntax: syntax, locals: ImmutableArray.Create <LocalSymbol>(boundTemp.LocalSymbol), sideEffects: ImmutableArray.Create <BoundExpression>(tempAssignment), value: conditionalExpression, type: type)); }
private static bool IsNullOrEmptyStringConstant(BoundExpression operand) { return((operand.ConstantValue != null && string.IsNullOrEmpty(operand.ConstantValue.StringValue)) || operand.IsDefaultValue()); }
private BoundExpression Spill( BoundSpillSequenceBuilder builder, BoundExpression expression, RefKind refKind = RefKind.None, bool sideEffectsOnly = false) { Debug.Assert(builder != null); while (true) { switch (expression.Kind) { case BoundKind.ArrayInitialization: Debug.Assert(refKind == RefKind.None); Debug.Assert(!sideEffectsOnly); var arrayInitialization = (BoundArrayInitialization)expression; var newInitializers = VisitExpressionList(ref builder, arrayInitialization.Initializers, forceSpill: true); return(arrayInitialization.Update(newInitializers)); case BoundKind.ArgListOperator: Debug.Assert(refKind == RefKind.None); Debug.Assert(!sideEffectsOnly); var argumentList = (BoundArgListOperator)expression; var newArgs = VisitExpressionList(ref builder, argumentList.Arguments, argumentList.ArgumentRefKindsOpt, forceSpill: true); return(argumentList.Update(newArgs, argumentList.ArgumentRefKindsOpt, argumentList.Type)); case SpillSequenceBuilder: var sequenceBuilder = (BoundSpillSequenceBuilder)expression; builder.Include(sequenceBuilder); expression = sequenceBuilder.Value; continue; case BoundKind.Sequence: // We don't need promote short-lived variables defined by the sequence to long-lived, // since neither the side-effects nor the value of the sequence contains await // (otherwise it would be converted to a SpillSequenceBuilder). var sequence = (BoundSequence)expression; builder.AddLocals(sequence.Locals); builder.AddExpressions(sequence.SideEffects); expression = sequence.Value; continue; case BoundKind.ThisReference: case BoundKind.BaseReference: if (refKind != RefKind.None || expression.Type.IsReferenceType) { return(expression); } goto default; case BoundKind.Parameter: if (refKind != RefKind.None) { return(expression); } goto default; case BoundKind.Local: var local = (BoundLocal)expression; if (local.LocalSymbol.SynthesizedKind == SynthesizedLocalKind.AwaitSpill || refKind != RefKind.None) { return(local); } goto default; case BoundKind.FieldAccess: var field = (BoundFieldAccess)expression; if (field.FieldSymbol.IsReadOnly) { if (field.FieldSymbol.IsStatic) { return(field); } if (field.FieldSymbol.ContainingType.IsValueType) { goto default; } // save the receiver; can get the field later. var receiver = Spill(builder, field.ReceiverOpt, (refKind != RefKind.None && field.FieldSymbol.Type.IsReferenceType) ? refKind : RefKind.None, sideEffectsOnly); return(field.Update(receiver, field.FieldSymbol, field.ConstantValueOpt, field.ResultKind, field.Type)); } goto default; case BoundKind.Call: var call = (BoundCall)expression; if (refKind != RefKind.None) { Debug.Assert(call.Method.RefKind != RefKind.None); _F.Diagnostics.Add(ErrorCode.ERR_RefReturningCallAndAwait, _F.Syntax.Location, call.Method); refKind = RefKind.None; // Switch the RefKind to avoid asserting later in the pipeline } goto default; case BoundKind.Literal: case BoundKind.TypeExpression: return(expression); case BoundKind.ConditionalReceiver: // we will rewrite this as a part of rewriting whole LoweredConditionalAccess // later, if needed return(expression); default: if (expression.Type.SpecialType == SpecialType.System_Void || sideEffectsOnly) { builder.AddStatement(_F.ExpressionStatement(expression)); return(null); } else { BoundAssignmentOperator assignToTemp; Debug.Assert(_F.Syntax.IsKind(SyntaxKind.AwaitExpression)); var replacement = _F.StoreToTemp( expression, out assignToTemp, refKind: refKind, kind: SynthesizedLocalKind.AwaitSpill, syntaxOpt: _F.Syntax); builder.AddLocal(replacement.LocalSymbol, _F.Diagnostics); builder.AddStatement(_F.ExpressionStatement(assignToTemp)); return(replacement); } } } }
private static RefKind ReceiverSpillRefKind(BoundExpression receiver) { return(LocalRewriter.WouldBeAssignableIfUsedAsMethodReceiver(receiver) ? RefKind.Ref : RefKind.None); }
private BoundExpression RewriteStringConcatenationTwoExprs(CSharpSyntaxNode syntax, BoundExpression loweredLeft, BoundExpression loweredRight) { SpecialMember member = (loweredLeft.Type.SpecialType == SpecialType.System_String && loweredRight.Type.SpecialType == SpecialType.System_String) ? SpecialMember.System_String__ConcatStringString : SpecialMember.System_String__ConcatObjectObject; var method = GetSpecialTypeMethod(syntax, member); Debug.Assert((object)method != null); return((BoundExpression)BoundCall.Synthesized(syntax, null, method, loweredLeft, loweredRight)); }
protected abstract bool TryGetReceiverAndMember(BoundExpression expr, out BoundExpression?receiver, [NotNullWhen(true)] out Symbol?member);
private BoundExpression HoistExpression( BoundExpression expr, AwaitExpressionSyntax awaitSyntaxOpt, int syntaxOffset, bool isRef, 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, false, sideEffects, hoistedFields, ref needsSacrificialEvaluation); var indices = ArrayBuilder <BoundExpression> .GetInstance(); foreach (var index in array.Indices) { indices.Add(HoistExpression(index, awaitSyntaxOpt, syntaxOffset, false, 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 (isRef || field.FieldSymbol.IsReadOnly) { return(expr); } goto default; } if (!isRef) { goto default; } var isFieldOfStruct = !field.FieldSymbol.ContainingType.IsReferenceType; var receiver = HoistExpression(field.ReceiverOpt, awaitSyntaxOpt, syntaxOffset, isFieldOfStruct, 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.DefaultOperator: return(expr); default: if (expr.ConstantValue != null) { return(expr); } if (isRef) { 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 = -1; if (slotAllocatorOpt != null) { slotIndex = slotAllocatorOpt.GetPreviousHoistedLocalSlotIndex(awaitSyntaxOpt, (Cci.ITypeReference)fieldType, kind, id); } if (slotIndex == -1) { slotIndex = nextFreeHoistedLocalSlot++; } string fieldName = GeneratedNames.MakeHoistedLocalFieldName(kind, slotIndex); hoistedField = F.StateMachineField(expr.Type, fieldName, new LocalSlotDebugInfo(kind, id), slotIndex); } else { hoistedField = GetOrAllocateReusableHoistedField(fieldType); } hoistedFields.Add(hoistedField); var replacement = F.Field(F.This(), hoistedField); sideEffects.Add(F.AssignmentExpression(replacement, expr)); return(replacement); } }
protected override void PropertySetter(BoundExpression node, BoundExpression receiver, MethodSymbol setter, BoundExpression value = null) { base.PropertySetter(node, receiver, setter, value); if (receiver is null || receiver is BoundThisReference) { ApplyMemberPostConditions(setter.ContainingType, setter.NotNullMembers, notNullWhenTrueMembers: default, notNullWhenFalseMembers: default);
/// <remarks> /// This method implements best type inference for the conditional operator ?:. /// NOTE: If either expression is an error type, we return error type as the inference result. /// </remarks> public static TypeSymbol InferBestTypeForConditionalOperator( BoundExpression expr1, BoundExpression expr2, ConversionsBase conversions, out bool hadMultipleCandidates, out bool hadNullabilityMismatch, ref HashSet <DiagnosticInfo> useSiteDiagnostics) { // SPEC: The second and third operands, x and y, of the ?: operator control the type of the conditional expression. // SPEC: • If x has type X and y has type Y then // SPEC: o If an implicit conversion (§6.1) exists from X to Y, but not from Y to X, then Y is the type of the conditional expression. // SPEC: o If an implicit conversion (§6.1) exists from Y to X, but not from X to Y, then X is the type of the conditional expression. // SPEC: o Otherwise, no expression type can be determined, and a compile-time error occurs. // SPEC: • If only one of x and y has a type, and both x and y, are implicitly convertible to that type, then that is the type of the conditional expression. // SPEC: • Otherwise, no expression type can be determined, and a compile-time error occurs. // A type is a candidate if all expressions are convertible to that type. ArrayBuilder <TypeSymbol> candidateTypes = ArrayBuilder <TypeSymbol> .GetInstance(); try { var conversionsWithoutNullability = conversions.WithNullability(false); TypeSymbol type1 = expr1.Type; if ((object)type1 != null) { if (type1.IsErrorType()) { hadMultipleCandidates = false; hadNullabilityMismatch = false; return(type1); } if (conversionsWithoutNullability.ClassifyImplicitConversionFromExpression(expr2, type1, ref useSiteDiagnostics).Exists) { candidateTypes.Add(type1); } } TypeSymbol type2 = expr2.Type; if ((object)type2 != null) { if (type2.IsErrorType()) { hadMultipleCandidates = false; hadNullabilityMismatch = false; return(type2); } if (conversionsWithoutNullability.ClassifyImplicitConversionFromExpression(expr1, type2, ref useSiteDiagnostics).Exists) { candidateTypes.Add(type2); } } hadMultipleCandidates = candidateTypes.Count > 1; return(GetBestType(candidateTypes, conversions, out hadNullabilityMismatch, ref useSiteDiagnostics)); } finally { candidateTypes.Free(); } }
/// <summary> /// The strategy of this rewrite is to do rewrite "locally". /// We analyze arguments of the concat in a shallow fasion 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(CSharpSyntaxNode 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)); } // 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); }
private BoundExpression MakeBuiltInIncrementOperator(BoundIncrementOperator node, BoundExpression rewrittenValueToIncrement) { BoundExpression result; // If we have a built-in increment or decrement then things get a bit trickier. Suppose for example we have // a user-defined conversion from X to short and from short to X, but no user-defined increment operator on // X. The increment portion of "++x" is then: (X)(short)((int)(short)x + 1). That is, first x must be // converted to short via an implicit user- defined conversion, then to int via an implicit numeric // conversion, then the addition is performed in integers. The resulting integer is converted back to short, // and then the short is converted to X. // This is the input and output type of the unary increment operator we're going to call. // That is, "short" in the example above. TypeSymbol unaryOperandType = GetUnaryOperatorType(node); // This is the kind of binary operator that we're going to realize the unary operator // as. That is, "int + int --> int" in the example above. BinaryOperatorKind binaryOperatorKind = GetCorrespondingBinaryOperator(node); binaryOperatorKind |= IsIncrement(node) ? BinaryOperatorKind.Addition : BinaryOperatorKind.Subtraction; // The "1" in the example above. ConstantValue constantOne = GetConstantOneForBinOp(binaryOperatorKind); Debug.Assert(constantOne != null); Debug.Assert(constantOne.SpecialType != SpecialType.None); Debug.Assert(binaryOperatorKind.OperandTypes() != 0); // The input/output type of the binary operand. "int" in the example. TypeSymbol binaryOperandType = _compilation.GetSpecialType(constantOne.SpecialType); // 1 BoundExpression boundOne = MakeLiteral( syntax: node.Syntax, constantValue: constantOne, type: binaryOperandType); if (binaryOperatorKind.IsLifted()) { binaryOperandType = _compilation.GetSpecialType(SpecialType.System_Nullable_T).Construct(binaryOperandType); MethodSymbol ctor = UnsafeGetNullableMethod(node.Syntax, binaryOperandType, SpecialMember.System_Nullable_T__ctor); boundOne = new BoundObjectCreationExpression(node.Syntax, ctor, boundOne); } // Now we construct the other operand to the binary addition. We start with just plain "x". BoundExpression binaryOperand = rewrittenValueToIncrement; bool @checked = node.OperatorKind.IsChecked(); // If we need to make a conversion from the original operand type to the operand type of the // underlying increment operation, do it now. if (!node.OperandConversion.IsIdentity) { // (short)x binaryOperand = MakeConversionNode( syntax: node.Syntax, rewrittenOperand: binaryOperand, conversion: node.OperandConversion, rewrittenType: unaryOperandType, @checked: @checked); } // Early-out for pointer increment - we don't need to convert the operands to a common type. if (node.OperatorKind.OperandTypes() == UnaryOperatorKind.Pointer) { Debug.Assert(binaryOperatorKind.OperandTypes() == BinaryOperatorKind.PointerAndInt); Debug.Assert(binaryOperand.Type.IsPointerType()); Debug.Assert(boundOne.Type.SpecialType == SpecialType.System_Int32); return(MakeBinaryOperator(node.Syntax, binaryOperatorKind, binaryOperand, boundOne, binaryOperand.Type, method: null)); } // If we need to make a conversion from the unary operator type to the binary operator type, // do it now. // (int)(short)x binaryOperand = MakeConversionNode(binaryOperand, binaryOperandType, @checked); // Perform the addition. // (int)(short)x + 1 BoundExpression binOp; if (unaryOperandType.SpecialType == SpecialType.System_Decimal) { binOp = MakeDecimalIncDecOperator(node.Syntax, binaryOperatorKind, binaryOperand); } else if (unaryOperandType.IsNullableType() && unaryOperandType.GetNullableUnderlyingType().SpecialType == SpecialType.System_Decimal) { binOp = MakeLiftedDecimalIncDecOperator(node.Syntax, binaryOperatorKind, binaryOperand); } else { binOp = MakeBinaryOperator(node.Syntax, binaryOperatorKind, binaryOperand, boundOne, binaryOperandType, method: null); } // Generate the conversion back to the type of the unary operator. // (short)((int)(short)x + 1) result = MakeConversionNode(binOp, unaryOperandType, @checked); return(result); }
private BoundExpression MakeUnaryOperator( BoundUnaryOperator oldNode, UnaryOperatorKind kind, SyntaxNode syntax, MethodSymbol method, BoundExpression loweredOperand, TypeSymbol type) { if (kind.IsDynamic()) { Debug.Assert(kind == UnaryOperatorKind.DynamicTrue && type.SpecialType == SpecialType.System_Boolean || type.IsDynamic()); Debug.Assert((object)method == null); // Logical operators on boxed Boolean constants: var constant = UnboxConstant(loweredOperand); if (constant == ConstantValue.True || constant == ConstantValue.False) { if (kind == UnaryOperatorKind.DynamicTrue) { return(_factory.Literal(constant.BooleanValue)); } else if (kind == UnaryOperatorKind.DynamicLogicalNegation) { return(MakeConversionNode(_factory.Literal(!constant.BooleanValue), type, @checked: false)); } } return(_dynamicFactory.MakeDynamicUnaryOperator(kind, loweredOperand, type).ToExpression()); } else if (kind.IsLifted()) { if (!_inExpressionLambda) { return(LowerLiftedUnaryOperator(kind, syntax, method, loweredOperand, type)); } } else if (kind.IsUserDefined()) { Debug.Assert((object)method != null); Debug.Assert(type == method.ReturnType); if (!_inExpressionLambda || kind == UnaryOperatorKind.UserDefinedTrue || kind == UnaryOperatorKind.UserDefinedFalse) { return(BoundCall.Synthesized(syntax, null, method, loweredOperand)); } } else if (kind.Operator() == UnaryOperatorKind.UnaryPlus) { // We do not call the operator even for decimal; we simply optimize it away entirely. return(loweredOperand); } if (kind == UnaryOperatorKind.EnumBitwiseComplement) { var underlyingType = loweredOperand.Type.GetEnumUnderlyingType(); var upconvertSpecialType = Binder.GetEnumPromotedType(underlyingType.SpecialType); var upconvertType = upconvertSpecialType == underlyingType.SpecialType ? underlyingType : _compilation.GetSpecialType(upconvertSpecialType); var newOperand = MakeConversionNode(loweredOperand, upconvertType, false); UnaryOperatorKind newKind = kind.Operator().WithType(upconvertSpecialType); var newNode = (oldNode != null) ? oldNode.Update( newKind, newOperand, oldNode.ConstantValueOpt, method, newOperand.ResultKind, upconvertType) : new BoundUnaryOperator( syntax, newKind, newOperand, null, method, LookupResultKind.Viable, upconvertType); return(MakeConversionNode(newNode.Syntax, newNode, Conversion.ExplicitEnumeration, type, @checked: false)); } if (kind == UnaryOperatorKind.DecimalUnaryMinus) { method = (MethodSymbol)_compilation.Assembly.GetSpecialTypeMember(SpecialMember.System_Decimal__op_UnaryNegation); if (!_inExpressionLambda) { return(BoundCall.Synthesized(syntax, null, method, loweredOperand)); } } return((oldNode != null) ? oldNode.Update(kind, loweredOperand, oldNode.ConstantValueOpt, method, oldNode.ResultKind, type) : new BoundUnaryOperator(syntax, kind, loweredOperand, null, method, LookupResultKind.Viable, type)); }
private BoundBlock VisitAwaitExpression( BoundAwaitExpression node, BoundExpression resultPlace ) { var expression = (BoundExpression)Visit(node.Expression); var awaitablePlaceholder = node.AwaitableInfo.AwaitableInstancePlaceholder; if (awaitablePlaceholder != null) { _placeholderMap.Add(awaitablePlaceholder, expression); } var getAwaiter = node.AwaitableInfo.IsDynamic ? MakeCallMaybeDynamic(expression, null, WellKnownMemberNames.GetAwaiter) : (BoundExpression)Visit(node.AwaitableInfo.GetAwaiter); resultPlace = (BoundExpression)Visit(resultPlace); MethodSymbol getResult = VisitMethodSymbol(node.AwaitableInfo.GetResult); MethodSymbol isCompletedMethod = ((object)node.AwaitableInfo.IsCompleted != null) ? VisitMethodSymbol(node.AwaitableInfo.IsCompleted.GetMethod) : null; TypeSymbol type = VisitType(node.Type); if (awaitablePlaceholder != null) { _placeholderMap.Remove(awaitablePlaceholder); } // The awaiter temp facilitates EnC method remapping and thus have to be long-lived. // It transfers the awaiter objects from the old version of the MoveNext method to the new one. Debug.Assert( node.Syntax.IsKind(SyntaxKind.AwaitExpression) || node.WasCompilerGenerated ); var awaiterTemp = F.SynthesizedLocal( getAwaiter.Type, syntax: node.Syntax, kind: SynthesizedLocalKind.Awaiter ); var awaitIfIncomplete = F.Block( // temp $awaiterTemp = <expr>.GetAwaiter(); F.Assignment(F.Local(awaiterTemp), getAwaiter), // hidden sequence point facilitates EnC method remapping, see explanation on SynthesizedLocalKind.Awaiter: F.HiddenSequencePoint(), // if(!($awaiterTemp.IsCompleted)) { ... } F.If( condition: F.Not(GenerateGetIsCompleted(awaiterTemp, isCompletedMethod)), thenClause: GenerateAwaitForIncompleteTask(awaiterTemp) ) ); BoundExpression getResultCall = MakeCallMaybeDynamic( F.Local(awaiterTemp), getResult, WellKnownMemberNames.GetResult, resultsDiscarded: resultPlace == null ); // [$resultPlace = ] $awaiterTemp.GetResult(); BoundStatement getResultStatement = resultPlace != null && !type.IsVoidType() ? F.Assignment(resultPlace, getResultCall) : F.ExpressionStatement(getResultCall); return(F.Block( ImmutableArray.Create(awaiterTemp), awaitIfIncomplete, getResultStatement )); }
/// <summary> /// digs into known concat operators and unwraps their arguments /// otherwise returns the expression as-is /// /// Generally we only need to recognize same node patterns that we create as a result of concatenation rewrite. /// </summary> private void FlattenConcatArg(BoundExpression lowered, ArrayBuilder <BoundExpression> flattened) { switch (lowered.Kind) { case BoundKind.Call: var boundCall = (BoundCall)lowered; var method = boundCall.Method; if (method.IsStatic && method.ContainingType.SpecialType == SpecialType.System_String) { if ((object)method == (object)this.compilation.GetSpecialTypeMember(SpecialMember.System_String__ConcatStringString) || (object)method == (object)this.compilation.GetSpecialTypeMember(SpecialMember.System_String__ConcatStringStringString) || (object)method == (object)this.compilation.GetSpecialTypeMember(SpecialMember.System_String__ConcatStringStringStringString) || (object)method == (object)this.compilation.GetSpecialTypeMember(SpecialMember.System_String__ConcatObject) || (object)method == (object)this.compilation.GetSpecialTypeMember(SpecialMember.System_String__ConcatObjectObject) || (object)method == (object)this.compilation.GetSpecialTypeMember(SpecialMember.System_String__ConcatObjectObjectObject)) { flattened.AddRange(boundCall.Arguments); return; } if ((object)method == (object)this.compilation.GetSpecialTypeMember(SpecialMember.System_String__ConcatStringArray) || (object)method == (object)this.compilation.GetSpecialTypeMember(SpecialMember.System_String__ConcatObjectArray)) { var args = boundCall.Arguments[0] as BoundArrayCreation; if (args != null) { var initializer = args.InitializerOpt; if (initializer != null) { flattened.AddRange(initializer.Initializers); return; } } } } break; case BoundKind.NullCoalescingOperator: var boundCoalesce = (BoundNullCoalescingOperator)lowered; if (boundCoalesce.LeftConversion.IsIdentity) { // The RHS may be a constant value with an identity conversion to string even // if it is not a string: in particular, the null literal behaves this way. // To be safe, check that the constant value is actually a string before // attempting to access its value as a string. var rightConstant = boundCoalesce.RightOperand.ConstantValue; if (rightConstant != null && rightConstant.IsString && rightConstant.StringValue.Length == 0) { flattened.Add(boundCoalesce.LeftOperand); return; } } break; } // fallback - if nothing above worked, leave arg as-is flattened.Add(lowered); return; }
/// <summary> /// Strangely enough there is such a thing as unary concatenation and it must be rewritten. /// </summary> private BoundExpression RewriteStringConcatenationOneExpr(CSharpSyntaxNode syntax, BoundExpression loweredOperand) { if (loweredOperand.Type.SpecialType == SpecialType.System_String) { // loweredOperand ?? "" return(factory.Coalesce(loweredOperand, factory.Literal(""))); } var method = GetSpecialTypeMethod(syntax, SpecialMember.System_String__ConcatObject); Debug.Assert((object)method != null); return((BoundExpression)BoundCall.Synthesized(syntax, null, method, loweredOperand)); }
private BoundExpression MakeUnaryOperator( BoundUnaryOperator oldNode, UnaryOperatorKind kind, CSharpSyntaxNode syntax, MethodSymbol method, BoundExpression loweredOperand, TypeSymbol type) { if (kind.IsDynamic()) { Debug.Assert(kind == UnaryOperatorKind.DynamicTrue && type.SpecialType == SpecialType.System_Boolean || type.IsDynamic()); Debug.Assert((object)method == null); // Logical operators on boxed Boolean constants: var constant = UnboxConstant(loweredOperand); if (constant == ConstantValue.True || constant == ConstantValue.False) { if (kind == UnaryOperatorKind.DynamicTrue) { return(_factory.Literal(constant.BooleanValue)); } else if (kind == UnaryOperatorKind.DynamicLogicalNegation) { return(MakeConversionNode(_factory.Literal(!constant.BooleanValue), type, @checked: false)); } } return(_dynamicFactory.MakeDynamicUnaryOperator(kind, loweredOperand, type).ToExpression()); } else if (kind.IsLifted()) { if (!_inExpressionLambda) { return(LowerLiftedUnaryOperator(kind, syntax, method, loweredOperand, type)); } } else if (kind.IsUserDefined()) { Debug.Assert((object)method != null); Debug.Assert(type == method.ReturnType); if (!_inExpressionLambda || kind == UnaryOperatorKind.UserDefinedTrue || kind == UnaryOperatorKind.UserDefinedFalse) { // @t-mawind // As usual, concept accesses need to be rewritten down to their // default() form. // Is this correct? It's mostly a copy over from the binary case, // but the unary case is different enough to make me nervous. if (method is SynthesizedWitnessMethodSymbol) { return(BoundCall.Synthesized(syntax, SynthesizeWitnessInvocationReceiver(syntax, ((SynthesizedWitnessMethodSymbol)method).Parent), method, loweredOperand)); } return(BoundCall.Synthesized(syntax, null, method, loweredOperand)); } } else if (kind.Operator() == UnaryOperatorKind.UnaryPlus) { // We do not call the operator even for decimal; we simply optimize it away entirely. return(loweredOperand); } if (kind == UnaryOperatorKind.EnumBitwiseComplement) { var underlyingType = loweredOperand.Type.GetEnumUnderlyingType(); var upconvertSpecialType = Binder.GetEnumPromotedType(underlyingType.SpecialType); var upconvertType = upconvertSpecialType == underlyingType.SpecialType ? underlyingType : _compilation.GetSpecialType(upconvertSpecialType); var newOperand = MakeConversionNode(loweredOperand, upconvertType, false); UnaryOperatorKind newKind = kind.Operator().WithType(upconvertSpecialType); var newNode = (oldNode != null) ? oldNode.Update( newKind, newOperand, oldNode.ConstantValueOpt, method, newOperand.ResultKind, upconvertType) : new BoundUnaryOperator( syntax, newKind, newOperand, null, method, LookupResultKind.Viable, upconvertType); return(MakeConversionNode(newNode.Syntax, newNode, Conversion.ExplicitEnumeration, type, @checked: false)); } if (kind == UnaryOperatorKind.DecimalUnaryMinus) { method = (MethodSymbol)_compilation.Assembly.GetSpecialTypeMember(SpecialMember.System_Decimal__op_UnaryNegation); if (!_inExpressionLambda) { return(BoundCall.Synthesized(syntax, null, method, loweredOperand)); } } return((oldNode != null) ? oldNode.Update(kind, loweredOperand, oldNode.ConstantValueOpt, method, oldNode.ResultKind, type) : new BoundUnaryOperator(syntax, kind, loweredOperand, null, method, LookupResultKind.Viable, type)); }
public BoundDecisionDag SimplifyDecisionDagIfConstantInput(BoundExpression input) { if (input.ConstantValue == null) { return(this); } else { ConstantValue inputConstant = input.ConstantValue; return(Rewrite(makeReplacement)); // Make a replacement for a given node, using the precomputed replacements for its successors. BoundDecisionDagNode makeReplacement(BoundDecisionDagNode dag, Func <BoundDecisionDagNode, BoundDecisionDagNode> replacement) { if (dag is BoundTestDecisionDagNode p) { // This is the key to the optimization. The result of a top-level test might be known if the input is constant. switch (knownResult(p.Test)) { case true: return(replacement(p.WhenTrue)); case false: return(replacement(p.WhenFalse)); } } return(TrivialReplacement(dag, replacement)); } // Is the decision's result known because the input is a constant? bool?knownResult(BoundDagTest choice) { if (!choice.Input.IsOriginalInput) { // This is a test of something other than the main input; result unknown return(null); } switch (choice) { case BoundDagExplicitNullTest d: return(inputConstant.IsNull); case BoundDagNonNullTest d: return(!inputConstant.IsNull); case BoundDagValueTest d: return(d.Value == inputConstant); case BoundDagTypeTest d: return(inputConstant.IsNull ? (bool?)false : null); case BoundDagRelationalTest d: var f = ValueSetFactory.ForType(input.Type); if (f is null) { return(null); } // TODO: When ValueSetFactory has a method for comparing two values, use it. var set = f.Related(d.Relation.Operator(), d.Value); return(set.Any(BinaryOperatorKind.Equal, inputConstant)); default: throw ExceptionUtilities.UnexpectedValue(choice); } } } }
public ExpressionAndDiagnostics(BoundExpression expression, ImmutableArray <Diagnostic> diagnostics) { this.Expression = expression; this.Diagnostics = diagnostics; }
private BoundExpression MakeLiftedDecimalIncDecOperator(SyntaxNode syntax, BinaryOperatorKind oper, BoundExpression operand) { Debug.Assert(operand.Type.IsNullableType() && operand.Type.GetNullableUnderlyingType().SpecialType == SpecialType.System_Decimal); // This method assumes that operand is already a temporary and so there is no need to copy it again. MethodSymbol method = GetDecimalIncDecOperator(oper); MethodSymbol getValueOrDefault = UnsafeGetNullableMethod(syntax, operand.Type, SpecialMember.System_Nullable_T_GetValueOrDefault); MethodSymbol ctor = UnsafeGetNullableMethod(syntax, operand.Type, SpecialMember.System_Nullable_T__ctor); // x.HasValue BoundExpression condition = MakeNullableHasValue(syntax, operand); // x.GetValueOrDefault() BoundExpression getValueCall = BoundCall.Synthesized(syntax, operand, getValueOrDefault); // op_Inc(x.GetValueOrDefault()) BoundExpression methodCall = BoundCall.Synthesized(syntax, null, method, getValueCall); // new decimal?(op_Inc(x.GetValueOrDefault())) BoundExpression consequence = new BoundObjectCreationExpression(syntax, ctor, methodCall); // default(decimal?) BoundExpression alternative = new BoundDefaultExpression(syntax, null, operand.Type); // x.HasValue ? new decimal?(op_Inc(x.GetValueOrDefault())) : default(decimal?) return(RewriteConditionalOperator(syntax, condition, consequence, alternative, ConstantValue.NotAvailable, operand.Type)); }
public BoundSpillSequenceBuilder(BoundExpression value = null) : base(SpillSequenceBuilder, null, value?.Type) { Debug.Assert(value == null || value.Kind != SpillSequenceBuilder); this.Value = value; }
private BoundExpression GetLiftedUnaryOperatorConsequence(UnaryOperatorKind kind, SyntaxNode syntax, MethodSymbol method, TypeSymbol type, BoundExpression nonNullOperand) { MethodSymbol ctor = UnsafeGetNullableMethod(syntax, type, SpecialMember.System_Nullable_T__ctor); // OP(temp.GetValueOrDefault()) BoundExpression unliftedOp = MakeUnaryOperator( oldNode: null, kind: kind.Unlifted(), syntax: syntax, method: method, loweredOperand: nonNullOperand, type: type.GetNullableUnderlyingType()); // new R?(OP(temp.GetValueOrDefault())) BoundExpression consequence = new BoundObjectCreationExpression( syntax, ctor, unliftedOp); return(consequence); }
private static BoundExpression UpdateExpression(BoundSpillSequenceBuilder builder, BoundExpression expression) { if (builder == null) { return(expression); } Debug.Assert(builder.Value == null); if (!builder.HasLocals && !builder.HasStatements) { builder.Free(); return(expression); } return(builder.Update(expression)); }
private BoundExpression OptimizeLiftedUnaryOperator( UnaryOperatorKind operatorKind, SyntaxNode syntax, MethodSymbol method, BoundExpression loweredOperand, TypeSymbol type) { if (NullableNeverHasValue(loweredOperand)) { return(new BoundDefaultExpression(syntax, null, type)); } // Second, another simple optimization. If we know that the operand is never null // then we can obtain the non-null value and skip generating the temporary. That is, // "~(new int?(M()))" is the same as "new int?(~M())". BoundExpression neverNull = NullableAlwaysHasValue(loweredOperand); if (neverNull != null) { return(GetLiftedUnaryOperatorConsequence(operatorKind, syntax, method, type, neverNull)); } var conditionalLeft = loweredOperand as BoundLoweredConditionalAccess; // NOTE: we could in theory handle side-effecting loweredRight here too // by including it as a part of whenNull, but there is a concern // that it can lead to code duplication var optimize = conditionalLeft != null && (conditionalLeft.WhenNullOpt == null || conditionalLeft.WhenNullOpt.IsDefaultValue()); if (optimize) { var result = LowerLiftedUnaryOperator(operatorKind, syntax, method, conditionalLeft.WhenNotNull, type); return(conditionalLeft.Update( conditionalLeft.Receiver, conditionalLeft.HasValueMethodOpt, whenNotNull: result, whenNullOpt: null, id: conditionalLeft.Id, type: result.Type )); } // This optimization is analogous to DistributeLiftedConversionIntoLiftedOperand. // Suppose we have a lifted unary conversion whose operand is itself a lifted operation. // That is, we have something like: // // int? r = - (M() + N()); // // where M() and N() return nullable ints. We would simply codegen this as first // creating the nullable int result of M() + N(), then checking it for nullity, // and then doing the unary minus. That is: // // int? m = M(); // int? n = N(); // int? t = m.HasValue && n.HasValue ? new int?(m.Value + n.Value) : new int?(); // int? r = t.HasValue ? new int?(-t.Value) : new int?(); // // However, we also observe that we can distribute the unary minus into both branches of // the conditional: // // int? m = M(); // int? n = N(); // int? r = m.HasValue && n.HasValue ? - (new int?(m.Value + n.Value))) : - new int?(); // // And we already optimize those! So we could reduce this to: // // int? m = M(); // int? n = N(); // int? r = m.HasValue && n.HasValue ? new int?(- (m.Value + n.Value)) : new int?()); // // which avoids entirely the creation of the unnecessary nullable int and the unnecessary // extra null check. if (loweredOperand.Kind == BoundKind.Sequence) { BoundSequence seq = (BoundSequence)loweredOperand; if (seq.Value.Kind == BoundKind.ConditionalOperator) { BoundConditionalOperator conditional = (BoundConditionalOperator)seq.Value; Debug.Assert(seq.Type == conditional.Type); Debug.Assert(conditional.Type == conditional.Consequence.Type); Debug.Assert(conditional.Type == conditional.Alternative.Type); if (NullableAlwaysHasValue(conditional.Consequence) != null && NullableNeverHasValue(conditional.Alternative)) { return(new BoundSequence( syntax, seq.Locals, seq.SideEffects, RewriteConditionalOperator( syntax, conditional.Condition, MakeUnaryOperator(operatorKind, syntax, method, conditional.Consequence, type), MakeUnaryOperator(operatorKind, syntax, method, conditional.Alternative, type), ConstantValue.NotAvailable, type), type)); } } } return(null); }
private BoundExpression MakeUserDefinedIncrementOperator(BoundIncrementOperator node, BoundExpression rewrittenValueToIncrement) { Debug.Assert((object)node.MethodOpt != null); Debug.Assert(node.MethodOpt.ParameterCount == 1); bool isLifted = node.OperatorKind.IsLifted(); bool @checked = node.OperatorKind.IsChecked(); BoundExpression rewrittenArgument = rewrittenValueToIncrement; SyntaxNode syntax = node.Syntax; TypeSymbol type = node.MethodOpt.ParameterTypes[0]; if (isLifted) { type = _compilation.GetSpecialType(SpecialType.System_Nullable_T).Construct(type); Debug.Assert(node.MethodOpt.ParameterTypes[0] == node.MethodOpt.ReturnType); } if (!node.OperandConversion.IsIdentity) { rewrittenArgument = MakeConversionNode( syntax: syntax, rewrittenOperand: rewrittenValueToIncrement, conversion: node.OperandConversion, rewrittenType: type, @checked: @checked); } if (!isLifted) { return(BoundCall.Synthesized(syntax, null, node.MethodOpt, rewrittenArgument)); } // S? temp = operand; // S? r = temp.HasValue ? // new S?(op_Increment(temp.GetValueOrDefault())) : // default(S?); // Unlike the other unary operators, we do not attempt to optimize nullable user-defined // increment or decrement. The operand is a variable (or property), and so we do not know if // it is always null/never null. BoundAssignmentOperator tempAssignment; BoundLocal boundTemp = _factory.StoreToTemp(rewrittenArgument, out tempAssignment); MethodSymbol getValueOrDefault = UnsafeGetNullableMethod(syntax, type, SpecialMember.System_Nullable_T_GetValueOrDefault); MethodSymbol ctor = UnsafeGetNullableMethod(syntax, type, SpecialMember.System_Nullable_T__ctor); // temp.HasValue BoundExpression condition = MakeNullableHasValue(node.Syntax, boundTemp); // temp.GetValueOrDefault() BoundExpression call_GetValueOrDefault = BoundCall.Synthesized(syntax, boundTemp, getValueOrDefault); // op_Increment(temp.GetValueOrDefault()) BoundExpression userDefinedCall = BoundCall.Synthesized(syntax, null, node.MethodOpt, call_GetValueOrDefault); // new S?(op_Increment(temp.GetValueOrDefault())) BoundExpression consequence = new BoundObjectCreationExpression(syntax, ctor, userDefinedCall); // default(S?) BoundExpression alternative = new BoundDefaultExpression(syntax, null, type); // temp.HasValue ? // new S?(op_Increment(temp.GetValueOrDefault())) : // default(S?); BoundExpression conditionalExpression = RewriteConditionalOperator( syntax: syntax, rewrittenCondition: condition, rewrittenConsequence: consequence, rewrittenAlternative: alternative, constantValueOpt: null, rewrittenType: type); // temp = operand; // temp.HasValue ? // new S?(op_Increment(temp.GetValueOrDefault())) : // default(S?); return(new BoundSequence( syntax: syntax, locals: ImmutableArray.Create <LocalSymbol>(boundTemp.LocalSymbol), sideEffects: ImmutableArray.Create <BoundExpression>(tempAssignment), value: conditionalExpression, type: type)); }
/// <summary> /// folds two concat operands into one expression if possible /// otherwise returns null /// </summary> private BoundExpression TryFoldTwoConcatOperands(CSharpSyntaxNode syntax, BoundExpression loweredLeft, BoundExpression loweredRight) { // both left and right are constants var leftConst = loweredLeft.ConstantValue; var rightConst = loweredRight.ConstantValue; if (leftConst != null && rightConst != null) { // const concat may fail to fold if strings are huge. // This would be unusual. ConstantValue concatenated = TryFoldTwoConcatConsts(leftConst, rightConst); if (concatenated != null) { return(factory.StringLiteral(concatenated)); } } // one or another is null. if (IsNullOrEmptyStringConstant(loweredLeft)) { if (IsNullOrEmptyStringConstant(loweredRight)) { return(factory.Literal((string)null + (string)null)); } return(RewriteStringConcatenationOneExpr(syntax, loweredRight)); } else if (IsNullOrEmptyStringConstant(loweredRight)) { return(RewriteStringConcatenationOneExpr(syntax, loweredLeft)); } return(null); }
/// <summary> /// The rewrites are as follows: suppose the operand x is a variable of type X. The /// chosen increment/decrement operator is modelled as a static method on a type T, /// which takes a value of type T and returns the result of incrementing or decrementing /// that value. /// /// x++ /// X temp = x /// x = (X)(T.Increment((T)temp)) /// return temp /// x-- /// X temp = x /// x = (X)(T.Decrement((T)temp)) /// return temp /// ++x /// X temp = (X)(T.Increment((T)x)) /// x = temp /// return temp /// --x /// X temp = (X)(T.Decrement((T)x)) /// x = temp /// return temp /// /// Note: /// Dev11 implements dynamic prefix operators incorrectly. /// /// result = ++x.P is emitted as result = SetMember{"P"}(t, UnaryOperation{Inc}(GetMember{"P"}(x))) /// /// The difference is that Dev11 relies on SetMember returning the same value as it was given as an argument. /// Failing to do so changes the semantics of ++/-- operator which is undesirable. We emit the same pattern for /// both dynamic and static operators. /// /// For example, we might have a class X with user-defined implicit conversions /// to and from short, but no user-defined increment or decrement operators. We /// would bind x++ as "X temp = x; x = (X)(short)((int)(short)temp + 1); return temp;" /// </summary> /// <param name="node">The unary operator expression representing the increment/decrement.</param> /// <returns>A bound sequence that uses a temp to achieve the correct side effects and return value.</returns> public override BoundNode VisitIncrementOperator(BoundIncrementOperator node) { bool isPrefix = IsPrefix(node); bool isDynamic = node.OperatorKind.IsDynamic(); bool isChecked = node.OperatorKind.IsChecked(); ArrayBuilder <LocalSymbol> tempSymbols = ArrayBuilder <LocalSymbol> .GetInstance(); ArrayBuilder <BoundExpression> tempInitializers = ArrayBuilder <BoundExpression> .GetInstance(); SyntaxNode syntax = node.Syntax; // This will be filled in with the LHS that uses temporaries to prevent // double-evaluation of side effects. BoundExpression transformedLHS = TransformCompoundAssignmentLHS(node.Operand, tempInitializers, tempSymbols, isDynamic); TypeSymbol operandType = transformedLHS.Type; //type of the variable being incremented Debug.Assert(operandType == node.Type); LocalSymbol tempSymbol = _factory.SynthesizedLocal(operandType); tempSymbols.Add(tempSymbol); // Not adding an entry to tempInitializers because the initial value depends on the case. BoundExpression boundTemp = new BoundLocal( syntax: syntax, localSymbol: tempSymbol, constantValueOpt: null, type: operandType); // prefix: (X)(T.Increment((T)operand))) // postfix: (X)(T.Increment((T)temp))) var newValue = MakeIncrementOperator(node, rewrittenValueToIncrement: (isPrefix ? MakeRValue(transformedLHS) : boundTemp)); // there are two strategies for completing the rewrite. // The reason is that indirect assignments read the target of the assignment before evaluating // of the assignment value and that may cause reads of operand and boundTemp to cross which // in turn would require one of them to be a real temp (not a stack local) // // To avoid this issue, in a case of ByRef operand, we perform a "nested sequence" rewrite. // // Ex: // Seq{..., operand = Seq{temp = operand + 1, temp}, ...} // instead of // Seq{.... temp = operand + 1, operand = temp, ...} // // Such rewrite will nest reads of boundTemp relative to reads of operand so both // operand and boundTemp could be optimizable (subject to all other conditions of course). // // In a case of the non-byref operand we use a single-sequence strategy as it results in shorter // overall life time of temps and as such more appropriate. (problem of crossed reads does not affect that case) // if (IsIndirectOrInstanceField(transformedLHS)) { return(RewriteWithRefOperand(isPrefix, isChecked, tempSymbols, tempInitializers, syntax, transformedLHS, operandType, boundTemp, newValue)); } else { return(RewriteWithNotRefOperand(isPrefix, isChecked, tempSymbols, tempInitializers, syntax, transformedLHS, operandType, boundTemp, newValue)); } }
// Build Decimal.op_Increment((Decimal)operand) or Decimal.op_Decrement((Decimal)operand) private BoundExpression MakeDecimalIncDecOperator(SyntaxNode syntax, BinaryOperatorKind oper, BoundExpression operand) { Debug.Assert(operand.Type.SpecialType == SpecialType.System_Decimal); MethodSymbol method = GetDecimalIncDecOperator(oper); return(BoundCall.Synthesized(syntax, null, method, operand)); }
private BoundExpression RewriteStringConcatenationThreeExprs(CSharpSyntaxNode 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 = GetSpecialTypeMethod(syntax, member); Debug.Assert((object)method != null); return(BoundCall.Synthesized(syntax, null, method, ImmutableArray.Create(loweredFirst, loweredSecond, loweredThird))); }
/// <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(CSharpSyntaxNode syntax, BinaryOperatorKind operatorKind, BoundExpression loweredLeft, BoundExpression loweredRight, TypeSymbol type) { SpecialMember member = (operatorKind == BinaryOperatorKind.StringConcatenation) ? SpecialMember.System_String__ConcatStringString : SpecialMember.System_String__ConcatObjectObject; var method = GetSpecialTypeMethod(syntax, member); Debug.Assert((object)method != null); return(new BoundBinaryOperator(syntax, operatorKind, loweredLeft, loweredRight, default(ConstantValue), method, default(LookupResultKind), type)); }