internal override void GenerateMethodBody(TypeCompilationState compilationState, DiagnosticBag diagnostics) { var concept = ImplementingMethod.ContainingType; var conceptLoc = concept.Locations.IsEmpty ? Location.None : concept.Locations[0]; // TODO: wrong location? Debug.Assert(concept.IsConcept, "Tried to synthesise default struct implementation on a non-concept interface"); var instance = ContainingType; var instanceLoc = instance.Locations.IsEmpty ? Location.None : instance.Locations[0]; // TODO: wrong location? Debug.Assert(instance.IsInstance, "Tried to synthesise default struct implementation for a non-instance"); SyntheticBoundNodeFactory F = new SyntheticBoundNodeFactory(this, this.GetNonNullSyntaxNode(), compilationState, diagnostics); F.CurrentMethod = OriginalDefinition; try { // Now try to find the default struct using the instance's scope... var binder = new BinderFactory(compilationState.Compilation, instance.GetNonNullSyntaxNode().SyntaxTree).GetBinder(instance.GetNonNullSyntaxNode()); var ignore = new HashSet <DiagnosticInfo>(); var defs = concept.GetDefaultStruct(binder, false, ref ignore); if (defs == null) { diagnostics.Add(ErrorCode.ERR_ConceptMethodNotImplementedAndNoDefault, instanceLoc, instance.Name, concept.Name, ImplementingMethod.ToDisplayString()); F.CloseMethod(F.ThrowNull()); return; } // Suppose the target concept is Foo<A, B>. // Then, the default must take type parameters <A, B, FooAB>, // where FooAB : Foo<A, B>. Thus, the arity is one higher than // the concept. if (defs.Arity != concept.Arity + 1) { // Don't use the default struct's location: it is an // implementation detail and may not actually exist. diagnostics.Add(ErrorCode.ERR_DefaultStructBadArity, conceptLoc, concept.Name, defs.Arity, concept.Arity + 1); F.CloseMethod(F.ThrowNull()); return; } // Due to above, arity must be at least 1. var witnessPar = defs.TypeParameters[defs.Arity - 1]; if (!witnessPar.IsConceptWitness) { diagnostics.Add(ErrorCode.ERR_DefaultStructNoWitnessParam, conceptLoc, concept.Name); F.CloseMethod(F.ThrowNull()); return; } var newTypeArguments = GenerateDefaultTypeArguments(); Debug.Assert(newTypeArguments.Length == concept.TypeArguments.Length + 1, "Conversion from concept type parameters to default struct lost or gained some entries."); // Now make the receiver for the call. As usual, it's a default(). var recvType = new ConstructedNamedTypeSymbol(defs, newTypeArguments); var receiver = F.Default(recvType); var arguments = GenerateInnerCallArguments(F); Debug.Assert(arguments.Length == ImplementingMethod.Parameters.Length, "Conversion from parameters to arguments lost or gained some entries."); var call = F.MakeInvocationExpression(BinderFlags.None, F.Syntax, receiver, ImplementingMethod.Name, arguments, diagnostics, ImplementingMethod.TypeArguments); if (call.HasErrors) { F.CloseMethod(F.ThrowNull()); return; } // If whichever call we end up making returns void, then we // can't just return its result; instead, we have to do the // call on its own _then_ return. BoundBlock block; if (call.Type.SpecialType == SpecialType.System_Void) { block = F.Block(F.ExpressionStatement(call), F.Return()); } else { block = F.Block(F.Return(call)); } F.CloseMethod(block); } catch (SyntheticBoundNodeFactory.MissingPredefinedMember ex) { diagnostics.Add(ex.Diagnostic); F.CloseMethod(F.ThrowNull()); } }
protected override BoundExpression GenerateCall(SyntheticBoundNodeFactory f, BoundExpression receiver, ImmutableArray <BoundExpression> arguments, ImmutableArray <RefKind> refKinds, DiagnosticBag diagnostics) { // For user-defined operators, the normal shim call approach works // fine. However, we also want to permit shim calls into types // with builtin operators, for which we need to do an actual // lookup as to which operator we're defining, and a quick // jump into part of the operator overload resolution code to see // if we're actually in one of those types. // // This may be incomplete, but is probably(??) sound. // TODO(@MattWindsor91): consider refkinds? if (ImplementingMethod.OriginalDefinition is SourceUserDefinedOperatorSymbol op) { Debug.Assert(receiver.Kind == BoundKind.TypeExpression, "receiver of an operator should always be a type"); var rectype = (BoundTypeExpression)receiver; var opdecl = op.GetSyntax(); Debug.Assert(opdecl != null, "should have operator syntax here"); var opsyn = opdecl.OperatorToken; var opkind = opsyn.Kind(); var binder = DeclaringCompilation.GetBinder(ImplementingMethod.GetNonNullSyntaxNode()).WithAdditionalFlagsAndContainingMemberOrLambda(BinderFlags.InShim, this); var ovr = new OverloadResolution(binder); var ignore = new HashSet <DiagnosticInfo>(); if (arguments.Length == 1) { (var usuccess, var ukind) = TryGetUnaryOperatorKind(opsyn.Kind()); if (usuccess) { var result = UnaryOperatorOverloadResolutionResult.GetInstance(); ovr.UnaryOperatorOverloadResolution(ukind, arguments[0], result, ref ignore); if (result.SingleValid()) { var bsig = result.Best.Signature; if (bsig.Method == null) { return(f.Unary(bsig.Kind, bsig.ReturnType, arguments[0])); } } } } else if (arguments.Length == 2) { (var bsuccess, var bkind) = TryGetBinaryOperatorKind(opsyn.Kind()); if (bsuccess) { var result = BinaryOperatorOverloadResolutionResult.GetInstance(); ovr.BinaryOperatorOverloadResolution(bkind, arguments[0], arguments[1], result, ref ignore); if (result.SingleValid()) { var bsig = result.Best.Signature; if (bsig.Method == null) { return(f.Binary(bsig.Kind, bsig.ReturnType, arguments[0], arguments[1])); } } } } } return(base.GenerateCall(f, receiver, arguments, refKinds, diagnostics)); }