瀏覽代碼

added completion unit testing (fixed issue #1050)

Nicolas Cannasse 13 年之前
父節點
當前提交
bbce2b8e69
共有 3 個文件被更改,包括 97 次插入10 次删除
  1. 8 0
      interp.ml
  2. 35 0
      tests/unit/TestType.hx
  3. 54 10
      typer.ml

+ 8 - 0
interp.ml

@@ -96,6 +96,7 @@ type extern_api = {
 	on_generate : (Type.t list -> unit) -> unit;
 	parse_string : string -> Ast.pos -> bool -> Ast.expr;
 	typeof : Ast.expr -> Type.t;
+	get_display : string -> string;
 	type_patch : string -> string -> bool -> string option -> unit;
 	meta_patch : string -> string -> string option -> bool -> unit;
 	set_js_generator : (value -> unit) -> unit;
@@ -2190,6 +2191,13 @@ let macro_lib =
 		"typeof", Fun1 (fun v ->
 			encode_type ((get_ctx()).curapi.typeof (decode_expr v))
 		);
+		"display", Fun1 (fun v ->
+			match v with
+			| VString s ->
+				VString ((get_ctx()).curapi.get_display s)
+			| _ ->
+				error()
+		);
 		"type_patch", Fun4 (fun t f s v ->
 			let p = (get_ctx()).curapi.type_patch in
 			(match t, f, s, v with

+ 35 - 0
tests/unit/TestType.hx

@@ -25,6 +25,11 @@ class TestType extends Test {
 		} catch (e:Dynamic) "true";
 		return { pos: haxe.macro.Context.currentPos(), expr: haxe.macro.Expr.ExprDef.EConst(haxe.macro.Expr.Constant.CIdent(result)) };
 	}
+	
+	@:macro static public function complete(e:String) : haxe.macro.Expr.ExprOf<String> {
+		var str = new String(untyped haxe.macro.Context.load("display", 1)(e.__s));
+		return { expr : EConst(CString(str)), pos : haxe.macro.Context.currentPos() };
+	}
 
 	public function testType() {
 		var name = u("unit")+"."+u("MyClass");
@@ -589,4 +594,34 @@ class TestType extends Test {
 	static function overloadFake_String(a:String) {
 		return a + "foo";
 	}
+	
+	function testCompletion() {
+		#if !macro
+		var s = { foo: 1 };
+		eq(complete("s.|"), "foo:Int");
+		eq(complete("var x : haxe.|"), "path(haxe)");
+		eq(complete("var x : haxe.macro.Expr.|"), "path(haxe.macro:Expr)");
+		
+		// could be improved by listing sub types
+		eq(complete("haxe.macro.Expr.|"), "error(haxe.macro.Expr is not a value)");
+		
+		// know issue : the expr optimization will prevent inferring the array content
+		eq(complete('{
+			var a = [];
+			a.push("");
+			a[0].|
+		}'),"Unknown<0>");
+		
+		// could be improved : expr optimization assume that variable not in scope is a member
+		// so it will eliminate the assignement that would have forced it into the local context
+		// that would be useful when you want to write some code and add the member variable afterwards
+		eq(complete('{
+			unknownVar = "";
+			unknownVar.|
+		}'),"path(unknownVar)");
+		
+		
+		#end
+	}
+	
 }

+ 54 - 10
typer.ml

@@ -2677,6 +2677,16 @@ let make_macro_api ctx p =
 		| TTypeDecl t -> TType (t,List.map snd t.t_types)
 		| TAbstractDecl a -> TAbstract (a,List.map snd a.a_types)
 	in
+	let parse_expr_string s p inl =
+		typing_timer ctx (fun() ->
+			let head = "class X{static function main() " in
+			let head = (if p.pmin > String.length head then head ^ String.make (p.pmin - String.length head) ' ' else head) in
+			let rec loop e = let e = Ast.map_expr loop e in (fst e,p) in
+			match parse_string ctx (head ^ s ^ "}") p inl with
+			| EClass { d_data = [{ cff_name = "main"; cff_kind = FFun { f_expr = Some e } }]} -> if inl then e else loop e
+			| _ -> assert false
+		)
+	in
 	{
 		Interp.pos = p;
 		Interp.get_com = (fun() -> ctx.com);
@@ -2704,19 +2714,53 @@ let make_macro_api ctx p =
 				t()
 			)
 		);
-		Interp.parse_string = (fun s p inl ->
-			typing_timer ctx (fun() ->
-				let head = "class X{static function main() " in
-				let head = (if p.pmin > String.length head then head ^ String.make (p.pmin - String.length head) ' ' else head) in
-				let rec loop e = let e = Ast.map_expr loop e in (fst e,p) in
-				match parse_string ctx (head ^ s ^ "}") p inl with
-				| EClass { d_data = [{ cff_name = "main"; cff_kind = FFun { f_expr = Some e } }]} -> if inl then e else loop e
-				| _ -> assert false
-			)
-		);
+		Interp.parse_string = parse_expr_string;
 		Interp.typeof = (fun e ->
 			typing_timer ctx (fun() -> (type_expr ctx ~need_val:true e).etype)
 		);
+		Interp.get_display = (fun s ->
+			let is_displaying = ctx.com.display in
+			let old_resume = !Parser.resume_display in
+			let restore () =
+				if not is_displaying then begin
+					ctx.com.defines <- PMap.remove "display" ctx.com.defines;
+					ctx.com.display <- false;
+				end;
+				Parser.resume_display := old_resume
+			in
+			(* temporarily enter display mode with a fake position *)
+			if not is_displaying then begin
+				Common.define ctx.com "display";
+				ctx.com.display <- true;
+			end;
+			Parser.resume_display := {
+				Ast.pfile = "macro";
+				Ast.pmin = 0;
+				Ast.pmax = 0;
+			};
+			let str = try
+				let e = parse_expr_string s Ast.null_pos true in
+				let e = Optimizer.optimize_completion_expr e in
+				ignore (type_expr ctx ~need_val:true e);
+				"NO COMPLETION"
+			with DisplayFields fields ->
+				let pctx = print_context() in
+				String.concat "," (List.map (fun (f,t,_) -> f ^ ":" ^ s_type pctx t) fields)				
+			| DisplayTypes tl ->
+				let pctx = print_context() in
+				String.concat "," (List.map (s_type pctx) tl)
+			| Parser.TypePath (p,sub) ->
+				(match sub with
+				| None ->
+					"path(" ^ String.concat "." p ^ ")"
+				| Some (c,_) ->
+					"path(" ^ String.concat "." p ^ ":" ^ c ^ ")")
+			| Typecore.Error (msg,p) ->
+				"error(" ^ error_msg msg ^ ")"
+			in
+			restore();
+			str
+		);
 		Interp.type_patch = (fun t f s v ->
 			typing_timer ctx (fun() ->
 				let v = (match v with None -> None | Some s ->