Browse Source

[cs] null string -> "null"; many unit test fixes

Caue Waneck 13 years ago
parent
commit
7f3a53af8b
6 changed files with 65 additions and 15 deletions
  1. 15 8
      gencommon.ml
  2. 20 4
      gencs.ml
  3. 1 1
      std/cs/_std/Math.hx
  4. 3 0
      std/cs/_std/Type.hx
  5. 9 1
      std/cs/internal/Runtime.hx
  6. 17 1
      tests/unit/TestBasetypes.hx

+ 15 - 8
gencommon.ml

@@ -1906,8 +1906,9 @@ struct
   
   
   let priority_as_synf = 100.0 (*solve_deps name [DBefore ExpressionUnwrap.priority]*)
   let priority_as_synf = 100.0 (*solve_deps name [DBefore ExpressionUnwrap.priority]*)
   
   
-  let abstract_implementation gen (should_change:texpr->bool) (equals_handler:texpr->texpr->texpr) (dyn_plus_handler:texpr->texpr->texpr->texpr) (compare_handler:texpr->texpr->texpr) =
-    
+  let abstract_implementation gen ?(handle_strings = true) (should_change:texpr->bool) (equals_handler:texpr->texpr->texpr) (dyn_plus_handler:texpr->texpr->texpr->texpr) (compare_handler:texpr->texpr->texpr) =
+  
+  
     let get_etype_one e =
     let get_etype_one e =
       match follow e.etype with
       match follow e.etype with
         | TInst({cl_path = ([],"Int")},[]) -> (gen.gcon.basic.tint, { eexpr = TConst(TInt(Int32.one)); etype = gen.gcon.basic.tint; epos = e.epos })
         | TInst({cl_path = ([],"Int")},[]) -> (gen.gcon.basic.tint, { eexpr = TConst(TInt(Int32.one)); etype = gen.gcon.basic.tint; epos = e.epos })
@@ -1949,7 +1950,7 @@ struct
             | OpNotEq -> (* != -> !equals() *)
             | OpNotEq -> (* != -> !equals() *)
               mk_paren { eexpr = TUnop(Ast.Not, Prefix, (equals_handler (run e1) (run e2))); etype = gen.gcon.basic.tbool; epos = e.epos }
               mk_paren { eexpr = TUnop(Ast.Not, Prefix, (equals_handler (run e1) (run e2))); etype = gen.gcon.basic.tbool; epos = e.epos }
             | OpAdd  ->
             | OpAdd  ->
-              if is_string e.etype or is_string e1.etype or is_string e2.etype then 
+              if handle_strings && (is_string e.etype or is_string e1.etype or is_string e2.etype) then 
                 { e with eexpr = TBinop(op, mk_cast gen.gcon.basic.tstring (run e1), mk_cast gen.gcon.basic.tstring (run e2)) }
                 { e with eexpr = TBinop(op, mk_cast gen.gcon.basic.tstring (run e1), mk_cast gen.gcon.basic.tstring (run e2)) }
               else
               else
                 dyn_plus_handler e (run e1) (run e2)
                 dyn_plus_handler e (run e1) (run e2)
@@ -4596,7 +4597,8 @@ struct
     
     
     let in_value = ref false in
     let in_value = ref false in
             
             
-    let rec run e =
+    let rec run ?(just_type = false) e =
+      let handle = if not just_type then handle else fun e t1 t2 -> { e with etype = gen.greal_type t2 } in
       let was_in_value = !in_value in
       let was_in_value = !in_value in
       in_value := true;
       in_value := true;
       match e.eexpr with 
       match e.eexpr with 
