private static BoundExpression RefAccessMustMakeCopy(BoundExpression visited) { visited = new BoundPassByCopy( visited.Syntax, visited, type: visited.Type); return(visited); }
public override BoundNode VisitPassByCopy(BoundPassByCopy node) { BoundSpillSequenceBuilder builder = null; var expression = VisitExpression(ref builder, node.Expression); return(UpdateExpression( builder, node.Update( expression, type: node.Type))); }
/// <summary> /// Returns an expression which converts the given expression into a string (or null). /// If necessary, this invokes .ToString() on the expression, to avoid boxing value types. /// </summary> private BoundExpression ConvertConcatExprToString(SyntaxNode syntax, BoundExpression expr) { // If it's a value type, it'll have been boxed by the +(string, object) or +(object, string) // operator. Undo that. if (expr.Kind == BoundKind.Conversion) { BoundConversion conv = (BoundConversion)expr; if (conv.ConversionKind == ConversionKind.Boxing) { expr = conv.Operand; } } // 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. // Similarly if it's a literal null, don't do anything special. if (expr.Kind == BoundKind.Literal) { ConstantValue cv = ((BoundLiteral)expr).ConstantValue; if (cv != null) { if (cv.SpecialType == SpecialType.System_Char) { return(_factory.StringLiteral(cv.CharValue.ToString())); } else if (cv.IsNull) { return(expr); } } } // If it's a string already, just return it if (expr.Type.IsStringType()) { return(expr); } // Evaluate toString at the last possible moment, to avoid spurious diagnostics if it's missing. // All code paths below here use it. var objectToStringMethod = UnsafeGetSpecialTypeMethod(syntax, SpecialMember.System_Object__ToString); // If it's a struct which has overridden ToString, find that method. Note that we might fail to // find it, e.g. if object.ToString is missing MethodSymbol structToStringMethod = null; if (expr.Type.IsValueType && !expr.Type.IsTypeParameter()) { var type = (NamedTypeSymbol)expr.Type; var typeToStringMembers = type.GetMembers(objectToStringMethod.Name); foreach (var member in typeToStringMembers) { if (member is MethodSymbol toStringMethod && toStringMethod.GetLeastOverriddenMethod(type) == (object)objectToStringMethod) { structToStringMethod = toStringMethod; break; } } } // If it's a special value type (and not a field of a MarshalByRef object), it should have its own ToString method (but we might fail to find // it if object.ToString is missing). Assume that this won't be removed, and emit a direct call rather // than a constrained virtual call. This keeps in the spirit of #7079, but expands the range of // types to all special value types. if (structToStringMethod != null && (expr.Type.SpecialType != SpecialType.None && !isFieldOfMarshalByRef(expr, _compilation))) { return(BoundCall.Synthesized(expr.Syntax, expr, structToStringMethod)); } // - It's a reference type (excluding unconstrained generics): no copy // - It's a constant: no copy // - The type definitely doesn't have its own ToString method (i.e. we're definitely calling // object.ToString on a struct type, not type parameter): no copy (yes this is a versioning issue, // but that doesn't matter) // - We're calling the type's own ToString method, and it's effectively readonly (the method or the whole // type is readonly): no copy // - Otherwise: copy // This is to mimic the old behaviour, where value types would be boxed before ToString was called on them, // but with optimizations for readonly methods. bool callWithoutCopy = expr.Type.IsReferenceType || expr.ConstantValue != null || (structToStringMethod == null && !expr.Type.IsTypeParameter()) || structToStringMethod?.IsEffectivelyReadOnly == true; // No need for a conditional access if it's a value type - we know it's not null. if (expr.Type.IsValueType) { if (!callWithoutCopy) { expr = new BoundPassByCopy(expr.Syntax, expr, expr.Type); } return(BoundCall.Synthesized(expr.Syntax, expr, objectToStringMethod)); } if (callWithoutCopy) { return(makeConditionalAccess(expr)); } else { // If we do conditional access on a copy, we need a proper BoundLocal rather than a // BoundPassByCopy (as it's accessed multiple times). If we don't do this, and the // receiver is an unconstrained generic parameter, BoundLoweredConditionalAccess has // to generate a lot of code to ensure it only accesses the copy once (which is pointless). var temp = _factory.StoreToTemp(expr, out var store); return(_factory.Sequence( ImmutableArray.Create(temp.LocalSymbol), ImmutableArray.Create <BoundExpression>(store), makeConditionalAccess(temp))); } BoundExpression makeConditionalAccess(BoundExpression receiver) { int currentConditionalAccessID = ++_currentConditionalAccessID; return(new BoundLoweredConditionalAccess( syntax, receiver, hasValueMethodOpt: null, whenNotNull: BoundCall.Synthesized( syntax, new BoundConditionalReceiver(syntax, currentConditionalAccessID, expr.Type), objectToStringMethod), whenNullOpt: null, id: currentConditionalAccessID, type: _compilation.GetSpecialType(SpecialType.System_String))); }