Browse Source

add @:bypassAccessor (closes #5039) (#8259)

* add @:bypassAccessor (closes #5039)

* try another approach

* check and error if @:bypassAccessor is used without a field access expression
Dan Korostelev 6 years ago
parent
commit
6194be13b2

+ 1 - 0
src/context/typecore.ml

@@ -96,6 +96,7 @@ and typer = {
 	com : context;
 	t : basic_types;
 	g : typer_globals;
+	mutable bypass_accessor : int;
 	mutable meta : metadata;
 	mutable this_stack : texpr list;
 	mutable with_type_stack : WithType.t list;

+ 2 - 0
src/core/meta.ml

@@ -17,6 +17,7 @@ type strict_meta =
 	| BridgeProperties
 	| Build
 	| BuildXml
+	| BypassAccessor
 	| Callable
 	| Class
 	| ClassCode
@@ -225,6 +226,7 @@ let get_info = function
 	| BridgeProperties -> ":bridgeProperties",("Creates native property bridges for all Haxe properties in this class",[UsedOn TClass;Platform Cs])
 	| Build -> ":build",("Builds a class or enum from a macro",[HasParam "Build macro call";UsedOnEither [TClass;TEnum]])
 	| BuildXml -> ":buildXml",("Specify xml data to be injected into Build.xml",[Platform Cpp])
+	| BypassAccessor -> ":bypassAccessor",("Do not call property accessor method and access the field directly",[UsedOn TExpr])
 	| Callable -> ":callable",("Abstract forwards call to its underlying type",[UsedOn TAbstract])
 	| Class -> ":class",("Used internally to annotate an enum that will be generated as a class",[Platforms [Java;Cs]; UsedOn TEnum; UsedInternally])
 	| ClassCode -> ":classCode",("Used to inject platform-native code into a class",[Platforms [Java;Cs]; UsedOn TClass])

+ 15 - 1
src/typing/fields.ml

@@ -137,6 +137,7 @@ let check_constructor_access ctx c f p =
 	if not (can_access ctx c f true || is_parent c ctx.curclass) && not ctx.untyped then display_error ctx (Printf.sprintf "Cannot access private constructor of %s" (s_class_path c)) p
 
 let field_access ctx mode f fmode t e p =
+	let bypass_accessor = if ctx.bypass_accessor > 0 then (ctx.bypass_accessor <- ctx.bypass_accessor - 1; true) else false in
 	let fnormal() = AKExpr (mk (TField (e,fmode)) t p) in
 	let normal() =
 		match follow e.etype with
@@ -214,7 +215,20 @@ let field_access ctx mode f fmode t e p =
 				| _ ->
 					false
 			in
-			if m = ctx.curfield.cf_name && (match e.eexpr with TConst TThis -> true | TLocal v -> Option.map_default (fun vthis -> v == vthis) false ctx.vthis | TTypeExpr (TClassDecl c) when c == ctx.curclass -> true | _ -> false) then
+			let bypass_accessor =
+				bypass_accessor
+				||
+				(
+					m = ctx.curfield.cf_name
+					&&
+					match e.eexpr with
+					| TConst TThis -> true
+					| TLocal v -> Option.map_default (fun vthis -> v == vthis) false ctx.vthis
+					| TTypeExpr (TClassDecl c) when c == ctx.curclass -> true
+					| _ -> false
+				)
+			in
+			if bypass_accessor then
 				let prefix = (match ctx.com.platform with Flash when Common.defined ctx.com Define.As3 -> "$" | _ -> "") in
 				(match e.eexpr with TLocal _ when Common.defined ctx.com Define.Haxe3Compat -> ctx.com.warning "Field set has changed here in Haxe 4: call setter explicitly to keep Haxe 3.x behaviour" p | _ -> ());
 				if not (is_physical_field f) then begin

+ 1 - 0
src/typing/typeloadModule.ml

@@ -841,6 +841,7 @@ let type_types_into_module ctx m tdecls p =
 			module_imports = [];
 		};
 		is_display_file = (ctx.com.display.dms_kind <> DMNone && DisplayPosition.display_position#is_in_file m.m_extra.m_file);
+		bypass_accessor = 0;
 		meta = [];
 		this_stack = [];
 		with_type_stack = [];

+ 7 - 0
src/typing/typer.ml

@@ -2236,6 +2236,12 @@ and type_meta ctx m e1 with_type p =
 		| (Meta.NullSafety, [(EConst (Ident "Off"), _)],_) ->
 			let e = e() in
 			{e with eexpr = TMeta(m,e)}
+		| (Meta.BypassAccessor,_,p) ->
+			let old_counter = ctx.bypass_accessor in
+			ctx.bypass_accessor <- old_counter + 1;
+			let e = e () in
+			(if ctx.bypass_accessor > old_counter then display_error ctx "Field access expression expected after @:bypassAccessor metadata" p);
+			e
 		| (Meta.Inline,_,_) ->
 			begin match fst e1 with
 			| ECall(e1,el) ->
@@ -2523,6 +2529,7 @@ let rec create com =
 			module_imports = [];
 		};
 		is_display_file = false;
+		bypass_accessor = 0;
 		meta = [];
 		this_stack = [];
 		with_type_stack = [];

+ 5 - 0
tests/misc/projects/Issue5039/Main.hx

@@ -0,0 +1,5 @@
+class Main {
+	static function main() {
+		@:bypassAccessor "hi";
+	}
+}

+ 6 - 0
tests/misc/projects/Issue5039/Main2.hx

@@ -0,0 +1,6 @@
+class Main {
+	static var a = {b: 3};
+	static function main() {
+		@:bypassAccessor @:bypassAccessor @:bypassAccessor a.b;
+	}
+}

+ 2 - 0
tests/misc/projects/Issue5039/compile-fail.hxml

@@ -0,0 +1,2 @@
+--main Main
+--interp

+ 1 - 0
tests/misc/projects/Issue5039/compile-fail.hxml.stderr

@@ -0,0 +1 @@
+Main.hx:3: characters 3-19 : Field access expression expected after @:bypassAccessor metadata

+ 2 - 0
tests/misc/projects/Issue5039/compile2-fail.hxml

@@ -0,0 +1,2 @@
+--main Main2
+--interp

+ 1 - 0
tests/misc/projects/Issue5039/compile2-fail.hxml.stderr

@@ -0,0 +1 @@
+Main2.hx:4: characters 3-19 : Field access expression expected after @:bypassAccessor metadata

+ 48 - 0
tests/unit/src/unit/issues/Issue5039.hx

@@ -0,0 +1,48 @@
+package unit.issues;
+
+private class C {
+	@:isVar public var prop(get,never):Int = 42;
+	function get_prop():Int throw "FAIL prop";
+
+	public function new() {}
+}
+
+class Issue5039 extends Test {
+	var getterCalled = false;
+	var setterCalled = false;
+
+	@:isVar var x(get,set):Int = 4;
+	function get_x():Int {
+		getterCalled = true;
+		return x;
+	}
+
+	function set_x(value:Int):Int {
+		setterCalled = true;
+		return x = value;
+	}
+
+	@:isVar public var c(get,never):C = new C();
+	function get_c():C throw "FAIL c";
+
+	function test() {
+		@:bypassAccessor x = x + 1;
+		t(getterCalled);
+		f(setterCalled);
+
+		getterCalled = false;
+
+		x = @:bypassAccessor x + 1;
+		f(getterCalled);
+		t(setterCalled);
+
+		setterCalled = false;
+
+		@:bypassAccessor x = @:bypassAccessor x + 1;
+		f(getterCalled);
+		f(setterCalled);
+
+		eq(42, @:bypassAccessor (@:bypassAccessor c).prop);
+		eq(42, @:bypassAccessor @:bypassAccessor c.prop);
+	}
+}