[mcs] Add string interpolation conversions
authorMarek Safar <marek.safar@gmail.com>
Fri, 9 Jan 2015 22:26:47 +0000 (23:26 +0100)
committerMarek Safar <marek.safar@gmail.com>
Fri, 9 Jan 2015 22:27:18 +0000 (23:27 +0100)
14 files changed:
mcs/errors/cs0029-36.cs [new file with mode: 0644]
mcs/errors/cs0150-4.cs [new file with mode: 0644]
mcs/errors/cs0266-29.cs [new file with mode: 0644]
mcs/errors/cs8094-2.cs [new file with mode: 0644]
mcs/errors/cs8094.cs [new file with mode: 0644]
mcs/mcs/constant.cs
mcs/mcs/convert.cs
mcs/mcs/cs-parser.jay
mcs/mcs/expression.cs
mcs/mcs/report.cs
mcs/mcs/typemanager.cs
mcs/tests/gtest-etree-01.cs
mcs/tests/test-interpolation-02.cs [new file with mode: 0644]
mcs/tests/ver-il-net_4_5.xml

diff --git a/mcs/errors/cs0029-36.cs b/mcs/errors/cs0029-36.cs
new file mode 100644 (file)
index 0000000..781bf1b
--- /dev/null
@@ -0,0 +1,12 @@
+// CS0029: Cannot implicitly convert type `void' to `object'
+// Line: 10
+
+using System;
+
+class TestClass
+{
+       public static void Main ()
+       {
+               Console.WriteLine ($"{ Main () }");             
+       }
+}
\ No newline at end of file
diff --git a/mcs/errors/cs0150-4.cs b/mcs/errors/cs0150-4.cs
new file mode 100644 (file)
index 0000000..80020b9
--- /dev/null
@@ -0,0 +1,15 @@
+// CS0150: A constant value is expected
+// Line: 13
+
+class Program
+{
+       static int Arg ()
+       {
+               return 4;
+       }
+
+       static void Main()
+       {
+               var s = $"{1,Arg()}";
+       }
+}
\ No newline at end of file
diff --git a/mcs/errors/cs0266-29.cs b/mcs/errors/cs0266-29.cs
new file mode 100644 (file)
index 0000000..c24fce2
--- /dev/null
@@ -0,0 +1,10 @@
+// CS0266: Cannot implicitly convert type `double' to `int'. An explicit conversion exists (are you missing a cast?)
+// Line: 8
+
+class Program
+{
+       static void Main()
+       {
+               var s = $"{1, 0.0}";
+       }
+}
\ No newline at end of file
diff --git a/mcs/errors/cs8094-2.cs b/mcs/errors/cs8094-2.cs
new file mode 100644 (file)
index 0000000..531512e
--- /dev/null
@@ -0,0 +1,11 @@
+// CS8094: Alignment value has a magnitude greater than 32767 and may result in a large formatted string
+// Line: 9
+// Compiler options: -warnaserror
+
+class Program
+{
+       static void Main ()
+       {
+               var s = $"{1, int.MinValue }";
+       }
+}
\ No newline at end of file
diff --git a/mcs/errors/cs8094.cs b/mcs/errors/cs8094.cs
new file mode 100644 (file)
index 0000000..a8792ce
--- /dev/null
@@ -0,0 +1,11 @@
+// CS8094: Alignment value has a magnitude greater than 32767 and may result in a large formatted string
+// Line: 9
+// Compiler options: -warnaserror
+
+class Program
+{
+       static void Main ()
+       {
+               var s = $"{1, int.MaxValue }";
+       }
+}
\ No newline at end of file
index 050dd83490f32849fde3ec477cfee0f848c8de34..ca9e6fb7df4d6431d2a0a5ac7e9b317e878dc76a 100644 (file)
@@ -60,7 +60,7 @@ namespace Mono.CSharp {
 
                public override void Error_ValueCannotBeConverted (ResolveContext ec, TypeSpec target, bool expl)
                {
-                       if (!expl && IsLiteral && 
+                       if (!expl && IsLiteral && type.BuiltinType != BuiltinTypeSpec.Type.Double &&
                                BuiltinTypeSpec.IsPrimitiveTypeOrDecimal (target) &&
                                BuiltinTypeSpec.IsPrimitiveTypeOrDecimal (type)) {
                                ec.Report.Error (31, loc, "Constant value `{0}' cannot be converted to a `{1}'",
index 065c9a7e3f8cc8bf7321042febe71c4798a42a82..95f9897c081a7dfbe9bde7d9faef9a60baae01dd 100644 (file)
@@ -1472,6 +1472,12 @@ namespace Mono.CSharp {
                        if (expr_type.IsStruct && TypeSpecComparer.IsEqual (expr_type, target_type))
                                return expr_type == target_type ? expr : EmptyCast.Create (expr, target_type);
 
+                       var interpolated_string = expr as InterpolatedString;
+                       if (interpolated_string != null) {
+                               if (target_type == ec.Module.PredefinedTypes.IFormattable.TypeSpec || target_type == ec.Module.PredefinedTypes.FormattableString.TypeSpec)
+                                       return interpolated_string.ConvertTo (ec, target_type);
+                       }
+
                        return null;
                }
 
index d1ed0daa5439b68dd125346a41e830359d4e16a3..d6f4a51e3708a3982427460cb719095f44995607 100644 (file)
@@ -3368,10 +3368,10 @@ interpolation
          {
                $$ = new InterpolatedStringInsert ((Expression) $1);
          }
-       | expression COMMA LITERAL
+       | expression COMMA expression
          {
                $$ = new InterpolatedStringInsert ((Expression) $1) {
-                       Alignment = (Constant)$3
+                       Alignment = (Expression)$3
                };
          }
        | expression COLON
@@ -3386,7 +3386,7 @@ interpolation
                        Format = (string)$4
                };
          }
-       | expression COMMA LITERAL COLON
+       | expression COMMA expression COLON
          {
                lexer.parsing_interpolation_format = true;
          }
@@ -3395,7 +3395,7 @@ interpolation
                lexer.parsing_interpolation_format = false;
 
                $$ = new InterpolatedStringInsert ((Expression) $1) {
-                       Alignment = (Constant)$3,
+                       Alignment = (Expression)$3,
                        Format = (string) $6
                };
          }
index 90ae2530146888fda195ef22910befc7f67f76d7..c72137b46e3a8370ed08d64c09314cb652b37d7f 100644 (file)
@@ -12379,31 +12379,67 @@ namespace Mono.CSharp
        {
                readonly StringLiteral start, end;
                readonly List<Expression> interpolations;
+               Arguments arguments;
 
                public InterpolatedString (StringLiteral start, List<Expression> interpolations, StringLiteral end)
                {
                        this.start = start;
                        this.end = end;
                        this.interpolations = interpolations;
+                       loc = start.Location;
                }
 
-               public override Expression CreateExpressionTree (ResolveContext ec)
+               public Expression ConvertTo (ResolveContext rc, TypeSpec type)
                {
-                       throw new NotSupportedException ();
+                       var factory = rc.Module.PredefinedTypes.FormattableStringFactory.Resolve ();
+                       if (factory == null)
+                               return null;
+
+                       var ma = new MemberAccess (new TypeExpression (factory, loc), "Create", loc);
+                       var res = new Invocation (ma, arguments).Resolve (rc);
+                       if (res != null && res.Type != type)
+                               res = Convert.ExplicitConversion (rc, res, type, loc);
+
+                       return res;
+               }
+
+               public override bool ContainsEmitWithAwait ()
+               {
+                       if (interpolations == null)
+                               return false;
+
+                       foreach (var expr in interpolations) {
+                               if (expr.ContainsEmitWithAwait ())
+                                       return true;
+                       }
+
+                       return false;
+               }
+
+               public override Expression CreateExpressionTree (ResolveContext rc)
+               {
+                       var best = ResolveBestFormatOverload (rc);
+                       if (best == null)
+                               return null;
+                       
+                       Expression instance = new NullLiteral (loc);
+                       var args = Arguments.CreateForExpressionTree (rc, arguments, instance, new TypeOfMethod (best, loc));
+                       return CreateExpressionFactoryCall (rc, "Call", args);  
                }
 
                protected override Expression DoResolve (ResolveContext rc)
                {
-                       if (interpolations != null) {
+                       string str;
+
+                       if (interpolations == null) {
+                               str = start.Value;
+                               arguments = new Arguments (1);
+                       } else {
                                for (int i = 0; i < interpolations.Count; i += 2) {
                                        var ipi = (InterpolatedStringInsert)interpolations [i];
                                        ipi.Resolve (rc);
                                }
-                       }
        
-                       string str;
-                       Arguments arguments;
-                       if (interpolations != null) {
                                arguments = new Arguments (interpolations.Count);
 
                                var sb = new StringBuilder (start.Value);
@@ -12413,7 +12449,9 @@ namespace Mono.CSharp
                                                var isi = (InterpolatedStringInsert)interpolations [i];
                                                if (isi.Alignment != null) {
                                                        sb.Append (',');
-                                                       sb.Append (isi.Alignment.GetValueAsLong ());
+                                                       var value = isi.ResolveAligment (rc);
+                                                       if (value != null)
+                                                               sb.Append (value.Value);
                                                }
 
                                                if (isi.Format != null) {
@@ -12430,25 +12468,41 @@ namespace Mono.CSharp
 
                                sb.Append (end.Value);
                                str = sb.ToString ();
-                       } else {
-                               arguments = new Arguments (1);
-                               str = start.Value;
                        }
 
                        arguments.Insert (0, new Argument (new StringLiteral (rc.BuiltinTypes, str, start.Location)));
 
-                       var members = MemberCache.FindMembers (rc.BuiltinTypes.String, "Format", true);
-                       var res = new OverloadResolver (members, OverloadResolver.Restrictions.NoBaseMembers, loc);
-                       var best = res.ResolveMember<MethodSpec> (rc, ref arguments);
+                       eclass = ExprClass.Value;
+                       type = rc.BuiltinTypes.String;
+                       return this;
+               }
+
+               public override void Emit (EmitContext ec)
+               {
+                       // No interpolation, convert to simple string result (needs to match string.Format unescaping)
+                       if (interpolations == null) {
+                               var str = start.Value.Replace ("{{", "{").Replace ("}}", "}");
+                               if (str != start.Value)
+                                       new StringConstant (ec.BuiltinTypes, str, loc).Emit (ec);
+                               else
+                                       start.Emit (ec);
+
+                               return;
+                       }
+
+                       var best = ResolveBestFormatOverload (new ResolveContext (ec.MemberContext));
                        if (best == null)
-                               return null;
+                               return;
 
-                       return new Invocation (MethodGroupExpr.CreatePredefined (best, best.DeclaringType, Location.Null), arguments).Resolve (rc);
+                       var ca = new CallEmitter ();
+                       ca.Emit (ec, best, arguments, loc);
                }
 
-               public override void Emit (EmitContext ec)
+               MethodSpec ResolveBestFormatOverload (ResolveContext rc)
                {
-                       throw new NotSupportedException ();
+                       var members = MemberCache.FindMembers (rc.BuiltinTypes.String, "Format", true);
+                       var res = new OverloadResolver (members, OverloadResolver.Restrictions.NoBaseMembers, loc);
+                       return res.ResolveMember<MethodSpec> (rc, ref arguments);
                }
        }
 
@@ -12459,7 +12513,39 @@ namespace Mono.CSharp
                {
                }
 
-               public Constant Alignment { get; set; }
+               public Expression Alignment { get; set; }
                public string Format { get; set; }
+
+               protected override Expression DoResolve (ResolveContext rc)
+               {
+                       var expr = base.DoResolve (rc);
+                       if (expr == null)
+                               return null;
+
+                       //
+                       // For better error reporting, assumes the built-in implementation uses object
+                       // as argument(s)
+                       //
+                       return Convert.ImplicitConversionRequired (rc, expr, rc.BuiltinTypes.Object, expr.Location);
+               }
+
+               public int? ResolveAligment (ResolveContext rc)
+               {
+                       var c = Alignment.ResolveLabelConstant (rc);
+                       if (c == null)
+                               return null;
+                       
+                       c = c.ImplicitConversionRequired (rc, rc.BuiltinTypes.Int);
+                       if (c == null)
+                               return null;
+                       
+                       var value = (int) c.GetValueAsLong ();
+                       if (value > 32767 || value < -32767) {
+                               rc.Report.Warning (8094, 1, Alignment.Location, 
+                                       "Alignment value has a magnitude greater than 32767 and may result in a large formatted string");
+                       }
+
+                       return value;
+               }
        }
 }
index e9b16d1c0ee2d8ac7fa001cb8e649c4c38bab705..74bbb46c640cf42053a1d096fd4472a61804934b 100644 (file)
@@ -58,7 +58,7 @@ namespace Mono.CSharp {
                        3021, 3022, 3023, 3024, 3026, 3027,
                        4014, 4024, 4025, 4026,
                        7035, 7080, 7081, 7082, 7095,
-                       8009,
+                       8009, 8094
                };
 
                static HashSet<int> AllWarningsHashSet;
index 6e785dfc097e7e8b082cbd15a0c79cbaab201adb..b16e2f38b9fefa8e189722f9fc636405a3ee5ff8 100644 (file)
@@ -233,6 +233,11 @@ namespace Mono.CSharp
                public readonly PredefinedType INotifyCompletion;
                public readonly PredefinedType ICriticalNotifyCompletion;
 
+               // C# 6.0
+               public readonly PredefinedType IFormattable;
+               public readonly PredefinedType FormattableString;
+               public readonly PredefinedType FormattableStringFactory;
+
                public PredefinedTypes (ModuleContainer module)
                {
                        TypedReference = new PredefinedType (module, MemberKind.Struct, "System", "TypedReference");
@@ -286,6 +291,10 @@ namespace Mono.CSharp
                        INotifyCompletion = new PredefinedType (module, MemberKind.Interface, "System.Runtime.CompilerServices", "INotifyCompletion");
                        ICriticalNotifyCompletion = new PredefinedType (module, MemberKind.Interface, "System.Runtime.CompilerServices", "ICriticalNotifyCompletion");
 
+                       IFormattable = new PredefinedType (module, MemberKind.Interface, "System", "IFormattable");
+                       FormattableString = new PredefinedType (module, MemberKind.Class, "System", "FormattableString");
+                       FormattableStringFactory = new PredefinedType (module, MemberKind.Class, "System.Runtime.CompilerServices", "FormattableStringFactory");
+
                        //
                        // Define types which are used for comparison. It does not matter
                        // if they don't exist as no error report is needed
@@ -322,6 +331,9 @@ namespace Mono.CSharp
                                TaskGeneric.TypeSpec.IsGenericTask = true;
 
                        SwitchUserTypes = Switch.CreateSwitchUserTypes (module, Nullable.TypeSpec);
+
+                       IFormattable.Define ();
+                       FormattableString.Define ();
                }
        }
 
index 43f605e6b5bac62c5cb503e695ce634e447b34f9..55cd1b0e55c0c1d4875f2a33309e88853d8f60f2 100644 (file)
@@ -730,6 +730,13 @@ class Tester
                e9.Compile ().Invoke (1);
        }               
 
