Code Issue - Nemerle way
От: artelk  
Дата: 17.02.12 10:49
Оценка: 165 (10) +1
После прочтения вот этой статьи на хабре возникло желание попробовать сделать что-нибудь подобное на Nemerle. Там делается две вещи: детектирование проблем в коде и исправления в нем. Можно ли сделать второе на Nemerle — не вкурсе. Расскажу как я сделал первую часть.

Для поиска и показа issues были сделаны два макроса. Первый — это макрос уровня выражения, который, будучи примененным к блоку кода, ищет issues и показывает их в VS. Второй — это макроатрибут уровня сборки, который оборачивает тела всех методов проекта в вызов первого макроса.

Для удобства были реализованы следующие вспомогательные методы:
        public ForAllMethods(this typer : Typer, changeBoby : PExpr -> PExpr): void
        {
            def types = typer.Manager.NameTree.NamespaceTree.GetTypeBuilders(onlyTopDeclarations=false);
            
            def bindingFlags = BindingFlags.DeclaredOnly | BindingFlags.Instance |
                                BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

            foreach (method is MethodBuilder in types.SelectMany(_.GetMembers(bindingFlags)))
                method.Body = changeBoby(<[ $(method.Body) : $(method.ReturnType : typed) ]>) <- { Location = method.Body.Location };
        }
        
        public HintIssues(this typer : Typer, body : PExpr, getIssues: PExpr -> IEnumerable[string]) : PExpr
        {
            typer.TransformWhenAllTypesWouldBeInfered(
                (pexpr, _) =>
                    {
                        ExprWalker().Walk(pexpr,info =>
                            {
                                match(info.Node)
                                {
                                    | node is PExpr =>
                                        foreach(issue in getIssues(node))
                                            Message.Hint(node.Location, issue);
                                    | _ => ()
                                }
                            });
                        pexpr
                    },
                typer.TypeExpr(body), body);
        }


Метод ForAllMethods занимается поиском всех методов во всех классах проекта и применяет к ним метод changeBoby. Он нужен для реализации нашего макроатрибута.
Метод HintIssues нужен, чтобы пройтись по всем подвыражениям body, вызвать для них функцию getIssues, которая собственно детектирует issues, и показать их.
Нужно, чтобы все подвыражения были типизированы к моменту работы этого метода, поэтому он откладывает свое выполнение на этот момент, используя typer.TransformWhenAllTypesWouldBeInfered.

Используя HintIssues, реализация макроса уровня выражения выглядит так:
    macro CheckCountAny(body : PExpr)
    syntax ("checkCountAny", body) 
    {
        CheckCountAnyImpl.DoTransform(Macros.ImplicitCTX(), body)
    }
    
    module CheckCountAnyImpl
    {
        public DoTransform(typer : Typer, body : PExpr) : PExpr
        {
            Macros.DefineCTX(typer);
            
            def enumType = <[ ttype: System.Collections.Generic.IEnumerable[_] ]>;
            
            typer.HintIssues(body, node => 
                comp enumerable
                {
                    match(node)
                    {
                        | <[ $o.Count() > 0 ]>
                        | <[ 0 < $o.Count() ]>
                        | <[ $o.Count() >= 1 ]>
                        | <[ 1 <= $o.Count() ]> =>
                            when(typer.TypeExpr(o).FixedType().TryRequire(enumType))
                                yield $"Change '$node' to '$o.Any()' to avoid possible enumeration of entire sequence.";
                        | _ => ()
                    }
                });
        }
    }

Макрос в действии:


Макрос, оборачивающий тела методов:
    [MacroUsage(MacroPhase.WithTypedMembers, MacroTargets.Assembly)]
    macro CheckCountAny()
    {
        CheckCountAnyImpl.DoTransform(Macros.ImplicitCTX())
    }

    module CheckCountAnyImpl
    {
        public DoTransform(typer : Typer) : void
        {
            Macros.DefineCTX(typer);
            
            typer.ForAllMethods(body => <[ CheckCountAny($body) ]>);
        }
    }


Для его применения в проекте, где нужны соответствующие проверки, необходимо добавить ссылку на макросборку, открыть пространство имен с макросами и добавить [assembly: CheckCountAny]:
using CheckCountAnyMacro;

[assembly: CheckCountAny]

class A
{
    public Count():int {42}
}

class B : IEnumerable[int]
{
    public Count():int {42}
    public GetEnumerator() : IEnumerator[int] {null}
}


Проверка:


Исходники тут.
Просьба попинать.
code issue roslyn nemerle
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.