Всё исправил:
| Макрос |
| [MacroUsage(MacroPhase.WithTypedMembers, MacroTargets.Class, Inherited = false, AllowMultiple = false)]
public macro Show(ty: TypeBuilder) {
ShowImpl.Transform (ty)
}
module ShowImpl {
GetRecordCtorParams(t: TypeBuilder): option[list[string]] {
def recordCtor =
t.GetConstructors(BindingFlags.Public)
.FirstOrDefault(x => x.IsCustomAttributeDefined("Nemerle.Internal.RecordCtorAttribute"))
.OfNull();
recordCtor.Map(x => {
x.GetParameters()
.Map(x => x.CustomAttributes)
.Flatten()
.Choose(x =>
match (x) {
| PExpr.Call(_, pars) =>
Some(pars.Choose(par =>
match (par) {
| PExpr.Literal(Literal.String(name)) => Some(name)
| _ => None()
}))
| _ => None()
})
.Flatten()
})
}
public Transform(ty: TypeBuilder): void {
match (ty) {
| _ when ty.IsDelegate with n = "delegates"
| _ when ty.IsEnum with n = "enums"
| _ when ty.IsInterface with n = "interfaces"
| _ when ty.IsModule with n = "modules" =>
Message.Error(ty.Location, $"The Show macro does not support $n.");
| _ => {
ty.GetVariantOptions().Iter(opt => {
unless (opt.GetDirectMembers().Any(x => x.Name == "ToString"))
{
match (GetRecordCtorParams(opt)) {
| None => ()
| Some(pars) => {
def fieldsExpr: PExpr = pars.Fold(<[]>, (f, acc) => {
match (acc) {
| <[]> => <[ $(f: usesite).ToString() ]>
| _ => <[ $acc + ", " + $(f: usesite).ToString() ]>
}
});
def body: PExpr =
match (fieldsExpr) {
| <[]> => <[ $(opt.Name) ]>
| _ => <[ $(opt.Name) + "(" + $fieldsExpr + ")" ]>
};
opt.Define(<[decl:
public override ToString(): string {
$body
}
]>);
}
}
}
});
}
}
}
}
|
| |
Два вопроса:
Оптимально ли так определять наличие атрибута (по строковому имени)?
То же самое для определения, есть ли неунаследованный переопределенный ToString (opt.GetDirectMembers().Any(x => x.Name == "ToString"))
Получение имен аргументов через мэнчинг PExpr — ОК? Как-то низкоуровнево выглядит, учитываю, что у нас по идее уже есть типизированный метод.