Bladeren bron

make `in` a Binop and remove `EIn` (closes #6224) (#6471)

* make `in` a Binop and remove `EIn` (closes #6224)

* use make_binop

* fix enumIndex in tests

* change OpIn precedence to be lessthan OpAssign(Op)
Dan Korostelev 8 jaren geleden
bovenliggende
commit
52f680c73a

+ 1 - 1
src/display/display.ml

@@ -239,7 +239,7 @@ module DocumentSymbols = struct
 			| EFunction(Some s,f) ->
 				add s Function p;
 				func parent f
-			| EIn((EConst(Ident s),p),e2) ->
+			| EBinop(OpIn,(EConst(Ident s),p),e2) ->
 				add s Variable p;
 				expr parent e2;
 			| _ ->

+ 1 - 1
src/generators/gencommon/dynamicOperators.ml

@@ -118,7 +118,7 @@ let init com handle_strings (should_change:texpr->bool) (equals_handler:texpr->t
 				{ e with eexpr = TBinop (op, mk_cast com.basic.tbool (run e1), mk_cast com.basic.tbool (run e2)) }
 			| OpAnd | OpOr | OpXor | OpShl | OpShr | OpUShr ->
 				{ e with eexpr = TBinop (op, mk_cast com.basic.tint (run e1), mk_cast com.basic.tint (run e2)) }
-			| OpAssign | OpAssignOp _ | OpInterval | OpArrow ->
+			| OpAssign | OpAssignOp _ | OpInterval | OpArrow | OpIn ->
 				assert false)
 
 		| TUnop (Increment as op, flag, e1)

+ 3 - 0
src/generators/gencpp.ml

@@ -4092,6 +4092,7 @@ let gen_cpp_ast_expression_tree ctx class_name func_name function_args function_
       | OpMod -> "%"
       | OpInterval -> "..."
       | OpArrow -> "->"
+      | OpIn -> " in "
       | OpAssign | OpAssignOp _ -> abort "Unprocessed OpAssign" pos
    and string_of_path path =
       "::" ^ (join_class_path_remap path "::") ^ "_obj"
@@ -7035,6 +7036,7 @@ let cppia_op_info = function
 	| IaBinOp OpMod -> ("%", 120)
 	| IaBinOp OpInterval -> ("...", 121)
 	| IaBinOp OpArrow -> ("=>", 122)
+	| IaBinOp OpIn -> (" in ", 123)
 	| IaBinOp OpAssignOp OpAdd -> ("+=", 201)
 	| IaBinOp OpAssignOp OpMult -> ("*=", 202)
 	| IaBinOp OpAssignOp OpDiv -> ("/=", 203)
@@ -7051,6 +7053,7 @@ let cppia_op_info = function
 	| IaBinOp OpAssignOp OpShl -> ("<<=", 219)
 	| IaBinOp OpAssignOp OpMod -> ("%=", 220)
 
+	| IaBinOp OpAssignOp OpIn
 	| IaBinOp OpAssignOp OpInterval
 	| IaBinOp OpAssignOp OpAssign
 	| IaBinOp OpAssignOp OpEq

+ 1 - 1
src/generators/genhl.ml

@@ -2316,7 +2316,7 @@ and eval_expr ctx e =
 					free ctx r;
 					binop r r b;
 					r))
-		| OpInterval | OpArrow ->
+		| OpInterval | OpArrow | OpIn ->
 			assert false)
 	| TUnop (Not,_,v) ->
 		let tmp = alloc_tmp ctx HBool in

+ 1 - 1
src/generators/genpy.ml

@@ -1053,7 +1053,7 @@ module Printer = struct
 		| OpShr -> ">>"
 		| OpUShr -> ">>"
 		| OpMod -> "%"
-		| OpInterval | OpArrow | OpAssignOp _ -> assert false
+		| OpInterval | OpArrow | OpIn | OpAssignOp _ -> assert false
 
 	let print_string s =
 		Printf.sprintf "\"%s\"" (Ast.s_escape s)

+ 1 - 1
src/generators/genswf9.ml

@@ -1691,7 +1691,7 @@ and gen_binop ctx retval op e1 e2 t p =
 		gen_op A3OLt
 	| OpLte ->
 		gen_op A3OLte
-	| OpInterval | OpArrow ->
+	| OpInterval | OpArrow | OpIn ->
 		assert false
 
 and gen_expr ctx retval e =