@@ -4605,12 +4607,17 @@ struct
           (match field_access gen (gen.greal_type tf.etype) f with
           (match field_access gen (gen.greal_type tf.etype) f with
             | FClassField(cl,params,_,is_static,actual_t) -> 
             | FClassField(cl,params,_,is_static,actual_t) -> 
               let actual_t = if is_static then actual_t else apply_params cl.cl_types params actual_t in
               let actual_t = if is_static then actual_t else apply_params cl.cl_types params actual_t in
-              { e with eexpr = TBinop(op, Type.map_expr run e1, handle (run e2) actual_t e2.etype) }
-            | _ -> { e with eexpr = TBinop(op, Type.map_expr run e1, handle (run e2) e1.etype e2.etype) }
+              
+              let e1 = run e1 ~just_type:true in
+              { e with eexpr = TBinop(op, e1, handle (run e2) actual_t e2.etype); etype = e1.etype }
+            | _ -> 
+              let e1 = run e1 ~just_type:true in
+              { e with eexpr = TBinop(op, e1, handle (run e2) e1.etype e2.etype); etype = e1.etype }
           )
           )
         | TBinop ( (Ast.OpAssign as op),e1,e2)
         | TBinop ( (Ast.OpAssign as op),e1,e2)
         | TBinop ( (Ast.OpAssignOp _ as op),e1,e2) ->
         | TBinop ( (Ast.OpAssignOp _ as op),e1,e2) ->
-          { e with eexpr = TBinop(op, Type.map_expr run e1, handle (run e2) e1.etype e2.etype) }
+          let e1 = run e1 ~just_type:true in
+          { e with eexpr = TBinop(op, e1, handle (run e2) e1.etype e2.etype); etype = e1.etype }
         (* this is an exception so we can avoid infinite loop on Std.String and haxe.lang.Runtime.toString(). It also takes off unnecessary casts to string *)
         (* this is an exception so we can avoid infinite loop on Std.String and haxe.lang.Runtime.toString(). It also takes off unnecessary casts to string *)
         | TBinop ( Ast.OpAdd, ( { eexpr = TCast(e1, _) } as e1c), e2 ) when native_string_cast && is_string e1c.etype && is_string e2.etype ->
         | TBinop ( Ast.OpAdd, ( { eexpr = TCast(e1, _) } as e1c), e2 ) when native_string_cast && is_string e1c.etype && is_string e2.etype ->
           { e with eexpr = TBinop( Ast.OpAdd, run e1, run e2 ) }
           { e with eexpr = TBinop( Ast.OpAdd, run e1, run e2 ) }
@@ -5259,7 +5266,7 @@ struct
     
     
     let handle_assign op left right =
     let handle_assign op left right =
       let left = check_left left in
       let left = check_left left in
-      Some (apply_assign (fun e -> { e with eexpr = TBinop(op, left, e) }) right )
+      Some (apply_assign (fun e -> { e with eexpr = TBinop(op, left, if is_void left.etype then e else gen.ghandle_cast left.etype e.etype e) }) right )
     in
     in
     
     
     let is_problematic_if right =
     let is_problematic_if right =

+ 20 - 4
gencs.ml

@@ -118,6 +118,11 @@ struct
     let rec run e =
     let rec run e =
       match e.eexpr with 
       match e.eexpr with 
         (* Std.is() *)
         (* Std.is() *)
+        | TCall(
+            { eexpr = TField( { eexpr = TTypeExpr ( TClassDecl { cl_path = ([], "Std") } ) }, "is") },
+            [ obj; { eexpr = TTypeExpr(TClassDecl { cl_path = [], "Dynamic" }) }]
+          ) ->
+            Type.map_expr run e
         | TCall(
         | TCall(
             { eexpr = TField( { eexpr = TTypeExpr ( TClassDecl { cl_path = ([], "Std") } ) }, "is") },
             { eexpr = TField( { eexpr = TTypeExpr ( TClassDecl { cl_path = ([], "Std") } ) }, "is") },
             [ obj; { eexpr = TTypeExpr(md) }]
             [ obj; { eexpr = TTypeExpr(md) }]
@@ -166,7 +171,7 @@ struct
               mk_is obj md
               mk_is obj md
           )
           )
         (* end Std.is() *)
         (* end Std.is() *)
-                  
+        
         | TBinop( Ast.OpUShr, e1, e2 ) ->
         | TBinop( Ast.OpUShr, e1, e2 ) ->
           mk_cast e.etype { e with eexpr = TBinop( Ast.OpShr, mk_cast (TType(uint,[])) (run e1), run e2 ) }
           mk_cast e.etype { e with eexpr = TBinop( Ast.OpShr, mk_cast (TType(uint,[])) (run e1), run e2 ) }
         
         
