Lowering для foreach: различия между версиями
Mikst (обсуждение | вклад) (Новая страница: «Такой код <source lang="Delphi"> foreach var x in l do Print(x); </source> заменяется на <source lang="Delphi"> var a: IEnumerable<auto_…») |
Mikst (обсуждение | вклад) |
||
(не показано 7 промежуточных версий этого же участника) | |||
Строка 9: | Строка 9: | ||
<source lang="Delphi"> | <source lang="Delphi"> | ||
var a: IEnumerable< | var a: IEnumerable<yield_unknown_foreach_type> := l; | ||
var en := a.GetEnumerator(); | var en := a.GetEnumerator(); | ||
while en.MoveNext do | while en.MoveNext do | ||
begin | begin | ||
curr := en.Current; | var curr := en.Current; | ||
Print(curr); | Print(curr); | ||
end; | end; | ||
Строка 21: | Строка 20: | ||
на синтаксическом уровне. | на синтаксическом уровне. | ||
После этого генератор yield преобразует синтаксическое дерево так, что строка: | |||
<source lang="Delphi"> | |||
var a: IEnumerable<yield_unknown_foreach_type> := l; | |||
</source> | |||
преобразуется в | |||
<source lang="Delphi"> | |||
// поле специального класса | |||
a: IEnumerable<yield_unknown_foreach_type> | |||
... | |||
// оператор в методе MoveNext этого класса | |||
a := l; | |||
</source> | |||
===Основная идея=== | |||
Основная идея автоопределния типа в yield - ввести семантический тип auto_type, который перевычисляется при первом присваивании переменной этого типа | |||
Для IEnumerable<yield_unknown_foreach_type> она трансформируется следующим образом: yield_unknown_foreach_type на семантике преобразуется в ienumerable_auto_type. | |||
При обработке присваивания на семантике если тип в левой части - IEnumerable<ienumerable_auto_type>, то он преобразуется к типу правой части в момент первого присваивания | |||
(это произойдёт в теле метода MoveNext()) | |||
===Реализация в syntax_tree_visitor.cs === | |||
Так вот, yield_unknown_foreach_type при преобразовании синтаксиса в семантику переводится в специальный семантический тип auto_type, который исправляется в момент первого присваивания переменной a. | |||
Таким образом, в syntax_tree_visitor.cs в | |||
<source lang="Delphi"> | |||
public override void visit(SyntaxTree.assign _assign) | |||
</source> | |||
будет код, аналогичный автоопределению типа в момент первого присваивания для переменных типа auto_type: | |||
<source lang="Delphi"> | |||
public override void visit(SyntaxTree.assign _assign) | |||
{ | |||
... | |||
if (to.type is compiled_generic_instance_type_node && (to.type as compiled_generic_instance_type_node).instance_params[0] is ienumerable_auto_type) | |||
{ | |||
var tt = to.type; | |||
type_node elem_type = null; | |||
var b = FindIEnumerableElementType(from.type, ref elem_type); | |||
if (!b) | |||
AddError(null, "FindIEnumerableElementType returns false"); | |||
if (to is class_field_reference) | |||
{ | |||
var cfr = to as class_field_reference; | |||
var IEnumType = new template_type_reference(new named_type_reference("System.Collections.Generic.IEnumerable"), new template_param_list(new semantic_type_node(elem_type))); | |||
cfr.field.type = convert_strong(IEnumType); | |||
cfr.type = cfr.field.type; | |||
} | |||
else if (to is local_block_variable_reference) | |||
{ | |||
var lvr = to as local_block_variable_reference; | |||
var IEnumType = new template_type_reference(new named_type_reference("System.Collections.Generic.IEnumerable"), new template_param_list(new semantic_type_node(elem_type))); | |||
lvr.var.type = convert_strong(IEnumType); // замена типа у описания переменной | |||
lvr.type = lvr.var.type; // замена типа у переменной | |||
} | |||
}</source> | |||
=== Дополнительная правка в связи с глобальными таблицами genericов === | |||
Увы, в результате реализации этого кода происходила ошибка на этапе генерации IL-кода. | |||
Выяснилось, что тип IEnumerable<ienumerable_auto_type> помещается в глобальную таблицу в generics.cs (строка около 354) | |||
<source lang="CSharp"> | |||
generic_convertions.all_type_instances.Add(instance); | |||
</source> | |||
сразу в момент описания переменной этого типа. | |||
Было принято решение вклиниться в этот код и не помещать в эту таблицу типы IEnumerable<ienumerable_auto_type>, которые всё равно не участвуют в генерации кода: | |||
<source lang="CSharp"> | |||
if (instance.instance_params[0] is ienumerable_auto_type) | |||
{ | |||
} | |||
else | |||
{ | |||
generic_convertions.all_type_instances.Add(instance); | |||
} | |||
</source> | |||
После этой правки всё заработало |
Текущая версия от 14:57, 5 июля 2016
Такой код
foreach var x in l do
Print(x);
заменяется на
var a: IEnumerable<yield_unknown_foreach_type> := l;
var en := a.GetEnumerator();
while en.MoveNext do
begin
var curr := en.Current;
Print(curr);
end;
на синтаксическом уровне.
После этого генератор yield преобразует синтаксическое дерево так, что строка:
var a: IEnumerable<yield_unknown_foreach_type> := l;
преобразуется в
// поле специального класса
a: IEnumerable<yield_unknown_foreach_type>
...
// оператор в методе MoveNext этого класса
a := l;
Основная идея
Основная идея автоопределния типа в yield - ввести семантический тип auto_type, который перевычисляется при первом присваивании переменной этого типа
Для IEnumerable<yield_unknown_foreach_type> она трансформируется следующим образом: yield_unknown_foreach_type на семантике преобразуется в ienumerable_auto_type. При обработке присваивания на семантике если тип в левой части - IEnumerable<ienumerable_auto_type>, то он преобразуется к типу правой части в момент первого присваивания (это произойдёт в теле метода MoveNext())
Реализация в syntax_tree_visitor.cs
Так вот, yield_unknown_foreach_type при преобразовании синтаксиса в семантику переводится в специальный семантический тип auto_type, который исправляется в момент первого присваивания переменной a. Таким образом, в syntax_tree_visitor.cs в
public override void visit(SyntaxTree.assign _assign)
будет код, аналогичный автоопределению типа в момент первого присваивания для переменных типа auto_type:
public override void visit(SyntaxTree.assign _assign)
{
...
if (to.type is compiled_generic_instance_type_node && (to.type as compiled_generic_instance_type_node).instance_params[0] is ienumerable_auto_type)
{
var tt = to.type;
type_node elem_type = null;
var b = FindIEnumerableElementType(from.type, ref elem_type);
if (!b)
AddError(null, "FindIEnumerableElementType returns false");
if (to is class_field_reference)
{
var cfr = to as class_field_reference;
var IEnumType = new template_type_reference(new named_type_reference("System.Collections.Generic.IEnumerable"), new template_param_list(new semantic_type_node(elem_type)));
cfr.field.type = convert_strong(IEnumType);
cfr.type = cfr.field.type;
}
else if (to is local_block_variable_reference)
{
var lvr = to as local_block_variable_reference;
var IEnumType = new template_type_reference(new named_type_reference("System.Collections.Generic.IEnumerable"), new template_param_list(new semantic_type_node(elem_type)));
lvr.var.type = convert_strong(IEnumType); // замена типа у описания переменной
lvr.type = lvr.var.type; // замена типа у переменной
}
}
Дополнительная правка в связи с глобальными таблицами genericов
Увы, в результате реализации этого кода происходила ошибка на этапе генерации IL-кода. Выяснилось, что тип IEnumerable<ienumerable_auto_type> помещается в глобальную таблицу в generics.cs (строка около 354)
generic_convertions.all_type_instances.Add(instance);
сразу в момент описания переменной этого типа.
Было принято решение вклиниться в этот код и не помещать в эту таблицу типы IEnumerable<ienumerable_auto_type>, которые всё равно не участвуют в генерации кода:
if (instance.instance_params[0] is ienumerable_auto_type)
{
}
else
{
generic_convertions.all_type_instances.Add(instance);
}
После этой правки всё заработало