+ 1 - 1
src/macro/eval/evalJit.ml

@@ -64,7 +64,7 @@ let get_binop_fun op p = match op with
 	| OpShr -> op_shr p
 	| OpUShr -> op_ushr p
 	| OpMod -> op_mod p
-	| OpAssign | OpBoolAnd | OpBoolOr | OpAssignOp _ | OpInterval | OpArrow -> assert false
+	| OpAssign | OpBoolAnd | OpBoolOr | OpAssignOp _ | OpInterval | OpArrow | OpIn -> assert false
 
 open EvalJitContext
 

+ 33 - 35
src/macro/macroApi.ml

@@ -331,6 +331,7 @@ let rec encode_binop op =
 	| OpAssignOp op -> 20, [encode_binop op]
 	| OpInterval -> 21, []
 	| OpArrow -> 22, []
+	| OpIn -> 23, []
 	in
 	encode_enum IBinop tag pl
 
@@ -496,14 +497,12 @@ and encode_expr e =
 				12, [encode_array (List.map loop el)]
 			| EFor (e,eloop) ->
 				13, [loop e;loop eloop]
-			| EIn (e1,e2) ->
-				14, [loop e1;loop e2]
 			| EIf (econd,e,eelse) ->
-				15, [loop econd;loop e;null loop eelse]
+				14, [loop econd;loop e;null loop eelse]
 			| EWhile (econd,e,flag) ->
-				16, [loop econd;loop e;vbool (match flag with NormalWhile -> true | DoWhile -> false)]
+				15, [loop econd;loop e;vbool (match flag with NormalWhile -> true | DoWhile -> false)]
 			| ESwitch (e,cases,eopt) ->