@@ -1645,7 +1650,9 @@ let configure gen =
     (DynamicOperators.abstract_implementation gen (fun e -> match e.eexpr with
     (DynamicOperators.abstract_implementation gen (fun e -> match e.eexpr with
       | TBinop (Ast.OpEq, e1, e2)
       | TBinop (Ast.OpEq, e1, e2)
       | TBinop (Ast.OpNotEq, e1, e2) -> should_handle_opeq e1.etype or should_handle_opeq e2.etype
       | TBinop (Ast.OpNotEq, e1, e2) -> should_handle_opeq e1.etype or should_handle_opeq e2.etype
-      | TBinop (Ast.OpAdd, e1, e2) -> is_dynamic e1.etype or is_dynamic e2.etype or is_type_param e1.etype or is_type_param e2.etype
+      | TBinop (Ast.OpAssignOp Ast.OpAdd, e1, e2) ->
+        is_dynamic_expr e1 || is_null_expr e1 || is_string e.etype
+      | TBinop (Ast.OpAdd, e1, e2) -> is_dynamic e1.etype or is_dynamic e2.etype or is_type_param e1.etype or is_type_param e2.etype or is_string e1.etype or is_string e2.etype or is_string e.etype
       | TBinop (Ast.OpLt, e1, e2)
       | TBinop (Ast.OpLt, e1, e2)
       | TBinop (Ast.OpLte, e1, e2)
       | TBinop (Ast.OpLte, e1, e2)
       | TBinop (Ast.OpGte, e1, e2)
       | TBinop (Ast.OpGte, e1, e2)
@@ -1685,6 +1692,15 @@ let configure gen =
             basic.tint, basic.tint
             basic.tint, basic.tint
           else t1, t2 in
           else t1, t2 in
           { eexpr = TBinop(Ast.OpAdd, mk_cast t1 e1, mk_cast t2 e2); etype = e.etype; epos = e1.epos }
           { eexpr = TBinop(Ast.OpAdd, mk_cast t1 e1, mk_cast t2 e2); etype = e.etype; epos = e1.epos }
+        | _ when is_string e.etype || is_string e1.etype || is_string e2.etype ->
+          {
+            eexpr = TCall(
+              mk_static_field_access_infer runtime_cl "concat" e.epos [],
+              [ e1; e2 ]
+            );
+            etype = basic.tstring;
+            epos = e.epos
+          }
         | _ ->
         | _ ->
           let static = mk_static_field_access_infer (runtime_cl) "plus"  e1.epos [] in
           let static = mk_static_field_access_infer (runtime_cl) "plus"  e1.epos [] in
           mk_cast e.etype { eexpr = TCall(static, [e1; e2]); etype = t_dynamic; epos=e1.epos })
           mk_cast e.etype { eexpr = TCall(static, [e1; e2]); etype = t_dynamic; epos=e1.epos })
@@ -1694,7 +1710,7 @@ let configure gen =
       end else begin
       end else begin
         let static = mk_static_field_access_infer (runtime_cl) "compare" e1.epos [] in
         let static = mk_static_field_access_infer (runtime_cl) "compare" e1.epos [] in
         { eexpr = TCall(static, [e1; e2]); etype = gen.gcon.basic.tint; epos=e1.epos } 
         { eexpr = TCall(static, [e1; e2]); etype = gen.gcon.basic.tint; epos=e1.epos } 
-      end));
+      end) ~handle_strings:false);
   
   
   FilterClosures.configure gen (FilterClosures.traverse gen (fun e1 s -> true) closure_func);
   FilterClosures.configure gen (FilterClosures.traverse gen (fun e1 s -> true) closure_func);
     
     
@@ -1742,7 +1758,7 @@ let configure gen =
     get_typeof e
     get_typeof e
   ));
   ));
   
   
-  CastDetect.configure gen (CastDetect.default_implementation gen (Some (TEnum(empty_e, []))) false~native_string_cast:false);
+  CastDetect.configure gen (CastDetect.default_implementation gen (Some (TEnum(empty_e, []))) false ~native_string_cast:false);
   
   
   (*FollowAll.configure gen;*)
   (*FollowAll.configure gen;*)
   
   

+ 1 - 1
std/cs/_std/Math.hx