+       void CallTest_10 ()
+       {
+               Expression<Func<string>> e = () => $"{int.MaxValue}";
+               AssertNodeType (e, ExpressionType.Call);
+               Assert (int.MaxValue.ToString (), e.Compile ().Invoke ());
+       }
+
        void CoalesceTest ()
        {
                Expression<Func<uint?, uint>> e = (uint? a) => a ?? 99;
diff --git a/mcs/tests/test-interpolation-02.cs b/mcs/tests/test-interpolation-02.cs
new file mode 100644 (file)
index 0000000..4667ddd
--- /dev/null
@@ -0,0 +1,59 @@
+using System;
+using System.Linq.Expressions;
+
+namespace System
+{
+       public class FormattableString
+       {
+               public FormattableString (string str, object[] arguments)
+               {
+                       Value = str;
+                       Arguments = arguments;
+               }
+
+               public string Value { get; set; }
+               public object[] Arguments;
+       }
+}
+
+namespace System.Runtime.CompilerServices
+{
+       public static class FormattableStringFactory
+       {
+               public static object Create(string format, params object[] arguments)
+               {
+                       if (format.StartsWith ("format"))
+                               return new MyFormattable ();
+
+                       return new FormattableString (format, arguments);
+               }
+       }
+}
+
+class MyFormattable : IFormattable
+{
+       string IFormattable.ToString (string str, IFormatProvider provider)
+       {
+               return null;
+       }
+}
+
+class ConversionTest
+{
+       static int Main ()
+       {
+               byte b = 3;
+
+               FormattableString c1;
+               c1 = $"{b}";
+               if (c1.Value != "{0}")
+                       return 1;
+
+               IFormattable c2;
+               c2 = $"format { b }";
+               if (!(c2 is MyFormattable))
+                       return 2;
+
+               return 0;
+       }
+}
\ No newline at end of file
index 2c71d6f25e3eaa0f446f3ff74954ea542ee2d86c..8f8ca59fc99c6ff6b36e40d9f0ed3c16581d9c95 100644 (file)
       <method name="Void NewTest_8()" attrs="129">\r
         <size>140</size>\r
       </method>\r
+      <method name="Void CallTest_10()" attrs="129">\r
+        <size>137</size>\r
+      </method>\r
     </type>\r
   </test>\r
   <test name="gtest-etree-02.cs">\r
   <test name="test-interpolation-01.cs">\r
     <type name="Test">\r
       <method name="Int32 Main()" attrs="150">\r
-        <size>573</size>\r
+        <size>540</size>\r
+      </method>\r
+      <method name="Void .ctor()" attrs="6278">\r
+        <size>7</size>\r
+      </method>\r
+    </type>\r
+  </test>\r
+  <test name="test-interpolation-02.cs">\r
+    <type name="System.FormattableString">\r
+      <method name="System.String get_Value()" attrs="2182">\r
+        <size>14</size>\r
+      </method>\r
+      <method name="Void set_Value(System.String)" attrs="2182">\r
+        <size>8</size>\r
+      </method>\r
+      <method name="Void .ctor(String, Object[])" attrs="6278">\r
+        <size>22</size>\r
+      </method>\r
+    </type>\r
+    <type name="System.Runtime.CompilerServices.FormattableStringFactory">\r
+      <method name="System.Object Create(System.String, System.Object[])" attrs="150">\r
+        <size>43</size>\r
+      </method>\r
+    </type>\r
+    <type name="MyFormattable">\r
+      <method name="System.String System.IFormattable.ToString(System.String, System.IFormatProvider)" attrs="481">\r
+        <size>10</size>\r
+      </method>\r
+      <method name="Void .ctor()" attrs="6278">\r
+        <size>7</size>\r
+      </method>\r
+    </type>\r
+    <type name="ConversionTest">\r
+      <method name="Int32 Main()" attrs="145">\r
+        <size>120</size>\r
       </method>\r
       <method name="Void .ctor()" attrs="6278">\r
         <size>7</size>\r