-				17, [loop e;encode_array (List.map (fun (ecl,eg,e,p) ->
+				16, [loop e;encode_array (List.map (fun (ecl,eg,e,p) ->
 					encode_obj OCase [
 						"values",encode_array (List.map loop ecl);
 						"guard",null loop eg;
@@ -512,7 +511,7 @@ and encode_expr e =
 					]
 				) cases);null (fun (e,_) -> encode_null_expr e) eopt]
 			| ETry (e,catches) ->
-				18, [loop e;encode_array (List.map (fun (v,t,e,p) ->
+				17, [loop e;encode_array (List.map (fun (v,t,e,p) ->
 					encode_obj OCatch [
 						"name",encode_placed_name v;
 						"name_pos",encode_pos (pos v);
@@ -522,27 +521,27 @@ and encode_expr e =
 					]
 				) catches)]
 			| EReturn eo ->
-				19, [null loop eo]
+				18, [null loop eo]
 			| EBreak ->
-				20, []
+				19, []
 			| EContinue ->
-				21, []
+				20, []
 			| EUntyped e ->
-				22, [loop e]
+				21, [loop e]
 			| EThrow e ->
-				23, [loop e]
+				22, [loop e]
 			| ECast (e,t) ->
-				24, [loop e; null encode_ctype t]
+				23, [loop e; null encode_ctype t]
 			| EDisplay (e,flag) ->
-				25, [loop e; vbool flag]
+				24, [loop e; vbool flag]
 			| EDisplayNew t ->
-				26, [encode_path t]
+				25, [encode_path t]
 			| ETernary (econd,e1,e2) ->
-				27, [loop econd;loop e1;loop e2]
+				26, [loop econd;loop e1;loop e2]
 			| ECheckType (e,t) ->
-				28, [loop e; encode_ctype t]
+				27, [loop e; encode_ctype t]
 			| EMeta (m,e) ->
-				29, [encode_meta_entry m;loop e]
+				28, [encode_meta_entry m;loop e]
 		in
 		encode_obj OExpr [
 			"pos", encode_pos p;
@@ -605,6 +604,7 @@ let rec decode_op op =
 	| 20, [op] -> OpAssignOp (decode_op op)
 	| 21, [] -> OpInterval
 	| 22,[] -> OpArrow
+	| 23,[] -> OpIn
 	| _ -> raise Invalid_expr
 
 let decode_unop op =
@@ -764,45 +764,43 @@ and decode_expr v =
 			EBlock (List.map loop (decode_array el))
 		| 13, [e1;e2] ->
 			EFor (loop e1, loop e2)
-		| 14, [e1;e2] ->
-			EIn (loop e1, loop e2)
-		| 15, [e1;e2;e3] ->
+		| 14, [e1;e2;e3] ->
 			EIf (loop e1, loop e2, opt loop e3)
-		| 16, [e1;e2;flag] ->
+		| 15, [e1;e2;flag] ->
 			EWhile (loop e1,loop e2,if decode_bool flag then NormalWhile else DoWhile)
-		| 17, [e;cases;eo] ->
+		| 16, [e;cases;eo] ->
 			let cases = List.map (fun c ->
 				(List.map loop (decode_array (field c "values")),opt loop (field c "guard"),opt loop (field c "expr"),maybe_decode_pos (field c "pos"))
 			) (decode_array cases) in
 			ESwitch (loop e,cases,opt (fun v -> (if field v "expr" = vnull then None else Some (decode_expr v)),Globals.null_pos) eo)
-		| 18, [e;catches] ->
+		| 17, [e;catches] ->
 			let catches = List.map (fun c ->
 				((decode_placed_name (field c "name_pos") (field c "name")),(decode_ctype (field c "type")),loop (field c "expr"),maybe_decode_pos (field c "pos"))
 			) (decode_array catches) in
 			ETry (loop e, catches)
-		| 19, [e] ->
+		| 18, [e] ->
 			EReturn (opt loop e)
-		| 20, [] ->
+		| 19, [] ->
 			EBreak
-		| 21, [] ->
+		| 20, [] ->
 			EContinue
-		| 22, [e] ->
+		| 21, [e] ->
 			EUntyped (loop e)
-		| 23, [e] ->
+		| 22, [e] ->
 			EThrow (loop e)
-		| 24, [e;t] ->
+		| 23, [e;t] ->
 			ECast (loop e,opt decode_ctype t)
-		| 25, [e;f] ->
+		| 24, [e;f] ->
 			EDisplay (loop e,decode_bool f)
-		| 26, [t] ->
+		| 25, [t] ->
 			EDisplayNew (decode_path t)
-		| 27, [e1;e2;e3] ->
+		| 26, [e1;e2;e3] ->
 			ETernary (loop e1,loop e2,loop e3)
-		| 28, [e;t] ->
+		| 27, [e;t] ->
 			ECheckType (loop e, (decode_ctype t))
-		| 29, [m;e] ->
+		| 28, [m;e] ->
 			EMeta (decode_meta_entry m,loop e)
-		| 30, [e;f] ->
+		| 29, [e;f] ->
 			EField (loop e, decode_string f) (*** deprecated EType, keep until haxe 3 **)
 		| _ ->
 			raise Invalid_expr

+ 1 - 0
src/optimization/analyzerTexpr.ml

@@ -620,6 +620,7 @@ module Fusion = struct
 		| OpBoolOr
 		| OpAssignOp _
 		| OpInterval
+		| OpIn
 		| OpArrow ->
 			false
 

+ 1 - 1
src/optimization/analyzerTexprTransformer.ml

@@ -731,7 +731,7 @@ and func ctx i =
 				| OpAdd | OpMult | OpDiv | OpSub | OpAnd
 				| OpOr | OpXor | OpShl | OpShr | OpUShr | OpMod ->
 					true
-				| OpAssignOp _ | OpInterval | OpArrow | OpAssign | OpEq
+				| OpAssignOp _ | OpInterval | OpArrow | OpIn | OpAssign | OpEq
 				| OpNotEq | OpGt | OpGte | OpLt | OpLte | OpBoolAnd | OpBoolOr ->
 					false
 			in

+ 6 - 5
src/optimization/optimizer.ml

@@ -914,8 +914,9 @@ let standard_precedence op =
 	| OpBoolAnd -> 14, left
 	| OpBoolOr -> 15, left
 	| OpArrow -> 16, left
-	| OpAssignOp OpAssign -> 17, right (* mimics ?: *)
-	| OpAssign | OpAssignOp _ -> 18, right
+	| OpIn -> 17, right
+	| OpAssignOp OpAssign -> 18, right (* mimics ?: *)
+	| OpAssign | OpAssignOp _ -> 19, right
 
 let rec need_parent e =
 	match e.eexpr with
@@ -1503,18 +1504,18 @@ let optimize_completion_expr e =
 			let e = map e in
 			old();
 			e
-		| EFor ((EIn ((EConst (Ident n),_) as id,it),p),efor) ->
+		| EFor ((EBinop (OpIn,((EConst (Ident n),_) as id),it),p),efor) ->
 			let it = loop it in
 			let old = save() in
 			let etmp = (EConst (Ident "$tmp"),p) in
 			decl n None (Some (EBlock [
 				(EVars [("$tmp",null_pos),None,None],p);
-				(EFor ((EIn (id,it),p),(EBinop (OpAssign,etmp,(EConst (Ident n),p)),p)),p);
+				(EFor ((EBinop (OpIn,id,it),p),(EBinop (OpAssign,etmp,(EConst (Ident n),p)),p)),p);
 				etmp
 			],p));
 			let efor = loop efor in
 			old();
-			(EFor ((EIn (id,it),p),efor),p)
+			(EFor ((EBinop (OpIn,id,it),p),efor),p)
 		| EReturn _ ->
 			typing_side_effect := true;
 			map e

+ 3 - 7
src/syntax/ast.ml

@@ -87,6 +87,7 @@ type binop =
 	| OpAssignOp of binop
 	| OpInterval
 	| OpArrow
+	| OpIn
 
 type unop =
 	| Increment
@@ -182,7 +183,6 @@ and expr_def =
 	| EFunction of string option * func
 	| EBlock of expr list
 	| EFor of expr * expr
-	| EIn of expr * expr
 	| EIf of expr * expr * expr option
 	| EWhile of expr * expr * while_flag
 	| ESwitch of expr * (expr list * expr option * expr option * pos) list * (expr option * pos) option
@@ -431,6 +431,7 @@ let rec s_binop = function
 	| OpAssignOp op -> s_binop op ^ "="
 	| OpInterval -> "..."
 	| OpArrow -> "=>"
+	| OpIn -> " in "
 
 let s_unop = function
 	| Increment -> "++"
@@ -607,10 +608,6 @@ let map_expr loop (e,p) =
 		let e1 = loop e1 in
 		let e2 = loop e2 in
 		EFor (e1,e2)
-	| EIn (e1,e2) ->
-		let e1 = loop e1 in
-		let e2 = loop e2 in
-		EIn (e1,e2)
 	| EIf (e,e1,e2) ->
 		let e = loop e in
 		let e1 = loop e1 in
@@ -665,7 +662,7 @@ let iter_expr loop (e,p) =
 	| EConst _ | EContinue | EBreak | EDisplayNew _ | EReturn None -> ()
 	| EParenthesis e1 | EField(e1,_) | EUnop(_,_,e1) | EReturn(Some e1) | EThrow e1 | EMeta(_,e1)
 	| ECheckType(e1,_) | EDisplay(e1,_) | ECast(e1,_) | EUntyped e1 -> loop e1;
-	| EArray(e1,e2) | EBinop(_,e1,e2) | EIn(e1,e2) | EFor(e1,e2) | EWhile(e1,e2,_) | EIf(e1,e2,None) -> loop e1; loop e2;
+	| EArray(e1,e2) | EBinop(_,e1,e2) | EFor(e1,e2) | EWhile(e1,e2,_) | EIf(e1,e2,None) -> loop e1; loop e2;
 	| ETernary(e1,e2,e3) | EIf(e1,e2,Some e3) -> loop e1; loop e2; loop e3;
 	| EArrayDecl el | ENew(_,el) | EBlock el -> List.iter loop el
 	| ECall(e1,el) -> loop e1; exprs el;
@@ -706,7 +703,6 @@ let s_expr e =
 		| EBlock [] -> "{ }"
 		| EBlock el -> s_block tabs el "{" "\n" "}"
 		| EFor (e1,e2) -> "for (" ^ s_expr_inner tabs e1 ^ ") " ^ s_expr_inner tabs e2
-		| EIn (e1,e2) -> s_expr_inner tabs e1 ^ " in " ^ s_expr_inner tabs e2
 		| EIf (e,e1,None) -> "if (" ^ s_expr_inner tabs e ^ ") " ^ s_expr_inner tabs e1
 		| EIf (e,e1,Some e2) -> "if (" ^ s_expr_inner tabs e ^ ") " ^ s_expr_inner tabs e1 ^ " else " ^ s_expr_inner tabs e2
 		| EWhile (econd,e,NormalWhile) -> "while (" ^ s_expr_inner tabs econd ^ ") " ^ s_expr_inner tabs e

+ 4 - 4
src/syntax/parser.mly

@@ -120,7 +120,8 @@ let precedence op =
 	| OpBoolAnd -> 7, left
 	| OpBoolOr -> 8, left
 	| OpArrow -> 9, right
-	| OpAssign | OpAssignOp _ -> 10, right
+	| OpIn -> 10, right
+	| OpAssign | OpAssignOp _ -> 11, right
 
 let is_not_assign = function
 	| OpAssign | OpAssignOp _ -> false
@@ -210,6 +211,7 @@ let reify in_macro =
 		| OpAssignOp o -> mk_enum "Binop" "OpAssignOp" [to_binop o p] p
 		| OpInterval -> op "OpInterval"
 		| OpArrow -> op "OpArrow"
+		| OpIn -> op "OpIn"
 	in
 	let to_string s p =
 		let len = String.length s in
@@ -432,8 +434,6 @@ let reify in_macro =
 			expr "EBlock" [to_expr_array el p]
 		| EFor (e1,e2) ->
 			expr "EFor" [loop e1;loop e2]
-		| EIn (e1,e2) ->
-			expr "EIn" [loop e1;loop e2]
 		| EIf (e1,e2,eelse) ->
 			expr "EIf" [loop e1;loop e2;to_opt to_expr eelse p]
 		| EWhile (e1,e2,flag) ->
@@ -1515,7 +1515,7 @@ and expr_next e1 = parser
 	| [< '(Question,_); e2 = expr; '(DblDot,_); e3 = expr >] ->
 		(ETernary (e1,e2,e3),punion (pos e1) (pos e3))
 	| [< '(Kwd In,_); e2 = expr >] ->
-		(EIn (e1,e2), punion (pos e1) (pos e2))
+		make_binop OpIn e1 e2
 	| [< >] -> e1
 
 and parse_guard = parser

+ 1 - 1
src/typing/type.ml

@@ -2540,7 +2540,7 @@ module TExprToExpr = struct
 			EVars ([(v.v_name,v.v_pos), mk_type_hint v.v_type v.v_pos, eopt eo])
 		| TBlock el -> EBlock (List.map convert_expr el)
 		| TFor (v,it,e) ->
-			let ein = (EIn ((EConst (Ident v.v_name),it.epos),convert_expr it),it.epos) in
+			let ein = (EBinop (OpIn,(EConst (Ident v.v_name),it.epos),convert_expr it),it.epos) in
 			EFor (ein,convert_expr e)
 		| TIf (e,e1,e2) -> EIf (convert_expr e,convert_expr e1,eopt e2)
 		| TWhile (e1,e2,flag) -> EWhile (convert_expr e1, convert_expr e2, flag)

+ 3 - 3
src/typing/typer.ml

@@ -2166,6 +2166,8 @@ and type_binop2 ctx op (e1 : texpr) (e2 : Ast.expr) is_assign_op wt p =
 		mk (TNew ((match t with TInst (c,[]) -> c | _ -> assert false),[],[e1;e2])) t p
 	| OpArrow ->
 		error "Unexpected =>" p
+	| OpIn ->
+		error "Unexpected in" p
 	| OpAssign
 	| OpAssignOp _ ->
 		assert false
@@ -3381,7 +3383,7 @@ and type_expr ctx (e,p) (with_type:with_type) =
 			| _ -> error "Identifier expected" (pos e1)
 		in
 		let rec loop display e1 = match fst e1 with
-			| EIn(e1,e2) -> loop_ident display e1,e2
+			| EBinop(OpIn,e1,e2) -> loop_ident display e1,e2
 			| EDisplay(e1,_) -> loop true e1
 			| _ -> error "For expression should be 'v in expr'" (snd it)
 		in
@@ -3428,8 +3430,6 @@ and type_expr ctx (e,p) (with_type:with_type) =
 		ctx.in_loop <- old_loop;
 		old_locals();
 		e
-	| EIn _ ->
-		error "This expression is not allowed outside a for loop" p
 	| ETernary (e1,e2,e3) ->
 		type_expr ctx (EIf (e1,e2,Some e3),p) with_type
 	| EIf (e,e1,e2) ->

+ 5 - 5
std/haxe/macro/Expr.hx

@@ -212,6 +212,11 @@ enum Binop {
 		`=>`
 	**/
 	OpArrow;
+
+	/**
+		`in`
+	**/
+	OpIn;
 }
 
 /**
@@ -413,11 +418,6 @@ enum ExprDef {
 	**/
 	EFor( it : Expr, expr : Expr );
 
-	/**
-		A `(e1 in e2)` expression.
-	**/
-	EIn( e1 : Expr, e2 : Expr );
-
 	/**
 		An `if(econd) eif` or `if(econd) eif else eelse` expression.
 	**/

+ 1 - 3
std/haxe/macro/ExprTools.hx

@@ -87,8 +87,7 @@ class ExprTools {
 			case EArray(e1, e2),
 				EWhile(e1, e2, _),
 				EBinop(_, e1, e2),
-				EFor(e1, e2),
-				EIn(e1, e2):
+				EFor(e1, e2):
 					f(e1);
 					f(e2);
 			case EVars(vl):
@@ -178,7 +177,6 @@ class ExprTools {
 				EVars(ret);
 			case EBlock(el): EBlock(ExprArrayTools.map(el, f));
 			case EFor(it, expr): EFor(f(it), f(expr));
-			case EIn(e1, e2): EIn(f(e1), f(e2));
 			case EIf(econd, eif, eelse): EIf(f(econd), f(eif), opt(eelse, f));
 			case EWhile(econd, e, normalWhile): EWhile(f(econd), f(e), normalWhile);
 			case EReturn(e): EReturn(opt(e,f));

+ 1 - 1
std/haxe/macro/Printer.hx

@@ -70,6 +70,7 @@ class Printer {
 		case OpMod: "%";
 		case OpInterval: "...";
 		case OpArrow: "=>";
+		case OpIn: " in ";
 		case OpAssignOp(op):
 			printBinop(op)
 			+ "=";
@@ -196,7 +197,6 @@ class Printer {
 			tabs = old;
 			s + ';\n$tabs}';
 		case EFor(e1, e2): 'for (${printExpr(e1)}) ${printExpr(e2)}';
-		case EIn(e1, e2): '${printExpr(e1)} in ${printExpr(e2)}';
 		case EIf(econd, eif, null): 'if (${printExpr(econd)}) ${printExpr(eif)}';
 		case EIf(econd, eif, eelse): 'if (${printExpr(econd)}) ${printExpr(eif)} else ${printExpr(eelse)}';
 		case EWhile(econd, e1, true): 'while (${printExpr(econd)}) ${printExpr(e1)}';

+ 1 - 1
tests/optimization/src/TestAnalyzer.hx

@@ -738,7 +738,7 @@ class TestAnalyzer extends TestBase {
 		assertEqualsConst("A", fromCharCode);
 
 		var enumIndex = Type.enumIndex(eBreak);
-		assertEqualsConst(20, enumIndex);
+		assertEqualsConst(19, enumIndex);
 	}
 
 	function testWhilePrune1() {

+ 1 - 1
tests/unit/src/unit/TestMatch.hx

@@ -46,7 +46,7 @@ class TestMatch extends Test {
 				s;
 			case EArray(_, { expr : EConst(CInt(i) | CFloat(i)) } ):
 				Std.string(i);
-			case EIn(_, { expr : e, pos : _ }) :
+			case EBinop(OpIn, _, { expr : e, pos : _ }) :
 				Std.string(e);
 			case _:
 				"not_found";

+ 1 - 1
tests/unit/src/unit/UnitBuilder.hx

@@ -147,7 +147,7 @@ class UnitBuilder {
 						}
 					case EThrow(e):
 						macro exc(function() $e);
-					case EIn(e1, {expr:EArrayDecl(el) }):
+					case EBinop(OpIn, e1, {expr:EArrayDecl(el) }):
 						var el2 = [];
 						for (e in el)
 							el2.push(macro $e1 == $e);

+ 13 - 0
tests/unit/src/unit/issues/Issue6224.hx

@@ -0,0 +1,13 @@
+package unit.issues;
+
+private abstract S(String) from String {
+	inline function asString() return this;
+	@:op(a in b) inline static function contains(a:S, b:S) return b.asString().indexOf(a.asString()) != -1;
+}
+
+class Issue6224 extends unit.Test {
+	function test() {
+		var s:S = "hello";
+		t("hell" in s);
+	}
+}