From 15a05360a42615bf719a7c53a775b6f9d278e3dd Mon Sep 17 00:00:00 2001 From: samuraiGH Date: Fri, 28 Jun 2024 17:25:39 +0300 Subject: [PATCH] attempt to fix GetEnumerator resolving for compiled types --- Compiler/PCU/PCUReader.cs | 7 +- Compiler/PCU/PCUWriter.cs | 3 + NETGenerator/Helpers.cs | 52 ---- NETGenerator/NETGenerator.cs | 54 +++-- SemanticTree/SemanticTree.cs | 10 + .../Closure/CapturedVariablesTreeBuilder.cs | 6 +- .../SyntaxTreeVisitorNodes/Foreach.cs | 25 +- .../TreeConversion/syntax_tree_visitor.cs | 224 ++++++++++-------- TreeConverter/TreeRealization/statements.cs | 36 ++- 9 files changed, 234 insertions(+), 183 deletions(-) diff --git a/Compiler/PCU/PCUReader.cs b/Compiler/PCU/PCUReader.cs index baf3a0279..24e5db0dc 100644 --- a/Compiler/PCU/PCUReader.cs +++ b/Compiler/PCU/PCUReader.cs @@ -3235,7 +3235,12 @@ namespace PascalABCCompiler.PCU var_definition_node vdn = GetLocalOrNamespaceVariableByOffset(br.ReadInt32()); expression_node expr = CreateExpression(); statement_node body = CreateStatement(); - return new foreach_node(vdn, expr, body, null); + type_node elementType = null; + if (CanReadObject()) + elementType = GetTypeReference(); + + bool isGeneric = br.ReadBoolean(); + return new foreach_node(vdn, expr, body, elementType, isGeneric, null); } private statement_node CreateLock() diff --git a/Compiler/PCU/PCUWriter.cs b/Compiler/PCU/PCUWriter.cs index c85cdaf1f..7fa271ff6 100644 --- a/Compiler/PCU/PCUWriter.cs +++ b/Compiler/PCU/PCUWriter.cs @@ -3555,6 +3555,9 @@ namespace PascalABCCompiler.PCU bw.Write(GetMemberOffset(fn.ident)); VisitExpression(fn.in_what); VisitStatement(fn.what_do); + if (CanWriteObject(fn.element_type)) + WriteTypeReference(fn.element_type); + bw.Write(fn.is_generic); } private void VisitLock(lock_statement node) diff --git a/NETGenerator/Helpers.cs b/NETGenerator/Helpers.cs index f47f9ed49..411f80c17 100644 --- a/NETGenerator/Helpers.cs +++ b/NETGenerator/Helpers.cs @@ -866,58 +866,6 @@ namespace PascalABCCompiler.NETGenerator { return null; } - public MethodInfo GetEnumeratorMethod(Type t, out Type[] generic_args) - { - generic_args = null; - Type generic_def = null; - if (t.IsGenericType && !t.IsGenericTypeDefinition) - generic_def = t.GetGenericTypeDefinition(); - else - generic_def = t; - if (generic_def.IsArray && generic_def.GetElementType().IsGenericParameter) - { - return TypeFactory.IEnumerableGenericType.GetMethod("GetEnumerator"); - } - if (generic_def.IsArray) - { - if (IsConstructedGenericType(generic_def.GetElementType())) - return TypeBuilder.GetMethod(TypeFactory.IEnumerableGenericType.MakeGenericType(generic_def.GetElementType()), TypeFactory.IEnumerableGenericType.GetMethod("GetEnumerator")); - else - return TypeFactory.IEnumerableGenericType.MakeGenericType(generic_def.GetElementType()).GetMethod("GetEnumerator"); - } - else if (generic_def.IsGenericParameter) - { - return TypeFactory.IEnumerableType.GetMethod("GetEnumerator", Type.EmptyTypes); - } - foreach (Type interf in generic_def.GetInterfaces()) - { - if (interf.IsGenericType && interf.GetGenericTypeDefinition() == TypeFactory.IEnumerableGenericType) - { - MethodInfo mi = interf.GetGenericTypeDefinition().GetMethod("GetEnumerator"); - if (generic_def != t) - { - if (t.GetGenericArguments().Length != interf.GetGenericTypeDefinition().GetGenericArguments().Length) - return null; - Type gt = interf.GetGenericTypeDefinition().MakeGenericType(t.GetGenericArguments()); - if (IsConstructedGenericType(gt)) - return TypeBuilder.GetMethod(gt, mi); - else - return interf.GetGenericTypeDefinition().MakeGenericType(t.GetGenericArguments()).GetMethod("GetEnumerator"); - } - else if (IsConstructedGenericType(interf)) - { - //return TypeBuilder.GetMethod(TypeFactory.IEnumerableGenericType.MakeGenericType(interf.GetGenericArguments()), TypeFactory.IEnumerableGenericType.GetMethod("GetEnumerator")); - //return TypeFactory.IEnumerableType.GetMethod("GetEnumerator", Type.EmptyTypes); - generic_args = interf.GetGenericArguments(); - return TypeBuilder.GetMethod(interf, mi); - } - else - return interf.GetMethod("GetEnumerator"); - } - } - return TypeFactory.IEnumerableType.GetMethod("GetEnumerator", Type.EmptyTypes); - } - public void SetAsProcessing(ICommonTypeNode type) { processing_types[type] = true; diff --git a/NETGenerator/NETGenerator.cs b/NETGenerator/NETGenerator.cs index 2cac39046..01c858026 100644 --- a/NETGenerator/NETGenerator.cs +++ b/NETGenerator/NETGenerator.cs @@ -11599,37 +11599,41 @@ namespace PascalABCCompiler.NETGenerator Type var_tp = helper.GetTypeReference(value.VarIdent.type).tp; //(ssyy) 12.04.2008 Поиск IEnumerable не нужен! Это дело семантики! Type in_what_type = helper.GetTypeReference(value.InWhatExpr.type).tp; - Type return_type = null; - bool is_generic = false; - Type[] generic_args = null; - MethodInfo enumer_mi = null; //typeof(System.Collections.IEnumerable).GetMethod("GetEnumerator", Type.EmptyTypes); - if (/*var_tp.IsValueType &&*/ !var_tp.IsGenericParameter && !(in_what_type.IsArray && in_what_type.GetArrayRank() > 1)) + Type return_type; + //bool is_generic = false; + //Type[] generic_args = null; + MethodInfo enumer_mi; + + Type elementType = helper.GetTypeReference(value.ElementType).tp; + + bool is_generic = value.IsGeneric; + + if (is_generic) { - enumer_mi = helper.GetEnumeratorMethod(in_what_type, out generic_args); - if (enumer_mi == null) + if (value.ElementType.node_kind == node_kind.compiled) { - enumer_mi = typeof(System.Collections.IEnumerable).GetMethod("GetEnumerator", Type.EmptyTypes); - return_type = enumer_mi.ReturnType; - } + enumer_mi = TypeFactory.IEnumerableGenericType.MakeGenericType(elementType).GetMethod("GetEnumerator"); + return_type = enumer_mi.ReturnType; + } else { - is_generic = enumer_mi.ReturnType.IsGenericType; - return_type = enumer_mi.ReturnType; - if (in_what_type.IsGenericType && return_type.IsGenericType && !return_type.IsGenericTypeDefinition) - return_type = return_type.GetGenericTypeDefinition().MakeGenericType(in_what_type.GetGenericArguments()); - else if (in_what_type.IsArray && return_type.IsGenericType && !return_type.IsGenericTypeDefinition) - return_type = return_type.GetGenericTypeDefinition().MakeGenericType(in_what_type.GetElementType()); - else if (generic_args != null) - return_type = return_type.GetGenericTypeDefinition().MakeGenericType(generic_args); - } - - } + enumer_mi = TypeBuilder.GetMethod( + TypeFactory.IEnumerableGenericType.MakeGenericType(elementType), + TypeFactory.IEnumerableGenericType.GetMethod("GetEnumerator") + ); + + return_type = enumer_mi.ReturnType + .GetGenericTypeDefinition() + .MakeGenericType(elementType); + } + } else { - enumer_mi = typeof(System.Collections.IEnumerable).GetMethod("GetEnumerator", Type.EmptyTypes); - return_type = enumer_mi.ReturnType; - } - LocalBuilder lb = il.DeclareLocal(return_type); + enumer_mi = TypeFactory.IEnumerableType.GetMethod("GetEnumerator"); + return_type = enumer_mi.ReturnType; + } + + LocalBuilder lb = il.DeclareLocal(return_type); if (save_debug_info) lb.SetLocalSymInfo("$enumer$" + uid++); value.InWhatExpr.visit(this); diff --git a/SemanticTree/SemanticTree.cs b/SemanticTree/SemanticTree.cs index 29dcd0b91..5095460d1 100644 --- a/SemanticTree/SemanticTree.cs +++ b/SemanticTree/SemanticTree.cs @@ -2350,6 +2350,16 @@ namespace PascalABCCompiler.SemanticTree { get; } + + ITypeNode ElementType + { + get; + } + + bool IsGeneric + { + get; + } } public interface ILockStatement : IStatementNode, ILocated diff --git a/TreeConverter/LambdaExpressions/Closure/CapturedVariablesTreeBuilder.cs b/TreeConverter/LambdaExpressions/Closure/CapturedVariablesTreeBuilder.cs index 545daadf7..b4ed54b74 100644 --- a/TreeConverter/LambdaExpressions/Closure/CapturedVariablesTreeBuilder.cs +++ b/TreeConverter/LambdaExpressions/Closure/CapturedVariablesTreeBuilder.cs @@ -641,8 +641,8 @@ namespace TreeConverter.LambdaExpressions.Closure type_node elemType = null; if (inWhat.type == null) inWhat = tmp; - bool bb; // здесь bb не нужно. Оно нужно в foreach - _visitor.FindIEnumerableElementType(/*_foreach_stmt, */inWhat.type, ref elemType, out bb); + bool sys_coll_ienum; + _visitor.FindIEnumerableElementType(/*_foreach_stmt, */inWhat.type, ref elemType, out sys_coll_ienum); if (_foreach_stmt.type_name == null) { @@ -676,7 +676,7 @@ namespace TreeConverter.LambdaExpressions.Closure if (!(vdn.type is compiled_generic_instance_type_node)) _visitor.convertion_data_and_alghoritms.check_convert_type_with_inheritance(vdn.type, elemType, _visitor.get_location(_foreach_stmt.identifier)); - var fn = new foreach_node(vdn, inWhat, null, _visitor.get_location(_foreach_stmt)); + var fn = new foreach_node(vdn, inWhat, null, elemType, !sys_coll_ienum, _visitor.get_location(_foreach_stmt)); _visitor.context.enter_in_cycle(fn); _visitor.context.loop_var_stack.Push(vdn); diff --git a/TreeConverter/TreeConversion/SyntaxTreeVisitorNodes/Foreach.cs b/TreeConverter/TreeConversion/SyntaxTreeVisitorNodes/Foreach.cs index 0a30a594d..a8f25cd54 100644 --- a/TreeConverter/TreeConversion/SyntaxTreeVisitorNodes/Foreach.cs +++ b/TreeConverter/TreeConversion/SyntaxTreeVisitorNodes/Foreach.cs @@ -36,7 +36,9 @@ namespace PascalABCCompiler.TreeConverter expression_node foreachCollection; var_definition_node foreachVariable; - ForeachCheckAndConvert(_foreach_stmt, out foreachCollection, out foreachVariable); + type_node elementType; + bool isGeneric; + ForeachCheckAndConvert(_foreach_stmt, out foreachCollection, out foreachVariable, out elementType, out isGeneric); definition_node dnind = null; var_definition_node vdn = null; @@ -54,7 +56,14 @@ namespace PascalABCCompiler.TreeConverter statements_list sl = new statements_list(get_location(_foreach_stmt.stmt)); convertion_data_and_alghoritms.statement_list_stack_push(sl); - foreach_node foreachNode = new foreach_node(foreachVariable, foreachCollection, null, get_location(_foreach_stmt)); + foreach_node foreachNode = new foreach_node( + foreachVariable, + foreachCollection, + null, + elementType, + isGeneric, + get_location(_foreach_stmt) + ); context.enter_in_cycle(foreachNode); context.loop_var_stack.Push(foreachVariable); @@ -115,8 +124,13 @@ namespace PascalABCCompiler.TreeConverter } - private void ForeachCheckAndConvert(foreach_stmt _foreach_stmt, out expression_node foreachCollection, - out var_definition_node foreachVariable) + private void ForeachCheckAndConvert( + foreach_stmt _foreach_stmt, + out expression_node foreachCollection, + out var_definition_node foreachVariable, + out type_node elementType, + out bool isGenereic + ) { var lambdaSearcher = new LambdaSearcher(_foreach_stmt.in_what); if (lambdaSearcher.CheckIfContainsLambdas()) @@ -135,6 +149,9 @@ namespace PascalABCCompiler.TreeConverter if (!FindIEnumerableElementType(foreachCollection.type, ref elem_type, out sys_coll_ienum)) AddError(foreachCollection.location, "CAN_NOT_EXECUTE_FOREACH_BY_EXPR_OF_TYPE_{0}", foreachCollection.type.name); + elementType = elem_type; + isGenereic = !sys_coll_ienum; + var vars = _foreach_stmt.ext as ident_list; if (vars != null) { diff --git a/TreeConverter/TreeConversion/syntax_tree_visitor.cs b/TreeConverter/TreeConversion/syntax_tree_visitor.cs index e0d4d0612..2744eb567 100644 --- a/TreeConverter/TreeConversion/syntax_tree_visitor.cs +++ b/TreeConverter/TreeConversion/syntax_tree_visitor.cs @@ -25,6 +25,8 @@ using while_node = PascalABCCompiler.TreeRealization.while_node; using TreeConverter.LambdaExpressions.Closure; using TreeConverter.LambdaExpressions; using PascalABCCompiler.TreeConverter.TreeConversion; +using System.Reflection; +using PascalABCCompiler.SemanticTree; namespace PascalABCCompiler.TreeConverter { @@ -13723,7 +13725,7 @@ namespace PascalABCCompiler.TreeConverter cmc.parameters.AddElement(new int_const_node((prm.type.element_type.element_type as short_string_type_node).Length,null)); } what_do.statements.AddElement(cmc); - foreach_node fn = new foreach_node(var,in_what,what_do,null); + foreach_node fn = new foreach_node(var, in_what,what_do, prm.type.element_type, true, null); sl.statements.AddElementFirst(fn); } } @@ -18481,11 +18483,87 @@ namespace PascalABCCompiler.TreeConverter return false; } - public bool FindIEnumerableElementType(type_node tn, ref type_node elem_type, out bool sys_coll_ienum) + private bool IsIEnumInterface(Type t) + { + var IEnumType = typeof(IEnumerable); + var IEnumTypedType = typeof(IEnumerable<>); + + var isIEnumType = (t == IEnumType); + var isIEnumTypedType = t.IsGenericType + && (t.GetGenericTypeDefinition() == IEnumTypedType + ); + + return isIEnumType || isIEnumTypedType; + } + + public Type FindIEnumerableInterfaceInCompiledType(Type compiledType) + { + var IEnumType = typeof(IEnumerable); + var IEnumTypedType = typeof(IEnumerable<>); + + // если тип является интерфейсом IEnumerable или IEnumerable то берём его + if (IsIEnumInterface(compiledType)) + return compiledType; + + // иначе ищем подходящие интерфейсы в иерархии + var filterdInterfaces = compiledType.FindInterfaces( + (item, _) => IsIEnumInterface(item), + null + ); + + var genericInterfaceCount = filterdInterfaces.Count(item => item.IsGenericType); + + if (compiledType.IsInterface) + { + // интерфейс наследует несколько IEnumerable с разными T + // невозможно выбрать. требуется явное приведение + if (genericInterfaceCount > 1) + return null; + + if (genericInterfaceCount == 1) + return filterdInterfaces.First(item => item.IsGenericType); + else + return IEnumType; + } + + // для класса/записи необходимо учитывать явные реализации GetEnumerator + var methods = compiledType.GetMethods().Where(item => item.Name == "GetEnumerator").ToArray(); + + switch (methods.Length) + { + case 0: + // тип реализует явно несколько IEnumerable + // невозможно выбрать. требуется явное приведение + if (genericInterfaceCount > 1) + return null; + // тип реализует и IEnumerable и IEnumerable явно + // выбираем IEnumerable + if (genericInterfaceCount == 1) + return filterdInterfaces.First(item => item.IsGenericType); + // явно реализует IEnumerable + else + return IEnumType; + case 1: + var method = methods[0]; + + if (!method.ReturnType.IsGenericType) + return IEnumType; + else + { + var elementType = method.ReturnType.GetGenericArguments().First(); + return IEnumTypedType.MakeGenericType(elementType); + } + default: + // в типе наследнике перекрыли GetEnumerator + // невозможно точно выбрать метод + return null; + } + } + + public bool FindIEnumerableElementType(type_node tn, ref type_node elem_type, out bool sys_coll_ienum) { sys_coll_ienum = false; - var IEnstring = "System.Collections.IEnumerable"; - compiled_type_node ctn = compiled_type_node.get_type_node(NetHelper.NetHelper.FindType(IEnstring)); + if (tn is compiled_type_node || tn is compiled_generic_instance_type_node) // Если этот тип зашит в .NET // IEnumerable, Range(1,10), Dictionary: tn = compiled_type_node // IEnumerable: tn = compiled_generic_instance_type_node @@ -18496,110 +18574,62 @@ namespace PascalABCCompiler.TreeConverter if (tn is compiled_type_node) orig = tn as compiled_type_node; - else orig = (tn as compiled_generic_instance_type_node).original_generic as compiled_type_node; - //var pars = tn.instance_params; - System.Type ct = orig.compiled_type; - - Type r; - var IEnTstring = "System.Collections.Generic.IEnumerable`1"; - //if (ct.ToString().StartsWith(IEnTstring)) // SSM Ошибка!!! IEnumerable`1[] тоже начинается с IEnumerable`1, но это массив! - if (ct.Name.Equals("IEnumerable`1")) - r = ct; - else - r = ct.GetInterface(IEnTstring); - if (r != null) + else + orig = (tn as compiled_generic_instance_type_node).original_generic as compiled_type_node; + + // непосредственно тип, от котоого разворачивается foreach + Type ct = orig.compiled_type; + + var isEnumeratedType = typeof(IEnumerable).IsAssignableFrom(ct); + + if (!isEnumeratedType) + return false; + + // для массива + if (tn.element_type != null) { - Type arg1 = r.GetGenericArguments().First(); // тип параметра IEnumerable - var str = arg1.GetGenericArguments().Count(); - if (tn is compiled_type_node) - { - elem_type = compiled_type_node.get_type_node(arg1); - } - else - { - if (arg1.GetGenericArguments().Count()>0) - { - elem_type = compiled_type_node.get_type_node(arg1.GetGenericTypeDefinition()); - elem_type = elem_type.get_instance(tn.instance_params); // SSM 19/07/15 - работает!!! - } - else - { - var ip = tn.instance_params; + elem_type = tn.element_type; - var IGrTstring = "System.Linq.IGrouping`2"; - if (ct.ToString().StartsWith(IGrTstring)) - elem_type = ip[1]; - else - { - var ln = tn.ImplementingInterfaces; - elem_type = null; - foreach (var x in ln) - { - var xctn = x as compiled_generic_instance_type_node; - if (xctn != null && xctn.name.StartsWith("IEnumerable<")) // Немного грубовато. - { - elem_type = xctn.instance_params[0]; - break; - } - } - if (ct.Name == "ValueCollection" && ct.IsNested && ct.GetGenericArguments().Length == 2) - { - elem_type = ip[1]; - } - else if (elem_type == null) - elem_type = ip[0]; - } - - //var Tname = ip[0].name; - //elem_type = convert_strong(new SyntaxTree.named_type_reference(Tname, _foreach_stmt.in_what.source_context)); - } + if (ct.GetArrayRank() > 1) + sys_coll_ienum = true; - //elem_type.instance_params = tn.instance_params; - //var ip = tn.instance_params; - //var Tname = new string(tn.name.SkipWhile(c => c != '<').Skip(1).TakeWhile(c => c != ',' && c != '>').ToArray()); - //var Tname = ip[0].name; - //var Tname = "System.Collections.Generic.KeyValuePair'2"; // - //elem_type = convert_strong(new SyntaxTree.named_type_reference(Tname, _foreach_stmt.in_what.source_context)); - } - return true; + return true; + } + + // в иных случаях ищем подходящий интерфейс + var desiredInterface = FindIEnumerableInterfaceInCompiledType(ct); + + if (desiredInterface == null) + return false; + + // обнаружен IEnumerable + if (!desiredInterface.IsGenericType) + { + elem_type = SystemLibrary.SystemLibrary.object_type; + sys_coll_ienum = true; + return true; } + + var foundElementType = desiredInterface.GetGenericArguments().First(); + + if (tn is compiled_type_node) + elem_type = compiled_type_node.get_type_node(foundElementType); else { - if (tn.element_type != null) // значит, это массив любой размерности - 02.02.16 SSM - еще может быть множество set of T - 22.02.16 SSM - { - elem_type = tn.element_type; - return true; - } - - var ttt = tn.ImplementingInterfaces; - foreach (SemanticTree.ITypeNode itn in tn.ImplementingInterfaces) - { - if (itn == ctn) - { - elem_type = SystemLibrary.SystemLibrary.object_type; - sys_coll_ienum = true; - return true; - } - } + // для "tn is compiled_generic_instance_type_node" необходимо восстановить стёртые типы + var ind = orig.instance_params.FindIndex(item => item.name == foundElementType.Name); + elem_type = tn.instance_params[ind]; } + + return true; } - /*else if (tn is compiled_generic_instance_type_node) - { - var g = tn as compiled_generic_instance_type_node; - var og = g.original_generic; - var tt = og.ImplementingInterfaces; - if (_foreach_stmt.type_name != null && _foreach_stmt.type_name.GetType()!=typeof(SyntaxTree.no_type_foreach)) - elem_type = convert_strong(_foreach_stmt.type_name); - else - { - var fn = tn.full_name; - } - return true; - } */ else // если мы самостоятельно определяем этот тип - можно реализовать в PascalABC.NET только IEnumerable. // Сейчас уже можно!!!!! // Попытка реализовать IEnumerable натыкается на необходимость определять GetEnumerator, возвращающий IEnumerator и IEnumerator { - if (tn == null || tn is null_type_node || tn.ImplementingInterfaces == null) + var IEnstring = "System.Collections.IEnumerable"; + compiled_type_node ctn = compiled_type_node.get_type_node(NetHelper.NetHelper.FindType(IEnstring)); + + if (tn == null || tn is null_type_node || tn.ImplementingInterfaces == null) { if (tn != null && tn.base_type != null) return FindIEnumerableElementType(tn.base_type, ref elem_type, out sys_coll_ienum); diff --git a/TreeConverter/TreeRealization/statements.cs b/TreeConverter/TreeRealization/statements.cs index 4c57148b3..e4666a416 100644 --- a/TreeConverter/TreeRealization/statements.cs +++ b/TreeConverter/TreeRealization/statements.cs @@ -1754,12 +1754,23 @@ namespace PascalABCCompiler.TreeRealization private var_definition_node _ident; private expression_node _in_what; private statement_node _what_do; + private type_node _element_type; + private bool _is_generic; - public foreach_node(var_definition_node _ident, expression_node _in_what, statement_node _what_do, location loc):base(loc) + public foreach_node( + var_definition_node _ident, + expression_node _in_what, + statement_node _what_do, + type_node _element_type, + bool _is_generic, + location loc + ):base(loc) { this._ident = _ident; this._in_what = _in_what; this._what_do = _what_do; + this._element_type = _element_type; + this._is_generic = _is_generic; } public var_definition_node ident @@ -1798,6 +1809,18 @@ namespace PascalABCCompiler.TreeRealization } } + public type_node element_type + { + get { return _element_type; } + set { _element_type = value; } + } + + public bool is_generic + { + get { return _is_generic; } + set { _is_generic = value; } + } + public SemanticTree.IStatementNode Body { get @@ -1821,6 +1844,17 @@ namespace PascalABCCompiler.TreeRealization return _ident; } } + + public SemanticTree.ITypeNode ElementType + { + get { return _element_type; } + } + + public bool IsGeneric + { + get { return _is_generic; } + } + /// /// Тип узла. ///