/// <summary> /// Emits the given conversion. 'from' and 'to' matches the classified conversion. /// </summary> public static void EmitConversion(this CodeGenerator cg, CommonConversion conversion, TypeSymbol from, TypeSymbol to, TypeSymbol op = null, bool @checked = false) { // {from}, {op} is loaded on stack // if (conversion.Exists == false) { throw cg.NotImplementedException($"Conversion from '{from}' to '{to}' "); } if (conversion.IsIdentity) { if (op != null) { throw new ArgumentException(nameof(op)); } if (to.SpecialType == SpecialType.System_Void) { // POP cg.EmitPop(from); } // nop } else if (conversion.IsNumeric) { if (op != null) { throw new ArgumentException(nameof(op)); } EmitNumericConversion(cg, from, to, @checked: @checked); } else if (conversion.IsReference) { if (op != null) { throw new ArgumentException(nameof(op)); } // TODO: ensure from/to is a valid reference type cg.EmitCastClass(to); } else if (conversion.IsUserDefined) { var method = (MethodSymbol)conversion.MethodSymbol; var ps = method.Parameters; int pconsumed = 0; if (method.HasThis) { if (from.IsValueType) { if (op != null) { throw new ArgumentException(nameof(op)); } cg.EmitStructAddr(from); } } else { if (ps[0].RefKind != RefKind.None) { throw new InvalidOperationException(); } if (from != ps[0].Type) { if (op != null) { if (!ps[0].Type.IsAssignableFrom(from)) { throw new ArgumentException(nameof(op)); } } else { EmitImplicitConversion(cg, from, ps[0].Type, @checked: @checked); } } pconsumed++; } if (op != null) { if (ps.Length > pconsumed) { EmitImplicitConversion(cg, op, ps[pconsumed].Type, @checked: @checked); } pconsumed++; } // Context ctx, if (ps.Length > pconsumed && SpecialParameterSymbol.IsContextParameter(ps[pconsumed])) { cg.EmitLoadContext(); pconsumed++; } if (ps.Length != pconsumed) { throw new InvalidOperationException(); } EmitImplicitConversion(cg, cg.EmitCall(method.IsVirtual ? ILOpCode.Callvirt : ILOpCode.Call, method), to, @checked: true); } else { throw new NotImplementedException(); } }
// resolve operator method public MethodSymbol ResolveOperator(TypeSymbol receiver, bool hasref, string[] opnames, TypeSymbol[] extensions, TypeSymbol operand = null, TypeSymbol target = null) { Debug.Assert(receiver != null); Debug.Assert(opnames != null && opnames.Length != 0); MethodSymbol candidate = null; int candidatecost = int.MaxValue; // candidate cost int candidatecost_minor = 0; // second cost for (int ext = -1; ext < extensions.Length; ext++) { // TODO: go through interfaces for (var container = ext < 0 ? receiver : extensions[ext]; container != null; container = container.IsStatic ? null : container.BaseType) { if (container.SpecialType == SpecialType.System_ValueType) { continue; // } for (int i = 0; i < opnames.Length; i++) { var members = container.GetMembers(opnames[i]); for (int m = 0; m < members.Length; m++) { if (members[m] is MethodSymbol method) { if (ext >= 0 && !method.IsStatic) { continue; // only static methods allowed in extension containers } if (method.DeclaredAccessibility != Accessibility.Public) { continue; } if (method.Arity != 0) { continue; // CONSIDER } // TODO: replace with overload resolution int cost = 0; int cost_minor = 0; if (target != null && method.ReturnType != target) { var conv = ClassifyConversion(method.ReturnType, target, ConversionKind.Numeric | ConversionKind.Reference); if (conv.Exists) // TODO: chain the conversion, sum the cost { cost += ConvCost(conv, method.ReturnType, target); } else { continue; } } var ps = method.Parameters; int pconsumed = 0; // TSource receiver, if (method.IsStatic) { if (ps.Length <= pconsumed) { continue; } bool isbyref = ps[pconsumed].RefKind != RefKind.None; if (isbyref && hasref == false) { continue; } // if (container != receiver && ps[pconsumed].HasThisAttribute == false) continue; // [ThisAttribute] // proper extension method var pstype = ps[pconsumed].Type; if (pstype != receiver) { if (isbyref) { continue; // cannot convert addr } var conv = ClassifyConversion(receiver, pstype, ConversionKind.Numeric | ConversionKind.Reference); if (conv.Exists) // TODO: chain the conversion { cost += ConvCost(conv, receiver, pstype); } else { continue; } } pconsumed++; } // Context ctx, if (pconsumed < ps.Length && SpecialParameterSymbol.IsContextParameter(ps[pconsumed])) { pconsumed++; cost_minor--; // specialized operator - prefered } // TOperand, if (operand != null) { if (ps.Length <= pconsumed) { continue; } if (ps[pconsumed].Type != operand) { var conv = ClassifyConversion(operand, ps[pconsumed].Type, ConversionKind.Implicit); if (conv.Exists) // TODO: chain the conversion { cost += ConvCost(conv, operand, ps[pconsumed].Type); } else { continue; } } pconsumed++; } // Context ctx, if (pconsumed < ps.Length && SpecialParameterSymbol.IsContextParameter(ps[pconsumed])) { pconsumed++; cost_minor--; // specialized operator - prefered } if (ps.Length != pconsumed) { continue; } if (container.SpecialType == SpecialType.System_Object || container.IsValueType) { //cost++; // should be enabled cost_minor++; // implicit conversion } // if (cost < candidatecost || (cost == candidatecost && cost_minor < candidatecost_minor)) { candidate = method; candidatecost = cost; candidatecost_minor = cost_minor; } } } } } } // return(candidate); }
/// <summary> /// Emits the given conversion. 'from' and 'to' matches the classified conversion. /// </summary> public static void EmitConversion(this CodeGenerator cg, CommonConversion conversion, TypeSymbol from, TypeSymbol to, TypeSymbol op = null, bool @checked = false) { // {from}, {op} is loaded on stack // if (conversion.Exists == false) { throw cg.NotImplementedException($"Conversion from '{from}' to '{to}' "); } if (conversion.IsNullable) { if (from.IsNullableType(out var ttype)) { if (op != null) { // TODO throw new ArgumentException(nameof(op)); } var lbltrue = new NamedLabel("has value"); var lblend = new NamedLabel("end"); var tmp = cg.GetTemporaryLocal(from, true); cg.Builder.EmitLocalStore(tmp); // Template: tmp.HasValue ? convert(tmp.Value) : default cg.Builder.EmitLocalAddress(tmp); cg.EmitCall(ILOpCode.Call, cg.DeclaringCompilation.System_Nullable_T_HasValue(from)); cg.Builder.EmitBranch(ILOpCode.Brtrue, lbltrue); // default: cg.EmitLoadDefault(to); cg.Builder.EmitBranch(ILOpCode.Br, lblend); // cg.Builder.AdjustStack(-1); // ? // lbltrue: cg.Builder.MarkLabel(lbltrue); // Template: convert( tmp.GetValueOrDefault() ) cg.Builder.EmitLocalAddress(tmp); cg.EmitCall(ILOpCode.Call, cg.DeclaringCompilation.System_Nullable_T_GetValueOrDefault(from)).Expect(ttype); EmitConversion(cg, conversion.WithIsNullable(false), ttype, to, op, @checked); // lblend: cg.Builder.MarkLabel(lblend); return; } if (to.IsNullableType(out ttype)) // NOTE: not used yet { // new Nullable<TType>( convert(from) ) EmitConversion(cg, conversion.WithIsNullable(false), from, ttype, op, @checked); cg.EmitCall(ILOpCode.Newobj, ((NamedTypeSymbol)to).InstanceConstructors[0]); // new Nullable<T>( STACK ) return; } throw Roslyn.Utilities.ExceptionUtilities.Unreachable; } if (conversion.IsIdentity) { if (op != null) { throw new ArgumentException(nameof(op)); } if (to.SpecialType == SpecialType.System_Void) { // POP cg.EmitPop(from); } // nop } else if (conversion.IsNumeric) { if (op != null) { throw new ArgumentException(nameof(op)); } EmitNumericConversion(cg, from, to, @checked: @checked); } else if (conversion.IsReference) { if (op != null) { throw new ArgumentException(nameof(op)); } // TODO: ensure from/to is a valid reference type cg.EmitCastClass(to); } else if (conversion.IsUserDefined) { var method = (MethodSymbol)conversion.MethodSymbol; var ps = method.Parameters; int pconsumed = 0; if (method.HasThis) { if (from.IsValueType) { if (op != null || from.IsVoid()) { throw new ArgumentException(nameof(op)); } cg.EmitStructAddr(from); } } else { if (ps[0].RefKind != RefKind.None) { throw new InvalidOperationException(); } if (from != ps[0].Type) { if (op != null) { if (!ps[0].Type.IsAssignableFrom(from)) { throw new ArgumentException(nameof(op)); } } else { EmitImplicitConversion(cg, from, ps[0].Type, @checked: @checked); } } pconsumed++; } if (op != null) { if (ps.Length > pconsumed) { EmitImplicitConversion(cg, op, ps[pconsumed].Type, @checked: @checked); } pconsumed++; } // Context ctx, if (ps.Length > pconsumed && SpecialParameterSymbol.IsContextParameter(ps[pconsumed])) { cg.EmitLoadContext(); pconsumed++; } if (ps.Length != pconsumed) { throw new InvalidOperationException(); } EmitImplicitConversion(cg, cg.EmitCall(method.IsVirtual ? ILOpCode.Callvirt : ILOpCode.Call, method), to, @checked: true); } else { throw new NotImplementedException(); } }