@@ -71,7 +71,7 @@ import system.Random;
 	
 	
 	public static inline function round(v:Float):Int
 	public static inline function round(v:Float):Int
 	{
 	{
-		return Std.int(v < 0 ? system.Math.Floor(v) : system.Math.Round(v)) ;
+		return Std.int(system.Math.Round(v));
 	}
 	}
 	
 	
 	public static inline function floor(v:Float):Int
 	public static inline function floor(v:Float):Int

+ 3 - 0
std/cs/_std/Type.hx

@@ -85,6 +85,8 @@ enum ValueType {
 			case "System.Int32": "Int";
 			case "System.Int32": "Int";
 			case "System.Double": "Float";
 			case "System.Double": "Float";
 			case "System.String": "String";
 			case "System.String": "String";
+			case "System.Object": "Dynamic";
+			case "System.Type": "Class";
 			default: ret.split("`")[0];
 			default: ret.split("`")[0];
 		}
 		}
 	}
 	}
@@ -116,6 +118,7 @@ enum ValueType {
 				case #if no-root "haxe.root.Float" #else "Float" #end: return Float;
 				case #if no-root "haxe.root.Float" #else "Float" #end: return Float;
 				case #if no-root "haxe.root.Class" #else "Class" #end: return Class;
 				case #if no-root "haxe.root.Class" #else "Class" #end: return Class;
 				case #if no-root "haxe.root.String" #else "String" #end: return String;
 				case #if no-root "haxe.root.String" #else "String" #end: return String;
+				case #if no-root "haxe.root.Dynamic" #else "Dynamic" #end: return Dynamic;
 				default: return null;
 				default: return null;
 			}
 			}
 		} else if (t.IsInterface && cast(untyped __typeof__(IGenericObject), system.Type).IsAssignableFrom(t)) { 
 		} else if (t.IsInterface && cast(untyped __typeof__(IGenericObject), system.Type).IsAssignableFrom(t)) { 

+ 9 - 1
std/cs/internal/Runtime.hx

@@ -211,7 +211,7 @@ import system.Type;
 	
 	
 	@:functionBody('
 	@:functionBody('
 			if (v1 is string || v2 is string)
 			if (v1 is string || v2 is string)
-				return (v1 + "") + (v2 + "");
+				return Std.@string(v1) + Std.@string(v2);
 			
 			
 			System.IConvertible cv1 = v1 as System.IConvertible;
 			System.IConvertible cv1 = v1 as System.IConvertible;
 			if (cv1 != null)
 			if (cv1 != null)
@@ -622,6 +622,14 @@ import system.Type;
 		return null;
 		return null;
 	}
 	}
 	
 	
+	@:functionBody('
+		return (s1 == null ? "null" : s1) + (s2 == null ? "null" : s2);
+	')
+	public static function concat(s1:String, s2:String):String
+	{
+		return null;
+	}
+	
 	//TODO: change from genericCast to getConverter, so we don't need to handle extra boxing associated with it
 	//TODO: change from genericCast to getConverter, so we don't need to handle extra boxing associated with it
 	/*@:functionBody('
 	/*@:functionBody('
 		if (typeof(To).TypeHandle == typeof(double).TypeHandle)
 		if (typeof(To).TypeHandle == typeof(double).TypeHandle)

+ 17 - 1
tests/unit/TestBasetypes.hx

@@ -75,7 +75,23 @@ class TestBasetypes extends Test {
 		var x:String = null;
 		var x:String = null;
 		eq("hello" +x, "hellonull");
 		eq("hello" +x, "hellonull");
 		eq(x + "hello", "nullhello");
 		eq(x + "hello", "nullhello");
-
+		
+		{
+			//testing correct substitution of += on string transformations
+			var arr = ["hello"];
+			var i = 0;
+			eq(arr[i++] += arr[i], "hellonull");
+			eq(arr[0], "hellonull");
+		}
+		
+		{
+			//testing correct substitution of += on dynamically-typed string transformations
+			var arr:Array<Dynamic> = ["hello"];
+			var i = 0;
+			eq( cast(arr[i++] += arr[i], String), "hellonull");
+			eq(arr[0], "hellonull");
+		}
+		
 		var x = { hello:"world", val:5 };
 		var x = { hello:"world", val:5 };
 		var xs = "" + x;
 		var xs = "" + x;
 		// Output should contain hello followed by world, and val followed by 5.
 		// Output should contain hello followed by world, and val followed by 5.