internal override BoundStatement BindUsingStatementParts(DiagnosticBag diagnostics, Binder originalBinder) { ExpressionSyntax expressionSyntax = TargetExpressionSyntax; VariableDeclarationSyntax declarationSyntax = _syntax.Declaration; bool hasAwait = _syntax.AwaitKeyword.Kind() != default; Debug.Assert((expressionSyntax == null) ^ (declarationSyntax == null)); // Can't have both or neither. TypeSymbol disposableInterface = getDisposableInterface(hasAwait); Debug.Assert((object)disposableInterface != null); bool hasErrors = ReportUseSiteDiagnostics(disposableInterface, diagnostics, hasAwait ? _syntax.AwaitKeyword : _syntax.UsingKeyword); Conversion iDisposableConversion = Conversion.NoConversion; BoundMultipleLocalDeclarations declarationsOpt = null; BoundExpression expressionOpt = null; AwaitableInfo awaitOpt = null; TypeSymbol declarationTypeOpt = null; if (expressionSyntax != null) { expressionOpt = this.BindTargetExpression(diagnostics, originalBinder); hasErrors |= !initConversion(fromExpression: true); } else { ImmutableArray <BoundLocalDeclaration> declarations; originalBinder.BindForOrUsingOrFixedDeclarations(declarationSyntax, LocalDeclarationKind.UsingVariable, diagnostics, out declarations); Debug.Assert(!declarations.IsEmpty); declarationsOpt = new BoundMultipleLocalDeclarations(declarationSyntax, declarations); declarationTypeOpt = declarations[0].DeclaredType.Type; if (declarationTypeOpt.IsDynamic()) { iDisposableConversion = Conversion.ImplicitDynamic; } else { hasErrors |= !initConversion(fromExpression: false); } } if (hasAwait) { TypeSymbol taskType = this.Compilation.GetWellKnownType(WellKnownType.System_Threading_Tasks_ValueTask); hasErrors |= ReportUseSiteDiagnostics(taskType, diagnostics, _syntax.AwaitKeyword); var resource = (SyntaxNode)expressionSyntax ?? declarationSyntax; BoundExpression placeholder = new BoundAwaitableValuePlaceholder(resource, taskType).MakeCompilerGenerated(); awaitOpt = BindAwaitInfo(placeholder, resource, _syntax.AwaitKeyword.GetLocation(), diagnostics, ref hasErrors); } BoundStatement boundBody = originalBinder.BindPossibleEmbeddedStatement(_syntax.Statement, diagnostics); Debug.Assert(GetDeclaredLocalsForScope(_syntax) == this.Locals); return(new BoundUsingStatement( _syntax, this.Locals, declarationsOpt, expressionOpt, iDisposableConversion, boundBody, awaitOpt, hasErrors)); bool initConversion(bool fromExpression) { HashSet <DiagnosticInfo> useSiteDiagnostics = null; iDisposableConversion = classifyConversion(fromExpression, disposableInterface, ref useSiteDiagnostics); diagnostics.Add(fromExpression ? (CSharpSyntaxNode)expressionSyntax : declarationSyntax, useSiteDiagnostics); if (iDisposableConversion.IsImplicit) { return(true); } TypeSymbol type = fromExpression ? expressionOpt.Type : declarationTypeOpt; if (type is null || !type.IsErrorType()) { // Retry with a different assumption about whether the `using` is async TypeSymbol alternateInterface = getDisposableInterface(!hasAwait); HashSet <DiagnosticInfo> ignored = null; Conversion alternateConversion = classifyConversion(fromExpression, alternateInterface, ref ignored); bool wrongAsync = alternateConversion.IsImplicit; ErrorCode errorCode = wrongAsync ? (hasAwait ? ErrorCode.ERR_NoConvToIAsyncDispWrongAsync : ErrorCode.ERR_NoConvToIDispWrongAsync) : (hasAwait ? ErrorCode.ERR_NoConvToIAsyncDisp : ErrorCode.ERR_NoConvToIDisp); Error(diagnostics, errorCode, (CSharpSyntaxNode)declarationSyntax ?? expressionSyntax, declarationTypeOpt ?? expressionOpt.Display); } return(false); } Conversion classifyConversion(bool fromExpression, TypeSymbol targetInterface, ref HashSet <DiagnosticInfo> diag) { return(fromExpression ? originalBinder.Conversions.ClassifyImplicitConversionFromExpression(expressionOpt, targetInterface, ref diag) : originalBinder.Conversions.ClassifyImplicitConversionFromType(declarationTypeOpt, targetInterface, ref diag)); } TypeSymbol getDisposableInterface(bool isAsync) { return(isAsync ? this.Compilation.GetWellKnownType(WellKnownType.System_IAsyncDisposable) : this.Compilation.GetSpecialType(SpecialType.System_IDisposable)); } }
internal override BoundStatement BindUsingStatementParts(DiagnosticBag diagnostics, Binder originalBinder) { ExpressionSyntax expressionSyntax = TargetExpressionSyntax; VariableDeclarationSyntax declarationSyntax = _syntax.Declaration; Debug.Assert((expressionSyntax == null) ^ (declarationSyntax == null)); // Can't have both or neither. bool hasErrors = false; BoundMultipleLocalDeclarations declarationsOpt = null; BoundExpression expressionOpt = null; Conversion iDisposableConversion = Conversion.NoConversion; TypeSymbol iDisposable = this.Compilation.GetSpecialType(SpecialType.System_IDisposable); // no need for diagnostics, so use the Compilation version Debug.Assert((object)iDisposable != null); if (expressionSyntax != null) { expressionOpt = this.BindTargetExpression(diagnostics, originalBinder); HashSet <DiagnosticInfo> useSiteDiagnostics = null; iDisposableConversion = originalBinder.Conversions.ClassifyImplicitConversionFromExpression(expressionOpt, iDisposable, ref useSiteDiagnostics); diagnostics.Add(expressionSyntax, useSiteDiagnostics); if (!iDisposableConversion.IsImplicit) { TypeSymbol expressionType = expressionOpt.Type; if ((object)expressionType == null || !expressionType.IsErrorType()) { Error(diagnostics, ErrorCode.ERR_NoConvToIDisp, expressionSyntax, expressionOpt.Display); } hasErrors = true; } } else { ImmutableArray <BoundLocalDeclaration> declarations; originalBinder.BindForOrUsingOrFixedDeclarations(declarationSyntax, LocalDeclarationKind.UsingVariable, diagnostics, out declarations); Debug.Assert(!declarations.IsEmpty); declarationsOpt = new BoundMultipleLocalDeclarations(declarationSyntax, declarations); TypeSymbol declType = declarations[0].DeclaredType.Type; if (declType.IsDynamic()) { iDisposableConversion = Conversion.ImplicitDynamic; } else { HashSet <DiagnosticInfo> useSiteDiagnostics = null; iDisposableConversion = originalBinder.Conversions.ClassifyImplicitConversionFromType(declType, iDisposable, ref useSiteDiagnostics); diagnostics.Add(declarationSyntax, useSiteDiagnostics); if (!iDisposableConversion.IsImplicit) { if (!declType.IsErrorType()) { Error(diagnostics, ErrorCode.ERR_NoConvToIDisp, declarationSyntax, declType); } hasErrors = true; } } } BoundStatement boundBody = originalBinder.BindPossibleEmbeddedStatement(_syntax.Statement, diagnostics); Debug.Assert(GetDeclaredLocalsForScope(_syntax) == this.Locals); return(new BoundUsingStatement( _syntax, this.Locals, declarationsOpt, expressionOpt, iDisposableConversion, boundBody, hasErrors)); }
/// <summary> /// Lower a foreach loop that will enumerate a collection using an enumerator. /// /// E e = ((C)(x)).GetEnumerator() /// try { /// while (e.MoveNext()) { /// V v = (V)(T)e.Current; /// // body /// } /// } /// finally { /// // clean up e /// } /// </summary> private BoundStatement RewriteEnumeratorForEachStatement(BoundForEachStatement node) { ForEachStatementSyntax forEachSyntax = (ForEachStatementSyntax)node.Syntax; ForEachEnumeratorInfo enumeratorInfo = node.EnumeratorInfoOpt; Debug.Assert(enumeratorInfo != null); BoundExpression collectionExpression = GetUnconvertedCollectionExpression(node); BoundExpression rewrittenExpression = (BoundExpression)Visit(collectionExpression); BoundStatement rewrittenBody = (BoundStatement)Visit(node.Body); TypeSymbol enumeratorType = enumeratorInfo.GetEnumeratorMethod.ReturnType; TypeSymbol elementType = enumeratorInfo.ElementType; // E e LocalSymbol enumeratorVar = factory.SynthesizedLocal(enumeratorType, syntax: forEachSyntax, kind: SynthesizedLocalKind.ForEachEnumerator); // Reference to e. BoundLocal boundEnumeratorVar = MakeBoundLocal(forEachSyntax, enumeratorVar, enumeratorType); // ((C)(x)).GetEnumerator() or (x).GetEnumerator(); BoundExpression enumeratorVarInitValue = SynthesizeCall(forEachSyntax, rewrittenExpression, enumeratorInfo.GetEnumeratorMethod, enumeratorInfo.CollectionConversion, enumeratorInfo.CollectionType); // E e = ((C)(x)).GetEnumerator(); BoundStatement enumeratorVarDecl = MakeLocalDeclaration(forEachSyntax, enumeratorVar, enumeratorVarInitValue); AddForEachExpressionSequencePoint(forEachSyntax, ref enumeratorVarDecl); // V v LocalSymbol iterationVar = node.IterationVariable; //(V)(T)e.Current BoundExpression iterationVarAssignValue = MakeConversion( syntax: forEachSyntax, rewrittenOperand: MakeConversion( syntax: forEachSyntax, rewrittenOperand: BoundCall.Synthesized( syntax: forEachSyntax, receiverOpt: boundEnumeratorVar, method: enumeratorInfo.CurrentPropertyGetter), conversion: enumeratorInfo.CurrentConversion, rewrittenType: elementType, @checked: node.Checked), conversion: node.ElementConversion, rewrittenType: iterationVar.Type, @checked: node.Checked); // V v = (V)(T)e.Current; BoundStatement iterationVarDecl = MakeLocalDeclaration(forEachSyntax, iterationVar, iterationVarAssignValue); AddForEachIterationVariableSequencePoint(forEachSyntax, ref iterationVarDecl); // while (e.MoveNext()) { // V v = (V)(T)e.Current; // /* node.Body */ // } BoundStatement whileLoop = RewriteWhileStatement( syntax: forEachSyntax, rewrittenCondition: BoundCall.Synthesized( syntax: forEachSyntax, receiverOpt: boundEnumeratorVar, method: enumeratorInfo.MoveNextMethod), conditionSequencePointSpan: forEachSyntax.InKeyword.Span, rewrittenBody: new BoundBlock(rewrittenBody.Syntax, statements: ImmutableArray.Create <BoundStatement>(iterationVarDecl, rewrittenBody), locals: ImmutableArray.Create <LocalSymbol>(iterationVar)), breakLabel: node.BreakLabel, continueLabel: node.ContinueLabel, hasErrors: false); BoundStatement result; MethodSymbol disposeMethod; if (enumeratorInfo.NeedsDisposeMethod && TryGetSpecialTypeMember(forEachSyntax, SpecialMember.System_IDisposable__Dispose, out disposeMethod)) { Binder.ReportDiagnosticsIfObsolete(diagnostics, disposeMethod, forEachSyntax, hasBaseReceiver: false, containingMember: this.factory.CurrentMethod, containingType: this.factory.CurrentClass, location: enumeratorInfo.Location); BoundBlock finallyBlockOpt; var idisposableTypeSymbol = disposeMethod.ContainingType; var conversions = new TypeConversions(this.factory.CurrentMethod.ContainingAssembly.CorLibrary); HashSet <DiagnosticInfo> useSiteDiagnostics = null; var isImplicit = conversions.ClassifyImplicitConversion(enumeratorType, idisposableTypeSymbol, ref useSiteDiagnostics).IsImplicit; diagnostics.Add(forEachSyntax, useSiteDiagnostics); if (isImplicit) { Debug.Assert(enumeratorInfo.NeedsDisposeMethod); Conversion receiverConversion = enumeratorType.IsStructType() ? Conversion.Boxing : Conversion.ImplicitReference; // ((IDisposable)e).Dispose(); or e.Dispose(); BoundStatement disposeCall = new BoundExpressionStatement(forEachSyntax, expression: SynthesizeCall(forEachSyntax, boundEnumeratorVar, disposeMethod, receiverConversion, idisposableTypeSymbol)); BoundStatement disposeStmt; if (enumeratorType.IsValueType) { // No way for the struct to be nullable and disposable. Debug.Assert(((TypeSymbol)enumeratorType.OriginalDefinition).SpecialType != SpecialType.System_Nullable_T); // For non-nullable structs, no null check is required. disposeStmt = disposeCall; } else { // NB: cast to object missing from spec. Needed to ignore user-defined operators and box type parameters. // if ((object)e != null) ((IDisposable)e).Dispose(); disposeStmt = RewriteIfStatement( syntax: forEachSyntax, rewrittenCondition: new BoundBinaryOperator(forEachSyntax, operatorKind: BinaryOperatorKind.NotEqual, left: MakeConversion( syntax: forEachSyntax, rewrittenOperand: boundEnumeratorVar, conversion: enumeratorInfo.EnumeratorConversion, rewrittenType: this.compilation.GetSpecialType(SpecialType.System_Object), @checked: false), right: MakeLiteral(forEachSyntax, constantValue: ConstantValue.Null, type: null), constantValueOpt: null, methodOpt: null, resultKind: LookupResultKind.Viable, type: this.compilation.GetSpecialType(SpecialType.System_Boolean)), rewrittenConsequence: disposeCall, rewrittenAlternativeOpt: null, hasErrors: false); } finallyBlockOpt = new BoundBlock(forEachSyntax, locals: ImmutableArray <LocalSymbol> .Empty, statements: ImmutableArray.Create <BoundStatement>(disposeStmt)); } else { Debug.Assert(!enumeratorType.IsSealed); // IDisposable d LocalSymbol disposableVar = factory.SynthesizedLocal(idisposableTypeSymbol); // Reference to d. BoundLocal boundDisposableVar = MakeBoundLocal(forEachSyntax, disposableVar, idisposableTypeSymbol); BoundTypeExpression boundIDisposableTypeExpr = new BoundTypeExpression(forEachSyntax, aliasOpt: null, type: idisposableTypeSymbol); // e as IDisposable BoundExpression disposableVarInitValue = new BoundAsOperator(forEachSyntax, operand: boundEnumeratorVar, targetType: boundIDisposableTypeExpr, conversion: Conversion.ExplicitReference, // Explicit so the emitter won't optimize it away. type: idisposableTypeSymbol); // IDisposable d = e as IDisposable; BoundStatement disposableVarDecl = MakeLocalDeclaration(forEachSyntax, disposableVar, disposableVarInitValue); // if (d != null) d.Dispose(); BoundStatement ifStmt = RewriteIfStatement( syntax: forEachSyntax, rewrittenCondition: new BoundBinaryOperator(forEachSyntax, operatorKind: BinaryOperatorKind.NotEqual, // reference equality left: boundDisposableVar, right: MakeLiteral(forEachSyntax, constantValue: ConstantValue.Null, type: null), constantValueOpt: null, methodOpt: null, resultKind: LookupResultKind.Viable, type: this.compilation.GetSpecialType(SpecialType.System_Boolean)), rewrittenConsequence: new BoundExpressionStatement(forEachSyntax, expression: BoundCall.Synthesized( syntax: forEachSyntax, receiverOpt: boundDisposableVar, method: disposeMethod)), rewrittenAlternativeOpt: null, hasErrors: false); // IDisposable d = e as IDisposable; // if (d != null) d.Dispose(); finallyBlockOpt = new BoundBlock(forEachSyntax, locals: ImmutableArray.Create <LocalSymbol>(disposableVar), statements: ImmutableArray.Create <BoundStatement>(disposableVarDecl, ifStmt)); } // try { // while (e.MoveNext()) { // V v = (V)(T)e.Current; // /* loop body */ // } // } // finally { // /* dispose of e */ // } BoundStatement tryFinally = new BoundTryStatement(forEachSyntax, tryBlock: new BoundBlock(forEachSyntax, locals: ImmutableArray <LocalSymbol> .Empty, statements: ImmutableArray.Create <BoundStatement>(whileLoop)), catchBlocks: ImmutableArray <BoundCatchBlock> .Empty, finallyBlockOpt: finallyBlockOpt); // E e = ((C)(x)).GetEnumerator(); // try { // /* as above */ result = new BoundBlock( syntax: forEachSyntax, locals: ImmutableArray.Create(enumeratorVar), statements: ImmutableArray.Create <BoundStatement>(enumeratorVarDecl, tryFinally)); } else { // E e = ((C)(x)).GetEnumerator(); // while (e.MoveNext()) { // V v = (V)(T)e.Current; // /* loop body */ // } result = new BoundBlock( syntax: forEachSyntax, locals: ImmutableArray.Create(enumeratorVar), statements: ImmutableArray.Create <BoundStatement>(enumeratorVarDecl, whileLoop)); } AddForEachKeywordSequencePoint(forEachSyntax, ref result); return(result); }
/// <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.TupleElementTypesWithAnnotations.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); }
ConversionKind ClassifyVoNullLiteralConversion(BoundExpression source, TypeSymbol destination, out Conversion conv) { if (_binder.Compilation.Options.HasRuntime && destination is NamedTypeSymbol) { var usualType = _binder.Compilation.UsualType(); var nts = destination as NamedTypeSymbol; if (nts.ConstructedFrom == usualType) { var op = usualType.GetOperators("op_Implicit") .WhereAsArray(o => o.ParameterCount == 1 && o.ParameterTypes[0].IsObjectType() && o.ReturnType == usualType) .AsSingleton() as MethodSymbol; if (op != null) { var sourceType = _binder.Compilation.GetSpecialType(SpecialType.System_Object); UserDefinedConversionAnalysis uca = UserDefinedConversionAnalysis.Normal(op, Conversion.ImplicitReference, Conversion.Identity, sourceType, destination); UserDefinedConversionResult cr = UserDefinedConversionResult.Valid(new[] { uca }.AsImmutable(), 0); conv = new Conversion(cr, isImplicit: true); return(ConversionKind.ImplicitUserDefined); } } } conv = Conversion.NoConversion; return(ConversionKind.NoConversion); }
/// <summary> /// Return the side-effect expression corresponding to an evaluation. /// </summary> protected BoundExpression LowerEvaluation(BoundDagEvaluation evaluation) { BoundExpression input = _tempAllocator.GetTemp(evaluation.Input); switch (evaluation) { case BoundDagFieldEvaluation f: { FieldSymbol field = f.Field; var outputTemp = new BoundDagTemp(f.Syntax, field.Type.TypeSymbol, f, index: 0); BoundExpression output = _tempAllocator.GetTemp(outputTemp); BoundExpression access = _localRewriter.MakeFieldAccess(f.Syntax, input, field, null, LookupResultKind.Viable, field.Type.TypeSymbol); access.WasCompilerGenerated = true; return(_factory.AssignmentExpression(output, access)); } case BoundDagPropertyEvaluation p: { PropertySymbol property = p.Property; var outputTemp = new BoundDagTemp(p.Syntax, property.Type.TypeSymbol, p, index: 0); BoundExpression output = _tempAllocator.GetTemp(outputTemp); return(_factory.AssignmentExpression(output, _factory.Property(input, property))); } case BoundDagDeconstructEvaluation d: { MethodSymbol method = d.DeconstructMethod; var refKindBuilder = ArrayBuilder <RefKind> .GetInstance(); var argBuilder = ArrayBuilder <BoundExpression> .GetInstance(); BoundExpression receiver; void addArg(RefKind refKind, BoundExpression expression) { refKindBuilder.Add(refKind); argBuilder.Add(expression); } Debug.Assert(method.Name == WellKnownMemberNames.DeconstructMethodName); int extensionExtra; if (method.IsStatic) { Debug.Assert(method.IsExtensionMethod); receiver = _factory.Type(method.ContainingType); addArg(method.ParameterRefKinds[0], input); extensionExtra = 1; } else { receiver = input; extensionExtra = 0; } for (int i = extensionExtra; i < method.ParameterCount; i++) { ParameterSymbol parameter = method.Parameters[i]; Debug.Assert(parameter.RefKind == RefKind.Out); var outputTemp = new BoundDagTemp(d.Syntax, parameter.Type.TypeSymbol, d, i - extensionExtra); addArg(RefKind.Out, _tempAllocator.GetTemp(outputTemp)); } return(_factory.Call(receiver, method, refKindBuilder.ToImmutableAndFree(), argBuilder.ToImmutableAndFree())); } case BoundDagTypeEvaluation t: { TypeSymbol inputType = input.Type; if (inputType.IsDynamic() || inputType.ContainsTypeParameter()) { inputType = _factory.SpecialType(SpecialType.System_Object); } TypeSymbol type = t.Type; var outputTemp = new BoundDagTemp(t.Syntax, type, t, index: 0); BoundExpression output = _tempAllocator.GetTemp(outputTemp); HashSet <DiagnosticInfo> useSiteDiagnostics = null; Conversion conversion = _factory.Compilation.Conversions.ClassifyBuiltInConversion(inputType, output.Type, ref useSiteDiagnostics); _localRewriter._diagnostics.Add(t.Syntax, useSiteDiagnostics); BoundExpression evaluated; if (conversion.Exists) { if (conversion.Kind == ConversionKind.ExplicitNullable && inputType.GetNullableUnderlyingType().Equals(output.Type, TypeCompareKind.AllIgnoreOptions) && _localRewriter.TryGetNullableMethod(t.Syntax, inputType, SpecialMember.System_Nullable_T_GetValueOrDefault, out MethodSymbol getValueOrDefault)) { // As a special case, since the null test has already been done we can use Nullable<T>.GetValueOrDefault evaluated = _factory.Call(input, getValueOrDefault); } else { evaluated = _factory.Convert(type, input, conversion); } } else { evaluated = _factory.As(input, type); } return(_factory.AssignmentExpression(output, evaluated)); } case BoundDagIndexEvaluation e: { // This is an evaluation of an indexed property with a constant int value. // The input type must be ITuple, and the property must be a property of ITuple. Debug.Assert(e.Property.ContainingSymbol.Equals(input.Type)); Debug.Assert(e.Property.GetMethod.ParameterCount == 1); Debug.Assert(e.Property.GetMethod.Parameters[0].Type.SpecialType == SpecialType.System_Int32); TypeSymbol type = e.Property.GetMethod.ReturnType.TypeSymbol; var outputTemp = new BoundDagTemp(e.Syntax, type, e, index: 0); BoundExpression output = _tempAllocator.GetTemp(outputTemp); return(_factory.AssignmentExpression(output, _factory.Call(input, e.Property.GetMethod, _factory.Literal(e.Index)))); } default: throw ExceptionUtilities.UnexpectedValue(evaluation); } }
private BoundExpression MakeNullCoalescingOperator( CSharpSyntaxNode syntax, BoundExpression rewrittenLeft, BoundExpression rewrittenRight, Conversion leftConversion, TypeSymbol rewrittenResultType) { Debug.Assert(rewrittenLeft != null); Debug.Assert(rewrittenRight != null); Debug.Assert(leftConversion.IsValid); Debug.Assert((object)rewrittenResultType != null); Debug.Assert(rewrittenRight.Type.Equals(rewrittenResultType, TypeCompareKind.IgnoreDynamicAndTupleNames)); if (_inExpressionLambda) { TypeSymbol strippedLeftType = rewrittenLeft.Type.StrippedType(); Conversion rewrittenConversion = MakeConversion(syntax, leftConversion, strippedLeftType, rewrittenResultType); return(new BoundNullCoalescingOperator(syntax, rewrittenLeft, rewrittenRight, rewrittenConversion, rewrittenResultType)); } // first we can make a small optimization: // If left is a constant then we already know whether it is null or not. If it is null then we // can simply generate "right". If it is not null then we can simply generate // MakeConversion(left). if (rewrittenLeft.IsDefaultValue()) { return(rewrittenRight); } if (rewrittenLeft.ConstantValue != null) { Debug.Assert(!rewrittenLeft.ConstantValue.IsNull); return(GetConvertedLeftForNullCoalescingOperator(rewrittenLeft, leftConversion, rewrittenResultType)); } // if left conversion is intrinsic implicit (always succeeds) and results in a reference type // we can apply conversion before doing the null check that allows for a more efficient IL emit. if (rewrittenLeft.Type.IsReferenceType && leftConversion.IsImplicit && !leftConversion.IsUserDefined) { if (!leftConversion.IsIdentity) { rewrittenLeft = MakeConversionNode(rewrittenLeft.Syntax, rewrittenLeft, leftConversion, rewrittenResultType, @checked: false); } return(new BoundNullCoalescingOperator(syntax, rewrittenLeft, rewrittenRight, Conversion.Identity, rewrittenResultType)); } if (leftConversion.IsIdentity || leftConversion.Kind == ConversionKind.ExplicitNullable) { var conditionalAccess = rewrittenLeft as BoundLoweredConditionalAccess; if (conditionalAccess != null && (conditionalAccess.WhenNullOpt == null || NullableNeverHasValue(conditionalAccess.WhenNullOpt))) { var notNullAccess = NullableAlwaysHasValue(conditionalAccess.WhenNotNull); if (notNullAccess != null) { var whenNullOpt = rewrittenRight; if (whenNullOpt.Type.IsNullableType()) { notNullAccess = conditionalAccess.WhenNotNull; } if (whenNullOpt.IsDefaultValue() && whenNullOpt.Type.SpecialType != SpecialType.System_Decimal) { whenNullOpt = null; } return(conditionalAccess.Update( conditionalAccess.Receiver, conditionalAccess.HasValueMethodOpt, whenNotNull: notNullAccess, whenNullOpt: whenNullOpt, id: conditionalAccess.Id, type: rewrittenResultType )); } } } // We lower left ?? right to // // var temp = left; // (temp != null) ? MakeConversion(temp) : right // BoundAssignmentOperator tempAssignment; BoundLocal boundTemp = _factory.StoreToTemp(rewrittenLeft, out tempAssignment); // temp != null BoundExpression nullCheck = MakeNullCheck(syntax, boundTemp, BinaryOperatorKind.NotEqual); // MakeConversion(temp, rewrittenResultType) BoundExpression convertedLeft = GetConvertedLeftForNullCoalescingOperator(boundTemp, leftConversion, rewrittenResultType); Debug.Assert(convertedLeft.Type.Equals(rewrittenResultType, TypeCompareKind.IgnoreDynamicAndTupleNames)); // (temp != null) ? MakeConversion(temp, LeftConversion) : RightOperand BoundExpression conditionalExpression = RewriteConditionalOperator( syntax: syntax, rewrittenCondition: nullCheck, rewrittenConsequence: convertedLeft, rewrittenAlternative: rewrittenRight, constantValueOpt: null, rewrittenType: rewrittenResultType); Debug.Assert(conditionalExpression.ConstantValue == null); // we shouldn't have hit this else case otherwise Debug.Assert(conditionalExpression.Type.Equals(rewrittenResultType, TypeCompareKind.IgnoreDynamicAndTupleNames)); return(new BoundSequence( syntax: syntax, locals: ImmutableArray.Create(boundTemp.LocalSymbol), sideEffects: ImmutableArray.Create <BoundExpression>(tempAssignment), value: conditionalExpression, type: rewrittenResultType)); }
/// <remarks> /// This method is intended for passes other than the LocalRewriter. /// Use MakeConversion helper method in the LocalRewriter instead, /// it generates a synthesized conversion in its lowered form. /// </remarks> public static BoundConversion SynthesizedNonUserDefined(SyntaxNode syntax, BoundExpression operand, Conversion conversion, TypeSymbol type, ConstantValue constantValueOpt = null) { return(new BoundConversion( syntax, operand, conversion, isBaseConversion: false, @checked: false, explicitCastInCode: false, constantValueOpt: constantValueOpt, type: type) { WasCompilerGenerated = true }); }
public static UnaryOperatorAnalysisResult Inapplicable(UnaryOperatorSignature signature, Conversion conversion) { return(new UnaryOperatorAnalysisResult(OperatorAnalysisResultKind.Inapplicable, signature, conversion)); }
private BoundExpression GetConvertedLeftForNullCoalescingOperator(BoundExpression rewrittenLeft, Conversion leftConversion, TypeSymbol rewrittenResultType) { Debug.Assert(rewrittenLeft != null); Debug.Assert((object)rewrittenLeft.Type != null); Debug.Assert((object)rewrittenResultType != null); Debug.Assert(leftConversion.IsValid); TypeSymbol rewrittenLeftType = rewrittenLeft.Type; Debug.Assert(rewrittenLeftType.IsNullableType() || rewrittenLeftType.IsReferenceType); // Native compiler violates the specification for the case where result type is right operand type and left operand is nullable. // For this case, we need to insert an extra explicit nullable conversion from the left operand to its underlying nullable type // before performing the leftConversion. // See comments in Binder.BindNullCoalescingOperator referring to GetConvertedLeftForNullCoalescingOperator for more details. if (rewrittenLeftType != rewrittenResultType && rewrittenLeftType.IsNullableType()) { TypeSymbol strippedLeftType = rewrittenLeftType.GetNullableUnderlyingType(); MethodSymbol getValueOrDefault = GetNullableMethod(rewrittenLeft.Syntax, rewrittenLeftType, SpecialMember.System_Nullable_T_GetValueOrDefault); rewrittenLeft = BoundCall.Synthesized(rewrittenLeft.Syntax, rewrittenLeft, getValueOrDefault); if (strippedLeftType == rewrittenResultType) { return(rewrittenLeft); } } return(MakeConversionNode(rewrittenLeft.Syntax, rewrittenLeft, leftConversion, rewrittenResultType, @checked: false)); }
private UnaryOperatorAnalysisResult(OperatorAnalysisResultKind kind, UnaryOperatorSignature signature, Conversion conversion) { this.Kind = kind; this.Signature = signature; this.Conversion = conversion; }
/// <summary> /// Lower "using [await] (expression) statement" to a try-finally block. /// </summary> private BoundBlock MakeExpressionUsingStatement(BoundUsingStatement node, BoundBlock tryBlock) { Debug.Assert(node.ExpressionOpt != null); Debug.Assert(node.DeclarationsOpt == null); // See comments in BuildUsingTryFinally for the details of the lowering to try-finally. // // SPEC: A using statement of the form "using (expression) statement; " has the // SPEC: same three possible expansions [ as "using (ResourceType r = expression) statement; ] // SPEC: but in this case ResourceType is implicitly the compile-time type of the expression, // SPEC: and the resource variable is inaccessible to and invisible to the embedded statement. // // DELIBERATE SPEC VIOLATION: // // The spec quote above implies that the expression must have a type; in fact we allow // the expression to be null. // // If expr is the constant null then we can elide the whole thing and simply generate the statement. BoundExpression rewrittenExpression = (BoundExpression)Visit(node.ExpressionOpt); if (rewrittenExpression.ConstantValue == ConstantValue.Null) { Debug.Assert(node.Locals.IsEmpty); // TODO: This might not be a valid assumption in presence of semicolon operator. return(tryBlock); } // Otherwise, we lower "using(expression) statement;" as follows: // // * If the expression is of type dynamic then we lower as though the user had written // // using(IDisposable temp = (IDisposable)expression) statement; // // Note that we have to do the conversion early, not in the finally block, because // if the conversion fails at runtime with an exception then the exception must happen // before the statement runs. // // * Otherwise we lower as though the user had written // // using(ResourceType temp = expression) statement; // TypeSymbol expressionType = rewrittenExpression.Type; SyntaxNode expressionSyntax = rewrittenExpression.Syntax; UsingStatementSyntax usingSyntax = (UsingStatementSyntax)node.Syntax; BoundAssignmentOperator tempAssignment; BoundLocal boundTemp; if ((object)expressionType == null || expressionType.IsDynamic()) { // IDisposable temp = (IDisposable) expr; // or // IAsyncDisposable temp = (IAsyncDisposable) expr; TypeSymbol iDisposableType = node.AwaitOpt is null? _compilation.GetSpecialType(SpecialType.System_IDisposable) : _compilation.GetWellKnownType(WellKnownType.System_IAsyncDisposable); BoundExpression tempInit = MakeConversionNode( expressionSyntax, rewrittenExpression, Conversion.GetTrivialConversion(node.IDisposableConversion.Kind), iDisposableType, @checked: false, constantValueOpt: rewrittenExpression.ConstantValue); boundTemp = _factory.StoreToTemp(tempInit, out tempAssignment, kind: SynthesizedLocalKind.Using); } else { // ResourceType temp = expr; boundTemp = _factory.StoreToTemp(rewrittenExpression, out tempAssignment, syntaxOpt: usingSyntax, kind: SynthesizedLocalKind.Using); } BoundStatement expressionStatement = new BoundExpressionStatement(expressionSyntax, tempAssignment); if (this.Instrument) { expressionStatement = _instrumenter.InstrumentUsingTargetCapture(node, expressionStatement); } BoundStatement tryFinally = RewriteUsingStatementTryFinally(usingSyntax, tryBlock, boundTemp, usingSyntax.AwaitKeyword, node.AwaitOpt, node.DisposeMethodOpt); // { ResourceType temp = expr; try { ... } finally { ... } } return(new BoundBlock( syntax: usingSyntax, locals: node.Locals.Add(boundTemp.LocalSymbol), statements: ImmutableArray.Create <BoundStatement>(expressionStatement, tryFinally))); }
public BoundForEachStatement(SyntaxNode syntax, ForEachEnumeratorInfo enumeratorInfoOpt, Conversion elementConversion, BoundTypeExpression iterationVariableType, ImmutableArray <LocalSymbol> iterationVariables, BoundExpression expression, BoundForEachDeconstructStep deconstructionOpt, BoundStatement body, bool @checked, GeneratedLabelSymbol breakLabel, GeneratedLabelSymbol continueLabel, bool hasErrors = false) : this(syntax, enumeratorInfoOpt, elementConversion, iterationVariableType, iterationVariables, iterationErrorExpressionOpt : null, expression, deconstructionOpt, body, @checked, breakLabel, continueLabel, hasErrors) { }
/// <summary> /// Produce an element-wise comparison and logic to ensure the result is a bool type. /// /// If an element-wise comparison doesn't return bool, then: /// - if it is dynamic, we'll do `!(comparisonResult.false)` or `comparisonResult.true` /// - if it implicitly converts to bool, we'll just do the conversion /// - otherwise, we'll do `!(comparisonResult.false)` or `comparisonResult.true` (as we'd do for `if` or `while`) /// </summary> private BoundExpression RewriteTupleSingleOperator(TupleBinaryOperatorInfo.Single single, BoundExpression left, BoundExpression right, TypeSymbol boolType, BinaryOperatorKind operatorKind) { if (single.Kind.IsDynamic()) { // Produce // !((left == right).op_false) // (left != right).op_true BoundExpression dynamicResult = _dynamicFactory.MakeDynamicBinaryOperator(single.Kind, left, right, isCompoundAssignment: false, _compilation.DynamicType).ToExpression(); if (operatorKind == BinaryOperatorKind.Equal) { return(_factory.Not(MakeUnaryOperator(UnaryOperatorKind.DynamicFalse, left.Syntax, method: null, dynamicResult, boolType))); } else { return(MakeUnaryOperator(UnaryOperatorKind.DynamicTrue, left.Syntax, method: null, dynamicResult, boolType)); } } if (left.IsLiteralNull() && right.IsLiteralNull()) { // For `null == null` this is special-cased during initial binding return(new BoundLiteral(left.Syntax, ConstantValue.Create(operatorKind == BinaryOperatorKind.Equal), boolType)); } // We leave both operands in nullable-null conversions unconverted, MakeBinaryOperator has special for null-literal bool isNullableNullConversion = single.Kind.OperandTypes() == BinaryOperatorKind.NullableNull; BoundExpression convertedLeft = isNullableNullConversion ? left : MakeConversionNode(left.Syntax, left, single.LeftConversion, single.LeftConvertedTypeOpt, @checked: false); BoundExpression convertedRight = isNullableNullConversion ? right : MakeConversionNode(right.Syntax, right, single.RightConversion, single.RightConvertedTypeOpt, @checked: false); BoundExpression binary = MakeBinaryOperator(_factory.Syntax, single.Kind, convertedLeft, convertedRight, single.MethodSymbolOpt?.ReturnType ?? boolType, single.MethodSymbolOpt); UnaryOperatorSignature boolOperator = single.BoolOperator; Conversion boolConversion = single.ConversionForBool; BoundExpression result; if (boolOperator.Kind != UnaryOperatorKind.Error) { // Produce // !((left == right).op_false) // (left != right).op_true BoundExpression convertedBinary = MakeConversionNode(_factory.Syntax, binary, boolConversion, boolOperator.OperandType, @checked: false); Debug.Assert(boolOperator.ReturnType.SpecialType == SpecialType.System_Boolean); result = MakeUnaryOperator(boolOperator.Kind, binary.Syntax, boolOperator.Method, convertedBinary, boolType); if (operatorKind == BinaryOperatorKind.Equal) { result = _factory.Not(result); } } else if (!boolConversion.IsIdentity) { // Produce // (bool)(left == right) // (bool)(left != right) result = MakeConversionNode(_factory.Syntax, binary, boolConversion, boolType, @checked: false); } else { result = binary; } return(result); }
/// <summary> /// Lower "using (ResourceType resource = expression) statement" to a try-finally block. /// </summary> /// <remarks> /// Assumes that the local symbol will be declared (i.e. in the LocalsOpt array) of an enclosing block. /// Assumes that using statements with multiple locals have already been split up into multiple using statements. /// </remarks> private BoundBlock RewriteDeclarationUsingStatement(CSharpSyntaxNode usingSyntax, BoundLocalDeclaration localDeclaration, BoundBlock tryBlock, Conversion idisposableConversion) { CSharpSyntaxNode declarationSyntax = localDeclaration.Syntax; LocalSymbol localSymbol = localDeclaration.LocalSymbol; TypeSymbol localType = localSymbol.Type; Debug.Assert((object)localType != null); //otherwise, there wouldn't be a conversion to IDisposable BoundLocal boundLocal = new BoundLocal(declarationSyntax, localSymbol, localDeclaration.InitializerOpt.ConstantValue, localType); BoundStatement rewrittenDeclaration = (BoundStatement)Visit(localDeclaration); // If we know that the expression is null, then we know that the null check in the finally block // will fail, and the Dispose call will never happen. That is, the finally block will have no effect. // Consequently, we can simply skip the whole try-finally construct and just create a block containing // the new declaration. if (boundLocal.ConstantValue == ConstantValue.Null) { //localSymbol will be declared by an enclosing block return(BoundBlock.SynthesizedNoLocals(usingSyntax, rewrittenDeclaration, tryBlock)); } if (localType.IsDynamic()) { BoundExpression tempInit = MakeConversion( declarationSyntax, boundLocal, idisposableConversion, compilation.GetSpecialType(SpecialType.System_IDisposable), @checked: false); BoundAssignmentOperator tempAssignment; BoundLocal boundTemp = this.factory.StoreToTemp(tempInit, out tempAssignment); BoundStatement tryFinally = RewriteUsingStatementTryFinally(usingSyntax, tryBlock, boundTemp); return(new BoundBlock( syntax: usingSyntax, localsOpt: ImmutableArray.Create <LocalSymbol>(boundTemp.LocalSymbol), //localSymbol will be declared by an enclosing block statements: ImmutableArray.Create <BoundStatement>( rewrittenDeclaration, new BoundExpressionStatement(declarationSyntax, tempAssignment), tryFinally))); } else { BoundStatement tryFinally = RewriteUsingStatementTryFinally(usingSyntax, tryBlock, boundLocal); // localSymbol will be declared by an enclosing block return(BoundBlock.SynthesizedNoLocals(usingSyntax, rewrittenDeclaration, tryFinally)); } }
private BoundExpression LowerSwitchExpression(BoundConvertedSwitchExpression node) { // When compiling for Debug (not Release), we produce the most detailed sequence points. var produceDetailedSequencePoints = GenerateInstrumentation && _localRewriter._compilation.Options.OptimizationLevel != OptimizationLevel.Release; _factory.Syntax = node.Syntax; var result = ArrayBuilder <BoundStatement> .GetInstance(); var outerVariables = ArrayBuilder <LocalSymbol> .GetInstance(); var loweredSwitchGoverningExpression = _localRewriter.VisitExpression(node.Expression); BoundDecisionDag decisionDag = ShareTempsIfPossibleAndEvaluateInput( node.GetDecisionDagForLowering(_factory.Compilation, out LabelSymbol? defaultLabel), loweredSwitchGoverningExpression, result, out BoundExpression savedInputExpression); Debug.Assert(savedInputExpression != null); object restorePointForEnclosingStatement = new object(); object restorePointForSwitchBody = new object(); // lower the decision dag. (ImmutableArray <BoundStatement> loweredDag, ImmutableDictionary <SyntaxNode, ImmutableArray <BoundStatement> > switchSections) = LowerDecisionDag(decisionDag); if (_whenNodeIdentifierLocal is not null) { outerVariables.Add(_whenNodeIdentifierLocal); } if (produceDetailedSequencePoints) { var syntax = (SwitchExpressionSyntax)node.Syntax; result.Add(new BoundSavePreviousSequencePoint(syntax, restorePointForEnclosingStatement)); // While evaluating the state machine, we highlight the `switch {...}` part. var spanStart = syntax.SwitchKeyword.Span.Start; var spanEnd = syntax.Span.End; var spanForSwitchBody = new TextSpan(spanStart, spanEnd - spanStart); result.Add(new BoundStepThroughSequencePoint(node.Syntax, span: spanForSwitchBody)); result.Add(new BoundSavePreviousSequencePoint(syntax, restorePointForSwitchBody)); } // add the rest of the lowered dag that references that input result.Add(_factory.Block(loweredDag)); // A branch to the default label when no switch case matches is included in the // decision tree, so the code in result is unreachable at this point. // Lower each switch expression arm LocalSymbol resultTemp = _factory.SynthesizedLocal(node.Type, node.Syntax, kind: SynthesizedLocalKind.LoweringTemp); LabelSymbol afterSwitchExpression = _factory.GenerateLabel("afterSwitchExpression"); foreach (BoundSwitchExpressionArm arm in node.SwitchArms) { _factory.Syntax = arm.Syntax; var sectionBuilder = ArrayBuilder <BoundStatement> .GetInstance(); sectionBuilder.AddRange(switchSections[arm.Syntax]); sectionBuilder.Add(_factory.Label(arm.Label)); var loweredValue = _localRewriter.VisitExpression(arm.Value); if (GenerateInstrumentation) { loweredValue = this._localRewriter._instrumenter.InstrumentSwitchExpressionArmExpression(arm.Value, loweredValue, _factory); } sectionBuilder.Add(_factory.Assignment(_factory.Local(resultTemp), loweredValue)); sectionBuilder.Add(_factory.Goto(afterSwitchExpression)); var statements = sectionBuilder.ToImmutableAndFree(); if (arm.Locals.IsEmpty) { result.Add(_factory.StatementList(statements)); } else { // Lifetime of these locals is expanded to the entire switch body, as it is possible to // share them as temps in the decision dag. outerVariables.AddRange(arm.Locals); // Note the language scope of the locals, even though they are included for the purposes of // lifetime analysis in the enclosing scope. result.Add(new BoundScope(arm.Syntax, arm.Locals, statements)); } } _factory.Syntax = node.Syntax; if (defaultLabel is not null) { result.Add(_factory.Label(defaultLabel)); if (produceDetailedSequencePoints) { result.Add(new BoundRestorePreviousSequencePoint(node.Syntax, restorePointForSwitchBody)); } var objectType = _factory.SpecialType(SpecialType.System_Object); var throwCall = (implicitConversionExists(savedInputExpression, objectType) && _factory.WellKnownMember(WellKnownMember.System_Runtime_CompilerServices_SwitchExpressionException__ctorObject, isOptional: true) is MethodSymbol) ? ConstructThrowSwitchExpressionExceptionHelperCall(_factory, _factory.Convert(objectType, savedInputExpression)) : (_factory.WellKnownMember(WellKnownMember.System_Runtime_CompilerServices_SwitchExpressionException__ctor, isOptional: true) is MethodSymbol) ? ConstructThrowSwitchExpressionExceptionParameterlessHelperCall(_factory) : ConstructThrowInvalidOperationExceptionHelperCall(_factory); result.Add(throwCall); } if (GenerateInstrumentation) { result.Add(_factory.HiddenSequencePoint()); } result.Add(_factory.Label(afterSwitchExpression)); if (produceDetailedSequencePoints) { result.Add(new BoundRestorePreviousSequencePoint(node.Syntax, restorePointForEnclosingStatement)); } outerVariables.Add(resultTemp); outerVariables.AddRange(_tempAllocator.AllTemps()); return(_factory.SpillSequence(outerVariables.ToImmutableAndFree(), result.ToImmutableAndFree(), _factory.Local(resultTemp))); bool implicitConversionExists(BoundExpression expression, TypeSymbol type) { var discardedUseSiteInfo = CompoundUseSiteInfo <AssemblySymbol> .Discarded; Conversion c = _localRewriter._compilation.Conversions.ClassifyConversionFromExpression(expression, type, isChecked: false, ref discardedUseSiteInfo); return(c.IsImplicit); } }
/// <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 AnalyzeImplicitUserDefinedConversionForSwitchGoverningType, /// 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 convertsTo = op.ReturnType; 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 && (object)target != null && source.IsNullableType() && convertsFrom.IsNonNullableValueType() && 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)); } } } } }
private BoundExpression MakeIsOperator( BoundIsOperator oldNode, CSharpSyntaxNode syntax, BoundExpression rewrittenOperand, BoundTypeExpression rewrittenTargetType, Conversion conversion, TypeSymbol rewrittenType) { if (rewrittenOperand.Kind == BoundKind.MethodGroup) { var methodGroup = (BoundMethodGroup)rewrittenOperand; BoundExpression receiver = methodGroup.ReceiverOpt; if (receiver != null && receiver.Kind != BoundKind.ThisReference) { // possible side-effect return(RewriteConstantIsOperator(receiver.Syntax, receiver, ConstantValue.False, rewrittenType)); } else { return(MakeLiteral(syntax, ConstantValue.False, rewrittenType)); } } var operandType = rewrittenOperand.Type; var targetType = rewrittenTargetType.Type; Debug.Assert((object)operandType != null || rewrittenOperand.ConstantValue.IsNull); Debug.Assert((object)targetType != null); // TODO: Handle dynamic operand type and target type if (!inExpressionLambda) { ConstantValue constantValue = Binder.GetIsOperatorConstantResult(operandType, targetType, conversion.Kind, rewrittenOperand.ConstantValue); if (constantValue != null) { return(RewriteConstantIsOperator(syntax, rewrittenOperand, constantValue, rewrittenType)); } else if (conversion.IsImplicit) { // operand is a reference type with bound identity or implicit conversion // We can replace the "is" instruction with a null check Debug.Assert((object)operandType != null); if (operandType.TypeKind == TypeKind.TypeParameter) { // We need to box the type parameter even if it is a known // reference type to ensure there are no verifier errors rewrittenOperand = MakeConversion( syntax: rewrittenOperand.Syntax, rewrittenOperand: rewrittenOperand, conversionKind: ConversionKind.Boxing, rewrittenType: compilation.GetSpecialType(SpecialType.System_Object), @checked: false); } return(MakeNullCheck(syntax, rewrittenOperand, BinaryOperatorKind.NotEqual)); } } return(oldNode.Update(rewrittenOperand, rewrittenTargetType, conversion, rewrittenType)); }
private BinaryOperatorAnalysisResult(OperatorAnalysisResultKind kind, BinaryOperatorSignature signature, Conversion leftConversion, Conversion rightConversion) { this.Kind = kind; this.Signature = signature; this.LeftConversion = leftConversion; this.RightConversion = rightConversion; }
internal static BoundStatement BindUsingStatementOrDeclarationFromParts(SyntaxNode syntax, SyntaxToken usingKeyword, SyntaxToken awaitKeyword, Binder originalBinder, UsingStatementBinder usingBinderOpt, DiagnosticBag diagnostics) { bool isUsingDeclaration = syntax.Kind() == SyntaxKind.LocalDeclarationStatement; bool isExpression = !isUsingDeclaration && syntax.Kind() != SyntaxKind.VariableDeclaration; bool hasAwait = awaitKeyword != default; Debug.Assert(isUsingDeclaration || usingBinderOpt != null); TypeSymbol disposableInterface = getDisposableInterface(hasAwait); Debug.Assert((object)disposableInterface != null); bool hasErrors = ReportUseSiteDiagnostics(disposableInterface, diagnostics, hasAwait ? awaitKeyword : usingKeyword); Conversion iDisposableConversion; ImmutableArray <BoundLocalDeclaration> declarationsOpt = default; BoundMultipleLocalDeclarations multipleDeclarationsOpt = null; BoundExpression expressionOpt = null; TypeSymbol declarationTypeOpt = null; MethodSymbol disposeMethodOpt; TypeSymbol awaitableTypeOpt; if (isExpression) { expressionOpt = usingBinderOpt.BindTargetExpression(diagnostics, originalBinder); hasErrors |= !populateDisposableConversionOrDisposeMethod(fromExpression: true, out iDisposableConversion, out disposeMethodOpt, out awaitableTypeOpt); } else { VariableDeclarationSyntax declarationSyntax = isUsingDeclaration ? ((LocalDeclarationStatementSyntax)syntax).Declaration : (VariableDeclarationSyntax)syntax; originalBinder.BindForOrUsingOrFixedDeclarations(declarationSyntax, LocalDeclarationKind.UsingVariable, diagnostics, out declarationsOpt); Debug.Assert(!declarationsOpt.IsEmpty); multipleDeclarationsOpt = new BoundMultipleLocalDeclarations(declarationSyntax, declarationsOpt); declarationTypeOpt = declarationsOpt[0].DeclaredTypeOpt.Type; if (declarationTypeOpt.IsDynamic()) { iDisposableConversion = Conversion.ImplicitDynamic; disposeMethodOpt = null; awaitableTypeOpt = null; } else { hasErrors |= !populateDisposableConversionOrDisposeMethod(fromExpression: false, out iDisposableConversion, out disposeMethodOpt, out awaitableTypeOpt); } } BoundAwaitableInfo awaitOpt = null; if (hasAwait) { // even if we don't have a proper value to await, we'll still report bad usages of `await` originalBinder.ReportBadAwaitDiagnostics(syntax, awaitKeyword.GetLocation(), diagnostics, ref hasErrors); if (awaitableTypeOpt is null) { awaitOpt = new BoundAwaitableInfo(syntax, awaitableInstancePlaceholder: null, isDynamic: true, getAwaiter: null, isCompleted: null, getResult: null) { WasCompilerGenerated = true }; } else { hasErrors |= ReportUseSiteDiagnostics(awaitableTypeOpt, diagnostics, awaitKeyword); var placeholder = new BoundAwaitableValuePlaceholder(syntax, valEscape: originalBinder.LocalScopeDepth, awaitableTypeOpt).MakeCompilerGenerated(); awaitOpt = originalBinder.BindAwaitInfo(placeholder, syntax, diagnostics, ref hasErrors); } } // This is not awesome, but its factored. // In the future it might be better to have a separate shared type that we add the info to, and have the callers create the appropriate bound nodes from it if (isUsingDeclaration) { return(new BoundUsingLocalDeclarations(syntax, disposeMethodOpt, iDisposableConversion, awaitOpt, declarationsOpt, hasErrors)); } else { BoundStatement boundBody = originalBinder.BindPossibleEmbeddedStatement(usingBinderOpt._syntax.Statement, diagnostics); return(new BoundUsingStatement( usingBinderOpt._syntax, usingBinderOpt.Locals, multipleDeclarationsOpt, expressionOpt, iDisposableConversion, boundBody, awaitOpt, disposeMethodOpt, hasErrors)); } bool populateDisposableConversionOrDisposeMethod(bool fromExpression, out Conversion iDisposableConversion, out MethodSymbol disposeMethodOpt, out TypeSymbol awaitableTypeOpt) { HashSet <DiagnosticInfo> useSiteDiagnostics = null; iDisposableConversion = classifyConversion(fromExpression, disposableInterface, ref useSiteDiagnostics); disposeMethodOpt = null; awaitableTypeOpt = null; diagnostics.Add(syntax, useSiteDiagnostics); if (iDisposableConversion.IsImplicit) { if (hasAwait) { awaitableTypeOpt = originalBinder.Compilation.GetWellKnownType(WellKnownType.System_Threading_Tasks_ValueTask); } return(true); } TypeSymbol type = fromExpression ? expressionOpt.Type : declarationTypeOpt; // If this is a ref struct, or we're in a valid asynchronous using, try binding via pattern. // We won't need to try and bind a second time if it fails, as async dispose can't be pattern based (ref structs are not allowed in async methods) if (type is object && (type.IsRefLikeType || hasAwait)) { BoundExpression receiver = fromExpression ? expressionOpt : new BoundLocal(syntax, declarationsOpt[0].LocalSymbol, null, type) { WasCompilerGenerated = true }; DiagnosticBag patternDiagnostics = originalBinder.Compilation.IsFeatureEnabled(MessageID.IDS_FeatureUsingDeclarations) ? diagnostics : new DiagnosticBag(); disposeMethodOpt = originalBinder.TryFindDisposePatternMethod(receiver, syntax, hasAwait, patternDiagnostics); if (disposeMethodOpt is object) { MessageID.IDS_FeatureUsingDeclarations.CheckFeatureAvailability(diagnostics, originalBinder.Compilation, syntax.Location); if (hasAwait) { awaitableTypeOpt = disposeMethodOpt.ReturnType; } return(true); } } if (type is null || !type.IsErrorType()) { // Retry with a different assumption about whether the `using` is async TypeSymbol alternateInterface = getDisposableInterface(!hasAwait); HashSet <DiagnosticInfo> ignored = null; Conversion alternateConversion = classifyConversion(fromExpression, alternateInterface, ref ignored); bool wrongAsync = alternateConversion.IsImplicit; ErrorCode errorCode = wrongAsync ? (hasAwait ? ErrorCode.ERR_NoConvToIAsyncDispWrongAsync : ErrorCode.ERR_NoConvToIDispWrongAsync) : (hasAwait ? ErrorCode.ERR_NoConvToIAsyncDisp : ErrorCode.ERR_NoConvToIDisp); Error(diagnostics, errorCode, syntax, declarationTypeOpt ?? expressionOpt.Display); } return(false); } Conversion classifyConversion(bool fromExpression, TypeSymbol targetInterface, ref HashSet <DiagnosticInfo> diag) { return(fromExpression ? originalBinder.Conversions.ClassifyImplicitConversionFromExpression(expressionOpt, targetInterface, ref diag) : originalBinder.Conversions.ClassifyImplicitConversionFromType(declarationTypeOpt, targetInterface, ref diag)); } TypeSymbol getDisposableInterface(bool isAsync) { return(isAsync ? originalBinder.Compilation.GetWellKnownType(WellKnownType.System_IAsyncDisposable) : originalBinder.Compilation.GetSpecialType(SpecialType.System_IDisposable)); } }
public static BinaryOperatorAnalysisResult Inapplicable(BinaryOperatorSignature signature, Conversion leftConversion, Conversion rightConversion) { return(new BinaryOperatorAnalysisResult(OperatorAnalysisResultKind.Inapplicable, signature, leftConversion, rightConversion)); }
private void AddUserDefinedConversionsToExplicitCandidateSet( BoundExpression sourceExpression, TypeSymbol source, TypeSymbol target, ArrayBuilder <UserDefinedConversionAnalysis> u, NamedTypeSymbol declaringType, string operatorName, ref HashSet <DiagnosticInfo> useSiteDiagnostics) { Debug.Assert(sourceExpression != null || (object)source != null); Debug.Assert((object)target != null); Debug.Assert(u != null); Debug.Assert((object)declaringType != null); Debug.Assert(operatorName != 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 or explicit // SPEC: conversion operators declared by the classes and structs in D that convert // SPEC: from a type encompassing E or encompassed by S (if it exists) to a type // SPEC: encompassing or encompassed by 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 the comment regarding bug 17021 in // UserDefinedImplicitConversions.cs. if ((object)source != null && source.IsInterfaceType() || target.IsInterfaceType()) { return; } foreach (MethodSymbol op in declaringType.GetOperators(operatorName)) { // We might have a bad operator and be in an error recovery situation. Ignore it. if (op.ReturnsVoid || op.ParameterCount != 1 || op.ReturnType.TypeKind == TypeKind.Error) { continue; } TypeSymbol convertsFrom = op.ParameterTypes[0].TypeSymbol; TypeSymbol convertsTo = op.ReturnType.TypeSymbol; Conversion fromConversion = EncompassingExplicitConversion(sourceExpression, source, convertsFrom, ref useSiteDiagnostics); Conversion toConversion = EncompassingExplicitConversion(null, convertsTo, target, ref useSiteDiagnostics); // We accept candidates for which the parameter type encompasses the *underlying* source type. if (!fromConversion.Exists && (object)source != null && source.IsNullableType() && EncompassingExplicitConversion(null, source.GetNullableUnderlyingType(), convertsFrom, ref useSiteDiagnostics).Exists) { fromConversion = ClassifyBuiltInConversion(source, convertsFrom, ref useSiteDiagnostics); } // As in dev11 (and the revised spec), we also accept candidates for which the return type is encompassed by the *stripped* target type. if (!toConversion.Exists && (object)target != null && target.IsNullableType() && EncompassingExplicitConversion(null, convertsTo, target.GetNullableUnderlyingType(), ref useSiteDiagnostics).Exists) { toConversion = ClassifyBuiltInConversion(convertsTo, target, ref useSiteDiagnostics); } // In the corresponding implicit conversion code we can get away with first // checking to see if standard implicit conversions exist from the source type // to the parameter type, and from the return type to the target type. If not, // then we can check for a lifted operator. // // That's not going to cut it in the explicit conversion code. Suppose we have // a conversion X-->Y and have source type X? and target type Y?. There *are* // standard explicit conversions from X?-->X and Y?-->Y, but we do not want // to bind this as an *unlifted* conversion from X? to Y?; we want such a thing // to be a *lifted* conversion from X? to Y?, that checks for null on the source // and decides to not call the underlying user-defined conversion if it is null. // // We therefore cannot do what we do in the implicit conversions, where we check // to see if the unlifted conversion works, and if it does, then don't add the lifted // conversion at all. Rather, we have to see if what we're building here is a // lifted conversion or not. // // Under what circumstances is this conversion a lifted conversion? (In the // "spec" sense of a lifted conversion; that is, that we check for null // and skip the user-defined conversion if necessary). // // * The source type must be a nullable value type. // * The parameter type must be a non-nullable value type. // * The target type must be able to take on a null value. if (fromConversion.Exists && toConversion.Exists) { if ((object)source != null && source.IsNullableType() && convertsFrom.IsNonNullableValueType() && target.CanBeAssignedNull()) { TypeSymbol nullableFrom = MakeNullableType(convertsFrom); TypeSymbol nullableTo = convertsTo.IsNonNullableValueType() ? MakeNullableType(convertsTo) : convertsTo; Conversion liftedFromConversion = EncompassingExplicitConversion(sourceExpression, source, nullableFrom, ref useSiteDiagnostics); Conversion liftedToConversion = EncompassingExplicitConversion(null, nullableTo, target, ref useSiteDiagnostics); Debug.Assert(liftedFromConversion.Exists); Debug.Assert(liftedToConversion.Exists); u.Add(UserDefinedConversionAnalysis.Lifted(op, liftedFromConversion, liftedToConversion, nullableFrom, nullableTo)); } else { // 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 a set of operators. // // Similarly, if we have a conversion from X-->Y and are asked to do // an explicit conversion from X? to Y then we treat the conversion as // though it really were X?-->Y for the purposes of determining the best // source type of a set of operators. // // We perpetuate these fictions here. if (target.IsNullableType() && convertsTo.IsNonNullableValueType()) { convertsTo = MakeNullableType(convertsTo); toConversion = EncompassingExplicitConversion(null, convertsTo, target, ref useSiteDiagnostics); } if ((object)source != null && source.IsNullableType() && convertsFrom.IsNonNullableValueType()) { convertsFrom = MakeNullableType(convertsFrom); fromConversion = EncompassingExplicitConversion(null, convertsFrom, source, ref useSiteDiagnostics); } u.Add(UserDefinedConversionAnalysis.Normal(op, fromConversion, toConversion, convertsFrom, convertsTo)); } } } }
/// <summary> /// Synthesize a no-argument call to a given method, possibly applying a conversion to the receiver. /// /// If the receiver is of struct type and the method is an interface method, then skip the conversion /// and just call the interface method directly - the code generator will detect this and generate a /// constrained virtual call. /// </summary> /// <param name="syntax">A syntax node to attach to the synthesized bound node.</param> /// <param name="receiver">Receiver of method call.</param> /// <param name="method">Method to invoke.</param> /// <param name="receiverConversion">Conversion to be applied to the receiver if not calling an interface method on a struct.</param> /// <param name="convertedReceiverType">Type of the receiver after applying the conversion.</param> /// <returns>A BoundExpression representing the call.</returns> private BoundExpression SynthesizeCall(CSharpSyntaxNode syntax, BoundExpression receiver, MethodSymbol method, Conversion receiverConversion, TypeSymbol convertedReceiverType) { if (receiver.Type.TypeKind == TypeKind.Struct && method.ContainingType.IsInterface) { Debug.Assert(receiverConversion.IsBoxing); // NOTE: The spec says that disposing of a struct enumerator won't cause any // unnecessary boxing to occur. However, Dev10 extends this improvement to the // GetEnumerator call as well. // We're going to let the emitter take care of avoiding the extra boxing. // When it sees an interface call to a struct, it will generate a constrained // virtual call, which will skip boxing, if possible. // CONSIDER: In cases where the struct implicitly implements the interface method // (i.e. with a public method), we could save a few bytes of IL by creating a // BoundCall to the struct method rather than the interface method (so that the // emitter wouldn't need to create a constrained virtual call). It is not clear // what effect this would have on back compat. // NOTE: This call does not correspond to anything that can be written in C# source. // We're invoking the interface method directly on the struct (which may have a private // explicit implementation). The code generator knows how to handle it though. // receiver.InterfaceMethod() return(BoundCall.Synthesized(syntax, receiver, method)); } else { // ((Interface)receiver).InterfaceMethod() Debug.Assert(!receiverConversion.IsNumeric); return(BoundCall.Synthesized( syntax: syntax, receiverOpt: MakeConversion( syntax: syntax, rewrittenOperand: receiver, conversion: receiverConversion, @checked: false, rewrittenType: convertedReceiverType), method: method)); } }