blob: 885a27af9059588647bed7a88b7c8b9a119a2ca4 [file] [log] [blame]
/**
* Implements the serialization of a lambda function.
*
* The serializationis computed by visiting the abstract syntax subtree of the given lambda function.
* The serialization is a string which contains the type of the parameters and the string
* represantation of the lambda expression.
*
* Copyright: Copyright (C) 1999-2023 by The D Language Foundation, All Rights Reserved
* Authors: $(LINK2 https://www.digitalmars.com, Walter Bright)
* License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
* Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/lamdbacomp.d, _lambdacomp.d)
* Documentation: https://dlang.org/phobos/dmd_lambdacomp.html
* Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/lambdacomp.d
*/
module dmd.lambdacomp;
import core.stdc.stdio;
import core.stdc.string;
import dmd.astenums;
import dmd.declaration;
import dmd.denum;
import dmd.dsymbol;
import dmd.dtemplate;
import dmd.expression;
import dmd.func;
import dmd.dmangle;
import dmd.hdrgen;
import dmd.mtype;
import dmd.common.outbuffer;
import dmd.root.rmem;
import dmd.root.stringtable;
import dmd.dscope;
import dmd.statement;
import dmd.tokens;
import dmd.visitor;
enum LOG = false;
/**
* The type of the visited expression.
*/
private enum ExpType
{
None,
EnumDecl,
Arg
}
/**
* Compares 2 lambda functions described by their serialization.
*
* Params:
* l1 = first lambda to be compared
* l2 = second lambda to be compared
* sc = the scope where the lambdas are compared
*
* Returns:
* `true` if the 2 lambda functions are equal, `false` otherwise
*/
bool isSameFuncLiteral(FuncLiteralDeclaration l1, FuncLiteralDeclaration l2, Scope* sc)
{
bool result;
if (auto ser1 = getSerialization(l1, sc))
{
//printf("l1 serialization: %.*s\n", cast(int)ser1.length, &ser1[0]);
if (auto ser2 = getSerialization(l2, sc))
{
//printf("l2 serialization: %.*s\n", cast(int)ser2.length, &ser2[0]);
if (ser1 == ser2)
result = true;
mem.xfree(cast(void*)ser2.ptr);
}
mem.xfree(cast(void*)ser1.ptr);
}
return result;
}
/**
* Computes the string representation of a
* lambda function described by the subtree starting from a
* $(REF dmd, func, FuncLiteralDeclaration).
*
* Limitations: only IntegerExps, Enums and function
* arguments are supported in the lambda function body. The
* arguments may be of any type (basic types, user defined types),
* except template instantiations. If a function call, a local
* variable or a template instance is encountered, the
* serialization is dropped and the function is considered
* uncomparable.
*
* Params:
* fld = the starting AST node for the lambda function
* sc = the scope in which the lambda function is located
*
* Returns:
* The serialization of `fld` allocated with mem.
*/
private string getSerialization(FuncLiteralDeclaration fld, Scope* sc)
{
scope serVisitor = new SerializeVisitor(fld.parent._scope);
fld.accept(serVisitor);
const len = serVisitor.buf.length;
if (len == 0)
return null;
return cast(string)serVisitor.buf.extractSlice();
}
private extern (C++) class SerializeVisitor : SemanticTimeTransitiveVisitor
{
private:
StringTable!(const(char)[]) arg_hash;
Scope* sc;
ExpType et;
Dsymbol d;
public:
OutBuffer buf;
alias visit = SemanticTimeTransitiveVisitor.visit;
this(Scope* sc) scope
{
this.sc = sc;
}
/**
* Entrypoint of the SerializeVisitor.
*
* Params:
* fld = the lambda function for which the serialization is computed
*/
override void visit(FuncLiteralDeclaration fld)
{
assert(fld.type.ty != Terror);
static if (LOG)
printf("FuncLiteralDeclaration: %s\n", fld.toChars());
TypeFunction tf = cast(TypeFunction) fld.type;
const dim = cast(uint) tf.parameterList.length;
// Start the serialization by printing the number of
// arguments the lambda has.
buf.printf("%d:", dim);
arg_hash._init(dim + 1);
// For each argument
foreach (i, fparam; tf.parameterList)
{
if (fparam.ident !is null)
{
// the variable name is introduced into a hashtable
// where the key is the user defined name and the
// value is the cannonically name (arg0, arg1 ...)
auto key = fparam.ident.toString();
OutBuffer value;
value.writestring("arg");
value.print(i);
arg_hash.insert(key, value.extractSlice());
// and the type of the variable is serialized.
fparam.accept(this);
}
}
// Now the function body can be serialized.
ReturnStatement rs = fld.fbody.endsWithReturnStatement();
if (rs && rs.exp)
{
rs.exp.accept(this);
}
else
{
buf.setsize(0);
}
}
override void visit(DotIdExp exp)
{
static if (LOG)
printf("DotIdExp: %s\n", exp.toChars());
if (buf.length == 0)
return;
// First we need to see what kind of expression e1 is.
// It might an enum member (enum.value) or the field of
// an argument (argX.value) if the argument is an aggregate
// type. This is reported through the et variable.
exp.e1.accept(this);
if (buf.length == 0)
return;
if (et == ExpType.EnumDecl)
{
Dsymbol s = d.search(exp.loc, exp.ident);
if (s)
{
if (auto em = s.isEnumMember())
{
em.value.accept(this);
}
et = ExpType.None;
d = null;
}
}
else if (et == ExpType.Arg)
{
buf.setsize(buf.length -1);
buf.writeByte('.');
buf.writestring(exp.ident.toString());
buf.writeByte('_');
}
}
bool checkArgument(const(char)* id)
{
// The identifier may be an argument
auto stringtable_value = arg_hash.lookup(id, strlen(id));
if (stringtable_value)
{
// In which case we need to update the serialization accordingly
const(char)[] gen_id = stringtable_value.value;
buf.write(gen_id);
buf.writeByte('_');
et = ExpType.Arg;
return true;
}
return false;
}
override void visit(IdentifierExp exp)
{
static if (LOG)
printf("IdentifierExp: %s\n", exp.toChars());
if (buf.length == 0)
return;
auto id = exp.ident.toChars();
// If it's not an argument
if (!checkArgument(id))
{
// we must check what the identifier expression is.
Dsymbol scopesym;
Dsymbol s = sc.search(exp.loc, exp.ident, &scopesym);
if (s)
{
auto v = s.isVarDeclaration();
// If it's a VarDeclaration, it must be a manifest constant
if (v && (v.storage_class & STC.manifest))
{
v.getConstInitializer.accept(this);
}
else if (auto em = s.isEnumDeclaration())
{
d = em;
et = ExpType.EnumDecl;
}
else if (auto fd = s.isFuncDeclaration())
{
writeMangledName(fd);
}
// For anything else, the function is deemed uncomparable
else
{
buf.setsize(0);
}
}
// If it's an unknown symbol, consider the function incomparable
else
{
buf.setsize(0);
}
}
}
override void visit(DotVarExp exp)
{
static if (LOG)
printf("DotVarExp: %s, var: %s, e1: %s\n", exp.toChars(),
exp.var.toChars(), exp.e1.toChars());
exp.e1.accept(this);
if (buf.length == 0)
return;
buf.setsize(buf.length -1);
buf.writeByte('.');
buf.writestring(exp.var.toChars());
buf.writeByte('_');
}
override void visit(VarExp exp)
{
static if (LOG)
printf("VarExp: %s, var: %s\n", exp.toChars(), exp.var.toChars());
if (buf.length == 0)
return;
auto id = exp.var.ident.toChars();
if (!checkArgument(id))
{
buf.setsize(0);
}
}
// serialize function calls
override void visit(CallExp exp)
{
static if (LOG)
printf("CallExp: %s\n", exp.toChars());
if (buf.length == 0)
return;
if (!exp.f)
{
exp.e1.accept(this);
}
else
{
writeMangledName(exp.f);
}
buf.writeByte('(');
foreach (arg; *(exp.arguments))
{
arg.accept(this);
}
buf.writeByte(')');
}
override void visit(UnaExp exp)
{
if (buf.length == 0)
return;
buf.writeByte('(');
buf.writestring(EXPtoString(exp.op));
exp.e1.accept(this);
if (buf.length != 0)
buf.writestring(")_");
}
override void visit(IntegerExp exp)
{
if (buf.length == 0)
return;
buf.print(exp.toInteger());
buf.writeByte('_');
}
override void visit(RealExp exp)
{
if (buf.length == 0)
return;
buf.writestring(exp.toChars());
buf.writeByte('_');
}
override void visit(BinExp exp)
{
static if (LOG)
printf("BinExp: %s\n", exp.toChars());
if (buf.length == 0)
return;
buf.writeByte('(');
buf.writestring(EXPtoString(exp.op).ptr);
exp.e1.accept(this);
if (buf.length == 0)
return;
exp.e2.accept(this);
if (buf.length == 0)
return;
buf.writeByte(')');
}
override void visit(TypeBasic t)
{
buf.writestring(t.dstring);
buf.writeByte('_');
}
void writeMangledName(Dsymbol s)
{
if (s)
{
OutBuffer mangledName;
mangleToBuffer(s, &mangledName);
buf.writestring(mangledName[]);
buf.writeByte('_');
}
else
buf.setsize(0);
}
private bool checkTemplateInstance(T)(T t)
if (is(T == TypeStruct) || is(T == TypeClass))
{
if (t.sym.parent && t.sym.parent.isTemplateInstance())
{
buf.setsize(0);
return true;
}
return false;
}
override void visit(TypeStruct t)
{
static if (LOG)
printf("TypeStruct: %s\n", t.toChars);
if (!checkTemplateInstance!TypeStruct(t))
writeMangledName(t.sym);
}
override void visit(TypeClass t)
{
static if (LOG)
printf("TypeClass: %s\n", t.toChars());
if (!checkTemplateInstance!TypeClass(t))
writeMangledName(t.sym);
}
override void visit(Parameter p)
{
if (p.type.ty == Tident
&& (cast(TypeIdentifier)p.type).ident.toString().length > 3
&& strncmp((cast(TypeIdentifier)p.type).ident.toChars(), "__T", 3) == 0)
{
buf.writestring("none_");
}
else
visitType(p.type);
}
override void visit(StructLiteralExp e) {
static if (LOG)
printf("StructLiteralExp: %s\n", e.toChars);
auto ty = cast(TypeStruct)e.stype;
if (ty)
{
writeMangledName(ty.sym);
auto dim = e.elements.length;
foreach (i; 0..dim)
{
auto elem = (*e.elements)[i];
if (elem)
elem.accept(this);
else
buf.writestring("null_");
}
}
else
buf.setsize(0);
}
override void visit(ArrayLiteralExp) { buf.setsize(0); }
override void visit(AssocArrayLiteralExp) { buf.setsize(0); }
override void visit(MixinExp) { buf.setsize(0); }
override void visit(ComplexExp) { buf.setsize(0); }
override void visit(DeclarationExp) { buf.setsize(0); }
override void visit(DefaultInitExp) { buf.setsize(0); }
override void visit(DsymbolExp) { buf.setsize(0); }
override void visit(ErrorExp) { buf.setsize(0); }
override void visit(FuncExp) { buf.setsize(0); }
override void visit(HaltExp) { buf.setsize(0); }
override void visit(IntervalExp) { buf.setsize(0); }
override void visit(IsExp) { buf.setsize(0); }
override void visit(NewAnonClassExp) { buf.setsize(0); }
override void visit(NewExp) { buf.setsize(0); }
override void visit(NullExp) { buf.setsize(0); }
override void visit(ObjcClassReferenceExp) { buf.setsize(0); }
override void visit(OverExp) { buf.setsize(0); }
override void visit(ScopeExp) { buf.setsize(0); }
override void visit(StringExp) { buf.setsize(0); }
override void visit(SymbolExp) { buf.setsize(0); }
override void visit(TemplateExp) { buf.setsize(0); }
override void visit(ThisExp) { buf.setsize(0); }
override void visit(TraitsExp) { buf.setsize(0); }
override void visit(TupleExp) { buf.setsize(0); }
override void visit(TypeExp) { buf.setsize(0); }
override void visit(TypeidExp) { buf.setsize(0); }
override void visit(VoidInitExp) { buf.setsize(0); }
}