| /** |
| * Defines `TemplateDeclaration`, `TemplateInstance` and a few utilities |
| * |
| * This modules holds the two main template types: |
| * `TemplateDeclaration`, which is the user-provided declaration of a template, |
| * and `TemplateInstance`, which is an instance of a `TemplateDeclaration` |
| * with specific arguments. |
| * |
| * Template_Parameter: |
| * Additionally, the classes for template parameters are defined in this module. |
| * The base class, `TemplateParameter`, is inherited by: |
| * - `TemplateTypeParameter` |
| * - `TemplateThisParameter` |
| * - `TemplateValueParameter` |
| * - `TemplateAliasParameter` |
| * - `TemplateTupleParameter` |
| * |
| * Templates_semantic: |
| * The start of the template instantiation process looks like this: |
| * - A `TypeInstance` or `TypeIdentifier` is encountered. |
| * `TypeInstance` have a bang (e.g. `Foo!(arg)`) while `TypeIdentifier` don't. |
| * - A `TemplateInstance` is instantiated |
| * - Semantic is run on the `TemplateInstance` (see `dmd.dsymbolsem`) |
| * - The `TemplateInstance` search for its `TemplateDeclaration`, |
| * runs semantic on the template arguments and deduce the best match |
| * among the possible overloads. |
| * - The `TemplateInstance` search for existing instances with the same |
| * arguments, and uses it if found. |
| * - Otherwise, the rest of semantic is run on the `TemplateInstance`. |
| * |
| * Copyright: Copyright (C) 1999-2022 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/dtemplate.d, _dtemplate.d) |
| * Documentation: https://dlang.org/phobos/dmd_dtemplate.html |
| * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/dtemplate.d |
| */ |
| |
| module dmd.dtemplate; |
| |
| import core.stdc.stdio; |
| import core.stdc.string; |
| import dmd.aggregate; |
| import dmd.aliasthis; |
| import dmd.arraytypes; |
| import dmd.astenums; |
| import dmd.ast_node; |
| import dmd.dcast; |
| import dmd.dclass; |
| import dmd.declaration; |
| import dmd.dmangle; |
| import dmd.dmodule; |
| import dmd.dscope; |
| import dmd.dsymbol; |
| import dmd.dsymbolsem; |
| import dmd.errors; |
| import dmd.expression; |
| import dmd.expressionsem; |
| import dmd.func; |
| import dmd.globals; |
| import dmd.hdrgen; |
| import dmd.id; |
| import dmd.identifier; |
| import dmd.impcnvtab; |
| import dmd.init; |
| import dmd.initsem; |
| import dmd.mtype; |
| import dmd.opover; |
| import dmd.root.array; |
| import dmd.common.outbuffer; |
| import dmd.root.rootobject; |
| import dmd.semantic2; |
| import dmd.semantic3; |
| import dmd.tokens; |
| import dmd.typesem; |
| import dmd.visitor; |
| |
| import dmd.templateparamsem; |
| |
| //debug = FindExistingInstance; // print debug stats of findExistingInstance |
| private enum LOG = false; |
| |
| enum IDX_NOTFOUND = 0x12345678; |
| |
| pure nothrow @nogc |
| { |
| |
| /******************************************** |
| * These functions substitute for dynamic_cast. dynamic_cast does not work |
| * on earlier versions of gcc. |
| */ |
| extern (C++) inout(Expression) isExpression(inout RootObject o) |
| { |
| //return dynamic_cast<Expression *>(o); |
| if (!o || o.dyncast() != DYNCAST.expression) |
| return null; |
| return cast(inout(Expression))o; |
| } |
| |
| extern (C++) inout(Dsymbol) isDsymbol(inout RootObject o) |
| { |
| //return dynamic_cast<Dsymbol *>(o); |
| if (!o || o.dyncast() != DYNCAST.dsymbol) |
| return null; |
| return cast(inout(Dsymbol))o; |
| } |
| |
| extern (C++) inout(Type) isType(inout RootObject o) |
| { |
| //return dynamic_cast<Type *>(o); |
| if (!o || o.dyncast() != DYNCAST.type) |
| return null; |
| return cast(inout(Type))o; |
| } |
| |
| extern (C++) inout(Tuple) isTuple(inout RootObject o) |
| { |
| //return dynamic_cast<Tuple *>(o); |
| if (!o || o.dyncast() != DYNCAST.tuple) |
| return null; |
| return cast(inout(Tuple))o; |
| } |
| |
| extern (C++) inout(Parameter) isParameter(inout RootObject o) |
| { |
| //return dynamic_cast<Parameter *>(o); |
| if (!o || o.dyncast() != DYNCAST.parameter) |
| return null; |
| return cast(inout(Parameter))o; |
| } |
| |
| extern (C++) inout(TemplateParameter) isTemplateParameter(inout RootObject o) |
| { |
| if (!o || o.dyncast() != DYNCAST.templateparameter) |
| return null; |
| return cast(inout(TemplateParameter))o; |
| } |
| |
| /************************************** |
| * Is this Object an error? |
| */ |
| extern (C++) bool isError(const RootObject o) |
| { |
| if (const t = isType(o)) |
| return (t.ty == Terror); |
| if (const e = isExpression(o)) |
| return (e.op == EXP.error || !e.type || e.type.ty == Terror); |
| if (const v = isTuple(o)) |
| return arrayObjectIsError(&v.objects); |
| const s = isDsymbol(o); |
| assert(s); |
| if (s.errors) |
| return true; |
| return s.parent ? isError(s.parent) : false; |
| } |
| |
| /************************************** |
| * Are any of the Objects an error? |
| */ |
| bool arrayObjectIsError(const Objects* args) |
| { |
| foreach (const o; *args) |
| { |
| if (isError(o)) |
| return true; |
| } |
| return false; |
| } |
| |
| /*********************** |
| * Try to get arg as a type. |
| */ |
| inout(Type) getType(inout RootObject o) |
| { |
| inout t = isType(o); |
| if (!t) |
| { |
| if (inout e = isExpression(o)) |
| return e.type; |
| } |
| return t; |
| } |
| |
| } |
| |
| Dsymbol getDsymbol(RootObject oarg) |
| { |
| //printf("getDsymbol()\n"); |
| //printf("e %p s %p t %p v %p\n", isExpression(oarg), isDsymbol(oarg), isType(oarg), isTuple(oarg)); |
| if (auto ea = isExpression(oarg)) |
| { |
| // Try to convert Expression to symbol |
| if (auto ve = ea.isVarExp()) |
| return ve.var; |
| else if (auto fe = ea.isFuncExp()) |
| return fe.td ? fe.td : fe.fd; |
| else if (auto te = ea.isTemplateExp()) |
| return te.td; |
| else if (auto te = ea.isScopeExp()) |
| return te.sds; |
| else |
| return null; |
| } |
| else |
| { |
| // Try to convert Type to symbol |
| if (auto ta = isType(oarg)) |
| return ta.toDsymbol(null); |
| else |
| return isDsymbol(oarg); // if already a symbol |
| } |
| } |
| |
| |
| private Expression getValue(ref Dsymbol s) |
| { |
| if (s) |
| { |
| if (VarDeclaration v = s.isVarDeclaration()) |
| { |
| if (v.storage_class & STC.manifest) |
| return v.getConstInitializer(); |
| } |
| } |
| return null; |
| } |
| |
| /*********************** |
| * Try to get value from manifest constant |
| */ |
| private Expression getValue(Expression e) |
| { |
| if (!e) |
| return null; |
| if (auto ve = e.isVarExp()) |
| { |
| if (auto v = ve.var.isVarDeclaration()) |
| { |
| if (v.storage_class & STC.manifest) |
| { |
| e = v.getConstInitializer(); |
| } |
| } |
| } |
| return e; |
| } |
| |
| private Expression getExpression(RootObject o) |
| { |
| auto s = isDsymbol(o); |
| return s ? .getValue(s) : .getValue(isExpression(o)); |
| } |
| |
| /****************************** |
| * If o1 matches o2, return true. |
| * Else, return false. |
| */ |
| private bool match(RootObject o1, RootObject o2) |
| { |
| enum log = false; |
| |
| static if (log) |
| { |
| printf("match() o1 = %p %s (%d), o2 = %p %s (%d)\n", |
| o1, o1.toChars(), o1.dyncast(), o2, o2.toChars(), o2.dyncast()); |
| } |
| |
| /* A proper implementation of the various equals() overrides |
| * should make it possible to just do o1.equals(o2), but |
| * we'll do that another day. |
| */ |
| /* Manifest constants should be compared by their values, |
| * at least in template arguments. |
| */ |
| |
| if (auto t1 = isType(o1)) |
| { |
| auto t2 = isType(o2); |
| if (!t2) |
| goto Lnomatch; |
| |
| static if (log) |
| { |
| printf("\tt1 = %s\n", t1.toChars()); |
| printf("\tt2 = %s\n", t2.toChars()); |
| } |
| if (!t1.equals(t2)) |
| goto Lnomatch; |
| |
| goto Lmatch; |
| } |
| if (auto e1 = getExpression(o1)) |
| { |
| auto e2 = getExpression(o2); |
| if (!e2) |
| goto Lnomatch; |
| |
| static if (log) |
| { |
| printf("\te1 = %s '%s' %s\n", e1.type ? e1.type.toChars() : "null", EXPtoString(e1.op).ptr, e1.toChars()); |
| printf("\te2 = %s '%s' %s\n", e2.type ? e2.type.toChars() : "null", EXPtoString(e2.op).ptr, e2.toChars()); |
| } |
| |
| // two expressions can be equal although they do not have the same |
| // type; that happens when they have the same value. So check type |
| // as well as expression equality to ensure templates are properly |
| // matched. |
| if (!(e1.type && e2.type && e1.type.equals(e2.type)) || !e1.equals(e2)) |
| goto Lnomatch; |
| |
| goto Lmatch; |
| } |
| if (auto s1 = isDsymbol(o1)) |
| { |
| auto s2 = isDsymbol(o2); |
| if (!s2) |
| goto Lnomatch; |
| |
| static if (log) |
| { |
| printf("\ts1 = %s \n", s1.kind(), s1.toChars()); |
| printf("\ts2 = %s \n", s2.kind(), s2.toChars()); |
| } |
| if (!s1.equals(s2)) |
| goto Lnomatch; |
| if (s1.parent != s2.parent && !s1.isFuncDeclaration() && !s2.isFuncDeclaration()) |
| goto Lnomatch; |
| |
| goto Lmatch; |
| } |
| if (auto u1 = isTuple(o1)) |
| { |
| auto u2 = isTuple(o2); |
| if (!u2) |
| goto Lnomatch; |
| |
| static if (log) |
| { |
| printf("\tu1 = %s\n", u1.toChars()); |
| printf("\tu2 = %s\n", u2.toChars()); |
| } |
| if (!arrayObjectMatch(&u1.objects, &u2.objects)) |
| goto Lnomatch; |
| |
| goto Lmatch; |
| } |
| Lmatch: |
| static if (log) |
| printf("\t. match\n"); |
| return true; |
| |
| Lnomatch: |
| static if (log) |
| printf("\t. nomatch\n"); |
| return false; |
| } |
| |
| /************************************ |
| * Match an array of them. |
| */ |
| private bool arrayObjectMatch(Objects* oa1, Objects* oa2) |
| { |
| if (oa1 == oa2) |
| return true; |
| if (oa1.dim != oa2.dim) |
| return false; |
| immutable oa1dim = oa1.dim; |
| auto oa1d = (*oa1)[].ptr; |
| auto oa2d = (*oa2)[].ptr; |
| foreach (j; 0 .. oa1dim) |
| { |
| RootObject o1 = oa1d[j]; |
| RootObject o2 = oa2d[j]; |
| if (!match(o1, o2)) |
| { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /************************************ |
| * Return hash of Objects. |
| */ |
| private size_t arrayObjectHash(Objects* oa1) |
| { |
| import dmd.root.hash : mixHash; |
| |
| size_t hash = 0; |
| foreach (o1; *oa1) |
| { |
| /* Must follow the logic of match() |
| */ |
| if (auto t1 = isType(o1)) |
| hash = mixHash(hash, cast(size_t)t1.deco); |
| else if (auto e1 = getExpression(o1)) |
| hash = mixHash(hash, expressionHash(e1)); |
| else if (auto s1 = isDsymbol(o1)) |
| { |
| auto fa1 = s1.isFuncAliasDeclaration(); |
| if (fa1) |
| s1 = fa1.toAliasFunc(); |
| hash = mixHash(hash, mixHash(cast(size_t)cast(void*)s1.getIdent(), cast(size_t)cast(void*)s1.parent)); |
| } |
| else if (auto u1 = isTuple(o1)) |
| hash = mixHash(hash, arrayObjectHash(&u1.objects)); |
| } |
| return hash; |
| } |
| |
| |
| /************************************ |
| * Computes hash of expression. |
| * Handles all Expression classes and MUST match their equals method, |
| * i.e. e1.equals(e2) implies expressionHash(e1) == expressionHash(e2). |
| */ |
| private size_t expressionHash(Expression e) |
| { |
| import dmd.root.ctfloat : CTFloat; |
| import dmd.root.hash : calcHash, mixHash; |
| |
| switch (e.op) |
| { |
| case EXP.int64: |
| return cast(size_t) e.isIntegerExp().getInteger(); |
| |
| case EXP.float64: |
| return CTFloat.hash(e.isRealExp().value); |
| |
| case EXP.complex80: |
| auto ce = e.isComplexExp(); |
| return mixHash(CTFloat.hash(ce.toReal), CTFloat.hash(ce.toImaginary)); |
| |
| case EXP.identifier: |
| return cast(size_t)cast(void*) e.isIdentifierExp().ident; |
| |
| case EXP.null_: |
| return cast(size_t)cast(void*) e.isNullExp().type; |
| |
| case EXP.string_: |
| return calcHash(e.isStringExp.peekData()); |
| |
| case EXP.tuple: |
| { |
| auto te = e.isTupleExp(); |
| size_t hash = 0; |
| hash += te.e0 ? expressionHash(te.e0) : 0; |
| foreach (elem; *te.exps) |
| hash = mixHash(hash, expressionHash(elem)); |
| return hash; |
| } |
| |
| case EXP.arrayLiteral: |
| { |
| auto ae = e.isArrayLiteralExp(); |
| size_t hash; |
| foreach (i; 0 .. ae.elements.dim) |
| hash = mixHash(hash, expressionHash(ae[i])); |
| return hash; |
| } |
| |
| case EXP.assocArrayLiteral: |
| { |
| auto ae = e.isAssocArrayLiteralExp(); |
| size_t hash; |
| foreach (i; 0 .. ae.keys.dim) |
| // reduction needs associative op as keys are unsorted (use XOR) |
| hash ^= mixHash(expressionHash((*ae.keys)[i]), expressionHash((*ae.values)[i])); |
| return hash; |
| } |
| |
| case EXP.structLiteral: |
| { |
| auto se = e.isStructLiteralExp(); |
| size_t hash; |
| foreach (elem; *se.elements) |
| hash = mixHash(hash, elem ? expressionHash(elem) : 0); |
| return hash; |
| } |
| |
| case EXP.variable: |
| return cast(size_t)cast(void*) e.isVarExp().var; |
| |
| case EXP.function_: |
| return cast(size_t)cast(void*) e.isFuncExp().fd; |
| |
| default: |
| // no custom equals for this expression |
| assert((&e.equals).funcptr is &RootObject.equals); |
| // equals based on identity |
| return cast(size_t)cast(void*) e; |
| } |
| } |
| |
| RootObject objectSyntaxCopy(RootObject o) |
| { |
| if (!o) |
| return null; |
| if (Type t = isType(o)) |
| return t.syntaxCopy(); |
| if (Expression e = isExpression(o)) |
| return e.syntaxCopy(); |
| return o; |
| } |
| |
| extern (C++) final class Tuple : RootObject |
| { |
| Objects objects; |
| |
| extern (D) this() {} |
| |
| /** |
| Params: |
| numObjects = The initial number of objects. |
| */ |
| extern (D) this(size_t numObjects) |
| { |
| objects.setDim(numObjects); |
| } |
| |
| // kludge for template.isType() |
| override DYNCAST dyncast() const |
| { |
| return DYNCAST.tuple; |
| } |
| |
| override const(char)* toChars() const |
| { |
| return objects.toChars(); |
| } |
| } |
| |
| struct TemplatePrevious |
| { |
| TemplatePrevious* prev; |
| Scope* sc; |
| Objects* dedargs; |
| } |
| |
| /*********************************************************** |
| * [mixin] template Identifier (parameters) [Constraint] |
| * https://dlang.org/spec/template.html |
| * https://dlang.org/spec/template-mixin.html |
| */ |
| extern (C++) final class TemplateDeclaration : ScopeDsymbol |
| { |
| import dmd.root.array : Array; |
| |
| TemplateParameters* parameters; // array of TemplateParameter's |
| TemplateParameters* origParameters; // originals for Ddoc |
| |
| Expression constraint; |
| |
| // Hash table to look up TemplateInstance's of this TemplateDeclaration |
| TemplateInstance[TemplateInstanceBox] instances; |
| |
| TemplateDeclaration overnext; // next overloaded TemplateDeclaration |
| TemplateDeclaration overroot; // first in overnext list |
| FuncDeclaration funcroot; // first function in unified overload list |
| |
| Dsymbol onemember; // if !=null then one member of this template |
| |
| bool literal; // this template declaration is a literal |
| bool ismixin; // this is a mixin template declaration |
| bool isstatic; // this is static template declaration |
| bool isTrivialAliasSeq; /// matches pattern `template AliasSeq(T...) { alias AliasSeq = T; }` |
| bool isTrivialAlias; /// matches pattern `template Alias(T) { alias Alias = qualifiers(T); }` |
| bool deprecated_; /// this template declaration is deprecated |
| Visibility visibility; |
| int inuse; /// for recursive expansion detection |
| |
| // threaded list of previous instantiation attempts on stack |
| TemplatePrevious* previous; |
| |
| private Expression lastConstraint; /// the constraint after the last failed evaluation |
| private Array!Expression lastConstraintNegs; /// its negative parts |
| private Objects* lastConstraintTiargs; /// template instance arguments for `lastConstraint` |
| |
| extern (D) this(const ref Loc loc, Identifier ident, TemplateParameters* parameters, Expression constraint, Dsymbols* decldefs, bool ismixin = false, bool literal = false) |
| { |
| super(loc, ident); |
| static if (LOG) |
| { |
| printf("TemplateDeclaration(this = %p, id = '%s')\n", this, ident.toChars()); |
| } |
| version (none) |
| { |
| if (parameters) |
| for (int i = 0; i < parameters.dim; i++) |
| { |
| TemplateParameter tp = (*parameters)[i]; |
| //printf("\tparameter[%d] = %p\n", i, tp); |
| TemplateTypeParameter ttp = tp.isTemplateTypeParameter(); |
| if (ttp) |
| { |
| printf("\tparameter[%d] = %s : %s\n", i, tp.ident.toChars(), ttp.specType ? ttp.specType.toChars() : ""); |
| } |
| } |
| } |
| this.parameters = parameters; |
| this.origParameters = parameters; |
| this.constraint = constraint; |
| this.members = decldefs; |
| this.literal = literal; |
| this.ismixin = ismixin; |
| this.isstatic = true; |
| this.visibility = Visibility(Visibility.Kind.undefined); |
| |
| // Compute in advance for Ddoc's use |
| // https://issues.dlang.org/show_bug.cgi?id=11153: ident could be NULL if parsing fails. |
| if (!members || !ident) |
| return; |
| |
| Dsymbol s; |
| if (!Dsymbol.oneMembers(members, &s, ident) || !s) |
| return; |
| |
| onemember = s; |
| s.parent = this; |
| |
| /* Set isTrivialAliasSeq if this fits the pattern: |
| * template AliasSeq(T...) { alias AliasSeq = T; } |
| * or set isTrivialAlias if this fits the pattern: |
| * template Alias(T) { alias Alias = qualifiers(T); } |
| */ |
| if (!(parameters && parameters.length == 1)) |
| return; |
| |
| auto ad = s.isAliasDeclaration(); |
| if (!ad || !ad.type) |
| return; |
| |
| auto ti = ad.type.isTypeIdentifier(); |
| |
| if (!ti || ti.idents.length != 0) |
| return; |
| |
| if (auto ttp = (*parameters)[0].isTemplateTupleParameter()) |
| { |
| if (ti.ident is ttp.ident && |
| ti.mod == 0) |
| { |
| //printf("found isTrivialAliasSeq %s %s\n", s.toChars(), ad.type.toChars()); |
| isTrivialAliasSeq = true; |
| } |
| } |
| else if (auto ttp = (*parameters)[0].isTemplateTypeParameter()) |
| { |
| if (ti.ident is ttp.ident) |
| { |
| //printf("found isTrivialAlias %s %s\n", s.toChars(), ad.type.toChars()); |
| isTrivialAlias = true; |
| } |
| } |
| } |
| |
| override TemplateDeclaration syntaxCopy(Dsymbol) |
| { |
| //printf("TemplateDeclaration.syntaxCopy()\n"); |
| TemplateParameters* p = null; |
| if (parameters) |
| { |
| p = new TemplateParameters(parameters.dim); |
| foreach (i, ref param; *p) |
| param = (*parameters)[i].syntaxCopy(); |
| } |
| return new TemplateDeclaration(loc, ident, p, constraint ? constraint.syntaxCopy() : null, Dsymbol.arraySyntaxCopy(members), ismixin, literal); |
| } |
| |
| /********************************** |
| * Overload existing TemplateDeclaration 'this' with the new one 's'. |
| * Return true if successful; i.e. no conflict. |
| */ |
| override bool overloadInsert(Dsymbol s) |
| { |
| static if (LOG) |
| { |
| printf("TemplateDeclaration.overloadInsert('%s')\n", s.toChars()); |
| } |
| FuncDeclaration fd = s.isFuncDeclaration(); |
| if (fd) |
| { |
| if (funcroot) |
| return funcroot.overloadInsert(fd); |
| funcroot = fd; |
| return funcroot.overloadInsert(this); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=15795 |
| // if candidate is an alias and its sema is not run then |
| // insertion can fail because the thing it alias is not known |
| if (AliasDeclaration ad = s.isAliasDeclaration()) |
| { |
| if (s._scope) |
| aliasSemantic(ad, s._scope); |
| if (ad.aliassym && ad.aliassym is this) |
| return false; |
| } |
| TemplateDeclaration td = s.toAlias().isTemplateDeclaration(); |
| if (!td) |
| return false; |
| |
| TemplateDeclaration pthis = this; |
| TemplateDeclaration* ptd; |
| for (ptd = &pthis; *ptd; ptd = &(*ptd).overnext) |
| { |
| } |
| |
| td.overroot = this; |
| *ptd = td; |
| static if (LOG) |
| { |
| printf("\ttrue: no conflict\n"); |
| } |
| return true; |
| } |
| |
| override bool hasStaticCtorOrDtor() |
| { |
| return false; // don't scan uninstantiated templates |
| } |
| |
| override const(char)* kind() const |
| { |
| return (onemember && onemember.isAggregateDeclaration()) ? onemember.kind() : "template"; |
| } |
| |
| override const(char)* toChars() const |
| { |
| return toCharsMaybeConstraints(true); |
| } |
| |
| /**************************** |
| * Similar to `toChars`, but does not print the template constraints |
| */ |
| const(char)* toCharsNoConstraints() const |
| { |
| return toCharsMaybeConstraints(false); |
| } |
| |
| const(char)* toCharsMaybeConstraints(bool includeConstraints) const |
| { |
| if (literal) |
| return Dsymbol.toChars(); |
| |
| OutBuffer buf; |
| HdrGenState hgs; |
| |
| buf.writestring(ident.toString()); |
| buf.writeByte('('); |
| foreach (i, const tp; *parameters) |
| { |
| if (i) |
| buf.writestring(", "); |
| .toCBuffer(tp, &buf, &hgs); |
| } |
| buf.writeByte(')'); |
| |
| if (onemember) |
| { |
| const FuncDeclaration fd = onemember.isFuncDeclaration(); |
| if (fd && fd.type) |
| { |
| TypeFunction tf = cast(TypeFunction)fd.type; |
| buf.writestring(parametersTypeToChars(tf.parameterList)); |
| } |
| } |
| |
| if (includeConstraints && |
| constraint) |
| { |
| buf.writestring(" if ("); |
| .toCBuffer(constraint, &buf, &hgs); |
| buf.writeByte(')'); |
| } |
| |
| return buf.extractChars(); |
| } |
| |
| override Visibility visible() pure nothrow @nogc @safe |
| { |
| return visibility; |
| } |
| |
| /**************************** |
| * Check to see if constraint is satisfied. |
| */ |
| extern (D) bool evaluateConstraint(TemplateInstance ti, Scope* sc, Scope* paramscope, Objects* dedargs, FuncDeclaration fd) |
| { |
| /* Detect recursive attempts to instantiate this template declaration, |
| * https://issues.dlang.org/show_bug.cgi?id=4072 |
| * void foo(T)(T x) if (is(typeof(foo(x)))) { } |
| * static assert(!is(typeof(foo(7)))); |
| * Recursive attempts are regarded as a constraint failure. |
| */ |
| /* There's a chicken-and-egg problem here. We don't know yet if this template |
| * instantiation will be a local one (enclosing is set), and we won't know until |
| * after selecting the correct template. Thus, function we're nesting inside |
| * is not on the sc scope chain, and this can cause errors in FuncDeclaration.getLevel(). |
| * Workaround the problem by setting a flag to relax the checking on frame errors. |
| */ |
| |
| for (TemplatePrevious* p = previous; p; p = p.prev) |
| { |
| if (!arrayObjectMatch(p.dedargs, dedargs)) |
| continue; |
| //printf("recursive, no match p.sc=%p %p %s\n", p.sc, this, this.toChars()); |
| /* It must be a subscope of p.sc, other scope chains are not recursive |
| * instantiations. |
| * the chain of enclosing scopes is broken by paramscope (its enclosing |
| * scope is _scope, but paramscope.callsc is the instantiating scope). So |
| * it's good enough to check the chain of callsc |
| */ |
| for (Scope* scx = paramscope.callsc; scx; scx = scx.callsc) |
| { |
| // The first scx might be identical for nested eponymeous templates, e.g. |
| // template foo() { void foo()() {...} } |
| if (scx == p.sc && scx !is paramscope.callsc) |
| return false; |
| } |
| /* BUG: should also check for ref param differences |
| */ |
| } |
| |
| TemplatePrevious pr; |
| pr.prev = previous; |
| pr.sc = paramscope.callsc; |
| pr.dedargs = dedargs; |
| previous = ≺ // add this to threaded list |
| |
| Scope* scx = paramscope.push(ti); |
| scx.parent = ti; |
| scx.tinst = null; |
| scx.minst = null; |
| // Set SCOPE.constraint before declaring function parameters for the static condition |
| // (previously, this was immediately before calling evalStaticCondition), so the |
| // semantic pass knows not to issue deprecation warnings for these throw-away decls. |
| // https://issues.dlang.org/show_bug.cgi?id=21831 |
| scx.flags |= SCOPE.constraint; |
| |
| assert(!ti.symtab); |
| if (fd) |
| { |
| /* Declare all the function parameters as variables and add them to the scope |
| * Making parameters is similar to FuncDeclaration.semantic3 |
| */ |
| auto tf = fd.type.isTypeFunction(); |
| |
| scx.parent = fd; |
| |
| Parameters* fparameters = tf.parameterList.parameters; |
| const nfparams = tf.parameterList.length; |
| foreach (i, fparam; tf.parameterList) |
| { |
| fparam.storageClass &= (STC.IOR | STC.lazy_ | STC.final_ | STC.TYPECTOR | STC.nodtor); |
| fparam.storageClass |= STC.parameter; |
| if (tf.parameterList.varargs == VarArg.typesafe && i + 1 == nfparams) |
| { |
| fparam.storageClass |= STC.variadic; |
| /* Don't need to set STC.scope_ because this will only |
| * be evaluated at compile time |
| */ |
| } |
| } |
| foreach (fparam; *fparameters) |
| { |
| if (!fparam.ident) |
| continue; |
| // don't add it, if it has no name |
| auto v = new VarDeclaration(loc, fparam.type, fparam.ident, null); |
| fparam.storageClass |= STC.parameter; |
| v.storage_class = fparam.storageClass; |
| v.dsymbolSemantic(scx); |
| if (!ti.symtab) |
| ti.symtab = new DsymbolTable(); |
| if (!scx.insert(v)) |
| error("parameter `%s.%s` is already defined", toChars(), v.toChars()); |
| else |
| v.parent = fd; |
| } |
| if (isstatic) |
| fd.storage_class |= STC.static_; |
| fd.declareThis(scx); |
| } |
| |
| lastConstraint = constraint.syntaxCopy(); |
| lastConstraintTiargs = ti.tiargs; |
| lastConstraintNegs.setDim(0); |
| |
| import dmd.staticcond; |
| |
| assert(ti.inst is null); |
| ti.inst = ti; // temporary instantiation to enable genIdent() |
| bool errors; |
| const bool result = evalStaticCondition(scx, constraint, lastConstraint, errors, &lastConstraintNegs); |
| if (result || errors) |
| { |
| lastConstraint = null; |
| lastConstraintTiargs = null; |
| lastConstraintNegs.setDim(0); |
| } |
| ti.inst = null; |
| ti.symtab = null; |
| scx = scx.pop(); |
| previous = pr.prev; // unlink from threaded list |
| if (errors) |
| return false; |
| return result; |
| } |
| |
| /**************************** |
| * Destructively get the error message from the last constraint evaluation |
| * Params: |
| * tip = tip to show after printing all overloads |
| */ |
| const(char)* getConstraintEvalError(ref const(char)* tip) |
| { |
| import dmd.staticcond; |
| |
| // there will be a full tree view in verbose mode, and more compact list in the usual |
| const full = global.params.verbose; |
| uint count; |
| const msg = visualizeStaticCondition(constraint, lastConstraint, lastConstraintNegs[], full, count); |
| scope (exit) |
| { |
| lastConstraint = null; |
| lastConstraintTiargs = null; |
| lastConstraintNegs.setDim(0); |
| } |
| if (!msg) |
| return null; |
| |
| OutBuffer buf; |
| |
| assert(parameters && lastConstraintTiargs); |
| if (parameters.length > 0) |
| { |
| formatParamsWithTiargs(*lastConstraintTiargs, buf); |
| buf.writenl(); |
| } |
| if (!full) |
| { |
| // choosing singular/plural |
| const s = (count == 1) ? |
| " must satisfy the following constraint:" : |
| " must satisfy one of the following constraints:"; |
| buf.writestring(s); |
| buf.writenl(); |
| // the constraints |
| buf.writeByte('`'); |
| buf.writestring(msg); |
| buf.writeByte('`'); |
| } |
| else |
| { |
| buf.writestring(" whose parameters have the following constraints:"); |
| buf.writenl(); |
| const sep = " `~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~`"; |
| buf.writestring(sep); |
| buf.writenl(); |
| // the constraints |
| buf.writeByte('`'); |
| buf.writestring(msg); |
| buf.writeByte('`'); |
| buf.writestring(sep); |
| tip = "not satisfied constraints are marked with `>`"; |
| } |
| return buf.extractChars(); |
| } |
| |
| private void formatParamsWithTiargs(ref Objects tiargs, ref OutBuffer buf) |
| { |
| buf.writestring(" with `"); |
| |
| // write usual arguments line-by-line |
| // skips trailing default ones - they are not present in `tiargs` |
| const bool variadic = isVariadic() !is null; |
| const end = cast(int)parameters.length - (variadic ? 1 : 0); |
| uint i; |
| for (; i < tiargs.length && i < end; i++) |
| { |
| if (i > 0) |
| { |
| buf.writeByte(','); |
| buf.writenl(); |
| buf.writestring(" "); |
| } |
| write(buf, (*parameters)[i]); |
| buf.writestring(" = "); |
| write(buf, tiargs[i]); |
| } |
| // write remaining variadic arguments on the last line |
| if (variadic) |
| { |
| if (i > 0) |
| { |
| buf.writeByte(','); |
| buf.writenl(); |
| buf.writestring(" "); |
| } |
| write(buf, (*parameters)[end]); |
| buf.writestring(" = "); |
| buf.writeByte('('); |
| if (cast(int)tiargs.length - end > 0) |
| { |
| write(buf, tiargs[end]); |
| foreach (j; parameters.length .. tiargs.length) |
| { |
| buf.writestring(", "); |
| write(buf, tiargs[j]); |
| } |
| } |
| buf.writeByte(')'); |
| } |
| buf.writeByte('`'); |
| } |
| |
| /****************************** |
| * Create a scope for the parameters of the TemplateInstance |
| * `ti` in the parent scope sc from the ScopeDsymbol paramsym. |
| * |
| * If paramsym is null a new ScopeDsymbol is used in place of |
| * paramsym. |
| * Params: |
| * ti = the TemplateInstance whose parameters to generate the scope for. |
| * sc = the parent scope of ti |
| * Returns: |
| * a scope for the parameters of ti |
| */ |
| Scope* scopeForTemplateParameters(TemplateInstance ti, Scope* sc) |
| { |
| ScopeDsymbol paramsym = new ScopeDsymbol(); |
| paramsym.parent = _scope.parent; |
| Scope* paramscope = _scope.push(paramsym); |
| paramscope.tinst = ti; |
| paramscope.minst = sc.minst; |
| paramscope.callsc = sc; |
| paramscope.stc = 0; |
| return paramscope; |
| } |
| |
| /*************************************** |
| * Given that ti is an instance of this TemplateDeclaration, |
| * deduce the types of the parameters to this, and store |
| * those deduced types in dedtypes[]. |
| * Input: |
| * flag 1: don't do semantic() because of dummy types |
| * 2: don't change types in matchArg() |
| * Output: |
| * dedtypes deduced arguments |
| * Return match level. |
| */ |
| extern (D) MATCH matchWithInstance(Scope* sc, TemplateInstance ti, Objects* dedtypes, Expressions* fargs, int flag) |
| { |
| enum LOGM = 0; |
| static if (LOGM) |
| { |
| printf("\n+TemplateDeclaration.matchWithInstance(this = %s, ti = %s, flag = %d)\n", toChars(), ti.toChars(), flag); |
| } |
| version (none) |
| { |
| printf("dedtypes.dim = %d, parameters.dim = %d\n", dedtypes.dim, parameters.dim); |
| if (ti.tiargs.dim) |
| printf("ti.tiargs.dim = %d, [0] = %p\n", ti.tiargs.dim, (*ti.tiargs)[0]); |
| } |
| MATCH nomatch() |
| { |
| static if (LOGM) |
| { |
| printf(" no match\n"); |
| } |
| return MATCH.nomatch; |
| } |
| MATCH m; |
| size_t dedtypes_dim = dedtypes.dim; |
| |
| dedtypes.zero(); |
| |
| if (errors) |
| return MATCH.nomatch; |
| |
| size_t parameters_dim = parameters.dim; |
| int variadic = isVariadic() !is null; |
| |
| // If more arguments than parameters, no match |
| if (ti.tiargs.dim > parameters_dim && !variadic) |
| { |
| static if (LOGM) |
| { |
| printf(" no match: more arguments than parameters\n"); |
| } |
| return MATCH.nomatch; |
| } |
| |
| assert(dedtypes_dim == parameters_dim); |
| assert(dedtypes_dim >= ti.tiargs.dim || variadic); |
| |
| assert(_scope); |
| |
| // Set up scope for template parameters |
| Scope* paramscope = scopeForTemplateParameters(ti,sc); |
| |
| // Attempt type deduction |
| m = MATCH.exact; |
| for (size_t i = 0; i < dedtypes_dim; i++) |
| { |
| MATCH m2; |
| TemplateParameter tp = (*parameters)[i]; |
| Declaration sparam; |
| |
| //printf("\targument [%d]\n", i); |
| static if (LOGM) |
| { |
| //printf("\targument [%d] is %s\n", i, oarg ? oarg.toChars() : "null"); |
| TemplateTypeParameter ttp = tp.isTemplateTypeParameter(); |
| if (ttp) |
| printf("\tparameter[%d] is %s : %s\n", i, tp.ident.toChars(), ttp.specType ? ttp.specType.toChars() : ""); |
| } |
| |
| inuse++; |
| m2 = tp.matchArg(ti.loc, paramscope, ti.tiargs, i, parameters, dedtypes, &sparam); |
| inuse--; |
| //printf("\tm2 = %d\n", m2); |
| if (m2 == MATCH.nomatch) |
| { |
| version (none) |
| { |
| printf("\tmatchArg() for parameter %i failed\n", i); |
| } |
| return nomatch(); |
| } |
| |
| if (m2 < m) |
| m = m2; |
| |
| if (!flag) |
| sparam.dsymbolSemantic(paramscope); |
| if (!paramscope.insert(sparam)) // TODO: This check can make more early |
| { |
| // in TemplateDeclaration.semantic, and |
| // then we don't need to make sparam if flags == 0 |
| return nomatch(); |
| } |
| } |
| |
| if (!flag) |
| { |
| /* Any parameter left without a type gets the type of |
| * its corresponding arg |
| */ |
| foreach (i, ref dedtype; *dedtypes) |
| { |
| if (!dedtype) |
| { |
| assert(i < ti.tiargs.dim); |
| dedtype = cast(Type)(*ti.tiargs)[i]; |
| } |
| } |
| } |
| |
| if (m > MATCH.nomatch && constraint && !flag) |
| { |
| if (ti.hasNestedArgs(ti.tiargs, this.isstatic)) // TODO: should gag error |
| ti.parent = ti.enclosing; |
| else |
| ti.parent = this.parent; |
| |
| // Similar to doHeaderInstantiation |
| FuncDeclaration fd = onemember ? onemember.isFuncDeclaration() : null; |
| if (fd) |
| { |
| TypeFunction tf = fd.type.isTypeFunction().syntaxCopy(); |
| |
| fd = new FuncDeclaration(fd.loc, fd.endloc, fd.ident, fd.storage_class, tf); |
| fd.parent = ti; |
| fd.inferRetType = true; |
| |
| // Shouldn't run semantic on default arguments and return type. |
| foreach (ref param; *tf.parameterList.parameters) |
| param.defaultArg = null; |
| |
| tf.next = null; |
| tf.incomplete = true; |
| |
| // Resolve parameter types and 'auto ref's. |
| tf.fargs = fargs; |
| uint olderrors = global.startGagging(); |
| fd.type = tf.typeSemantic(loc, paramscope); |
| global.endGagging(olderrors); |
| if (fd.type.ty != Tfunction) |
| return nomatch(); |
| fd.originalType = fd.type; // for mangling |
| } |
| |
| // TODO: dedtypes => ti.tiargs ? |
| if (!evaluateConstraint(ti, sc, paramscope, dedtypes, fd)) |
| return nomatch(); |
| } |
| |
| static if (LOGM) |
| { |
| // Print out the results |
| printf("--------------------------\n"); |
| printf("template %s\n", toChars()); |
| printf("instance %s\n", ti.toChars()); |
| if (m > MATCH.nomatch) |
| { |
| for (size_t i = 0; i < dedtypes_dim; i++) |
| { |
| TemplateParameter tp = (*parameters)[i]; |
| RootObject oarg; |
| printf(" [%d]", i); |
| if (i < ti.tiargs.dim) |
| oarg = (*ti.tiargs)[i]; |
| else |
| oarg = null; |
| tp.print(oarg, (*dedtypes)[i]); |
| } |
| } |
| else |
| return nomatch(); |
| } |
| static if (LOGM) |
| { |
| printf(" match = %d\n", m); |
| } |
| |
| paramscope.pop(); |
| static if (LOGM) |
| { |
| printf("-TemplateDeclaration.matchWithInstance(this = %p, ti = %p) = %d\n", this, ti, m); |
| } |
| return m; |
| } |
| |
| /******************************************** |
| * Determine partial specialization order of 'this' vs td2. |
| * Returns: |
| * match this is at least as specialized as td2 |
| * 0 td2 is more specialized than this |
| */ |
| MATCH leastAsSpecialized(Scope* sc, TemplateDeclaration td2, Expressions* fargs) |
| { |
| enum LOG_LEASTAS = 0; |
| static if (LOG_LEASTAS) |
| { |
| printf("%s.leastAsSpecialized(%s)\n", toChars(), td2.toChars()); |
| } |
| |
| /* This works by taking the template parameters to this template |
| * declaration and feeding them to td2 as if it were a template |
| * instance. |
| * If it works, then this template is at least as specialized |
| * as td2. |
| */ |
| |
| // Set type arguments to dummy template instance to be types |
| // generated from the parameters to this template declaration |
| auto tiargs = new Objects(); |
| tiargs.reserve(parameters.dim); |
| foreach (tp; *parameters) |
| { |
| if (tp.dependent) |
| break; |
| RootObject p = tp.dummyArg(); |
| if (!p) //TemplateTupleParameter |
| break; |
| |
| tiargs.push(p); |
| } |
| scope TemplateInstance ti = new TemplateInstance(Loc.initial, ident, tiargs); // create dummy template instance |
| |
| // Temporary Array to hold deduced types |
| Objects dedtypes = Objects(td2.parameters.dim); |
| |
| // Attempt a type deduction |
| MATCH m = td2.matchWithInstance(sc, ti, &dedtypes, fargs, 1); |
| if (m > MATCH.nomatch) |
| { |
| /* A non-variadic template is more specialized than a |
| * variadic one. |
| */ |
| TemplateTupleParameter tp = isVariadic(); |
| if (tp && !tp.dependent && !td2.isVariadic()) |
| goto L1; |
| |
| static if (LOG_LEASTAS) |
| { |
| printf(" matches %d, so is least as specialized\n", m); |
| } |
| return m; |
| } |
| L1: |
| static if (LOG_LEASTAS) |
| { |
| printf(" doesn't match, so is not as specialized\n"); |
| } |
| return MATCH.nomatch; |
| } |
| |
| /************************************************* |
| * Match function arguments against a specific template function. |
| * Input: |
| * ti |
| * sc instantiation scope |
| * fd |
| * tthis 'this' argument if !NULL |
| * fargs arguments to function |
| * Output: |
| * fd Partially instantiated function declaration |
| * ti.tdtypes Expression/Type deduced template arguments |
| * Returns: |
| * match pair of initial and inferred template arguments |
| */ |
| extern (D) MATCHpair deduceFunctionTemplateMatch(TemplateInstance ti, Scope* sc, ref FuncDeclaration fd, Type tthis, Expressions* fargs) |
| { |
| size_t nfparams; |
| size_t nfargs; |
| size_t ntargs; // array size of tiargs |
| size_t fptupindex = IDX_NOTFOUND; |
| MATCH match = MATCH.exact; |
| MATCH matchTiargs = MATCH.exact; |
| ParameterList fparameters; // function parameter list |
| VarArg fvarargs; // function varargs |
| uint wildmatch = 0; |
| size_t inferStart = 0; |
| |
| Loc instLoc = ti.loc; |
| Objects* tiargs = ti.tiargs; |
| auto dedargs = new Objects(parameters.dim); |
| Objects* dedtypes = &ti.tdtypes; // for T:T*, the dedargs is the T*, dedtypes is the T |
| |
| version (none) |
| { |
| printf("\nTemplateDeclaration.deduceFunctionTemplateMatch() %s\n", toChars()); |
| for (size_t i = 0; i < (fargs ? fargs.dim : 0); i++) |
| { |
| Expression e = (*fargs)[i]; |
| printf("\tfarg[%d] is %s, type is %s\n", i, e.toChars(), e.type.toChars()); |
| } |
| printf("fd = %s\n", fd.toChars()); |
| printf("fd.type = %s\n", fd.type.toChars()); |
| if (tthis) |
| printf("tthis = %s\n", tthis.toChars()); |
| } |
| |
| assert(_scope); |
| |
| dedargs.zero(); |
| |
| dedtypes.setDim(parameters.dim); |
| dedtypes.zero(); |
| |
| if (errors || fd.errors) |
| return MATCHpair(MATCH.nomatch, MATCH.nomatch); |
| |
| // Set up scope for parameters |
| Scope* paramscope = scopeForTemplateParameters(ti,sc); |
| |
| MATCHpair nomatch() |
| { |
| paramscope.pop(); |
| //printf("\tnomatch\n"); |
| return MATCHpair(MATCH.nomatch, MATCH.nomatch); |
| } |
| |
| MATCHpair matcherror() |
| { |
| // todo: for the future improvement |
| paramscope.pop(); |
| //printf("\terror\n"); |
| return MATCHpair(MATCH.nomatch, MATCH.nomatch); |
| } |
| // Mark the parameter scope as deprecated if the templated |
| // function is deprecated (since paramscope.enclosing is the |
| // calling scope already) |
| paramscope.stc |= fd.storage_class & STC.deprecated_; |
| |
| TemplateTupleParameter tp = isVariadic(); |
| Tuple declaredTuple = null; |
| |
| version (none) |
| { |
| for (size_t i = 0; i < dedargs.dim; i++) |
| { |
| printf("\tdedarg[%d] = ", i); |
| RootObject oarg = (*dedargs)[i]; |
| if (oarg) |
| printf("%s", oarg.toChars()); |
| printf("\n"); |
| } |
| } |
| |
| ntargs = 0; |
| if (tiargs) |
| { |
| // Set initial template arguments |
| ntargs = tiargs.dim; |
| size_t n = parameters.dim; |
| if (tp) |
| n--; |
| if (ntargs > n) |
| { |
| if (!tp) |
| return nomatch(); |
| |
| /* The extra initial template arguments |
| * now form the tuple argument. |
| */ |
| auto t = new Tuple(ntargs - n); |
| assert(parameters.dim); |
| (*dedargs)[parameters.dim - 1] = t; |
| |
| for (size_t i = 0; i < t.objects.dim; i++) |
| { |
| t.objects[i] = (*tiargs)[n + i]; |
| } |
| declareParameter(paramscope, tp, t); |
| declaredTuple = t; |
| } |
| else |
| n = ntargs; |
| |
| memcpy(dedargs.tdata(), tiargs.tdata(), n * (*dedargs.tdata()).sizeof); |
| |
| for (size_t i = 0; i < n; i++) |
| { |
| assert(i < parameters.dim); |
| Declaration sparam = null; |
| MATCH m = (*parameters)[i].matchArg(instLoc, paramscope, dedargs, i, parameters, dedtypes, &sparam); |
| //printf("\tdeduceType m = %d\n", m); |
| if (m == MATCH.nomatch) |
| return nomatch(); |
| if (m < matchTiargs) |
| matchTiargs = m; |
| |
| sparam.dsymbolSemantic(paramscope); |
| if (!paramscope.insert(sparam)) |
| return nomatch(); |
| } |
| if (n < parameters.dim && !declaredTuple) |
| { |
| inferStart = n; |
| } |
| else |
| inferStart = parameters.dim; |
| //printf("tiargs matchTiargs = %d\n", matchTiargs); |
| } |
| version (none) |
| { |
| for (size_t i = 0; i < dedargs.dim; i++) |
| { |
| printf("\tdedarg[%d] = ", i); |
| RootObject oarg = (*dedargs)[i]; |
| if (oarg) |
| printf("%s", oarg.toChars()); |
| printf("\n"); |
| } |
| } |
| |
| fparameters = fd.getParameterList(); |
| nfparams = fparameters.length; // number of function parameters |
| nfargs = fargs ? fargs.dim : 0; // number of function arguments |
| |
| /* Check for match of function arguments with variadic template |
| * parameter, such as: |
| * |
| * void foo(T, A...)(T t, A a); |
| * void main() { foo(1,2,3); } |
| */ |
| if (tp) // if variadic |
| { |
| // TemplateTupleParameter always makes most lesser matching. |
| matchTiargs = MATCH.convert; |
| |
| if (nfparams == 0 && nfargs != 0) // if no function parameters |
| { |
| if (!declaredTuple) |
| { |
| auto t = new Tuple(); |
| //printf("t = %p\n", t); |
| (*dedargs)[parameters.dim - 1] = t; |
| declareParameter(paramscope, tp, t); |
| declaredTuple = t; |
| } |
| } |
| else |
| { |
| /* Figure out which of the function parameters matches |
| * the tuple template parameter. Do this by matching |
| * type identifiers. |
| * Set the index of this function parameter to fptupindex. |
| */ |
| for (fptupindex = 0; fptupindex < nfparams; fptupindex++) |
| { |
| auto fparam = (*fparameters.parameters)[fptupindex]; // fparameters[fptupindex] ? |
| if (fparam.type.ty != Tident) |
| continue; |
| TypeIdentifier tid = cast(TypeIdentifier)fparam.type; |
| if (!tp.ident.equals(tid.ident) || tid.idents.dim) |
| continue; |
| |
| if (fparameters.varargs != VarArg.none) // variadic function doesn't |
| return nomatch(); // go with variadic template |
| |
| goto L1; |
| } |
| fptupindex = IDX_NOTFOUND; |
| L1: |
| } |
| } |
| |
| if (toParent().isModule()) |
| tthis = null; |
| if (tthis) |
| { |
| bool hasttp = false; |
| |
| // Match 'tthis' to any TemplateThisParameter's |
| foreach (param; *parameters) |
| { |
| if (auto ttp = param.isTemplateThisParameter()) |
| { |
| hasttp = true; |
| |
| Type t = new TypeIdentifier(Loc.initial, ttp.ident); |
| MATCH m = deduceType(tthis, paramscope, t, parameters, dedtypes); |
| if (m == MATCH.nomatch) |
| return nomatch(); |
| if (m < match) |
| match = m; // pick worst match |
| } |
| } |
| |
| // Match attributes of tthis against attributes of fd |
| if (fd.type && !fd.isCtorDeclaration() && !(_scope.stc & STC.static_)) |
| { |
| StorageClass stc = _scope.stc | fd.storage_class2; |
| // Propagate parent storage class, https://issues.dlang.org/show_bug.cgi?id=5504 |
| Dsymbol p = parent; |
| while (p.isTemplateDeclaration() || p.isTemplateInstance()) |
| p = p.parent; |
| AggregateDeclaration ad = p.isAggregateDeclaration(); |
| if (ad) |
| stc |= ad.storage_class; |
| |
| ubyte mod = fd.type.mod; |
| if (stc & STC.immutable_) |
| mod = MODFlags.immutable_; |
| else |
| { |
| if (stc & (STC.shared_ | STC.synchronized_)) |
| mod |= MODFlags.shared_; |
| if (stc & STC.const_) |
| mod |= MODFlags.const_; |
| if (stc & STC.wild) |
| mod |= MODFlags.wild; |
| } |
| |
| ubyte thismod = tthis.mod; |
| if (hasttp) |
| mod = MODmerge(thismod, mod); |
| MATCH m = MODmethodConv(thismod, mod); |
| if (m == MATCH.nomatch) |
| return nomatch(); |
| if (m < match) |
| match = m; |
| } |
| } |
| |
| // Loop through the function parameters |
| { |
| //printf("%s\n\tnfargs = %d, nfparams = %d, tuple_dim = %d\n", toChars(), nfargs, nfparams, declaredTuple ? declaredTuple.objects.dim : 0); |
| //printf("\ttp = %p, fptupindex = %d, found = %d, declaredTuple = %s\n", tp, fptupindex, fptupindex != IDX_NOTFOUND, declaredTuple ? declaredTuple.toChars() : NULL); |
| size_t argi = 0; |
| size_t nfargs2 = nfargs; // nfargs + supplied defaultArgs |
| for (size_t parami = 0; parami < nfparams; parami++) |
| { |
| Parameter fparam = fparameters[parami]; |
| |
| // Apply function parameter storage classes to parameter types |
| Type prmtype = fparam.type.addStorageClass(fparam.storageClass); |
| |
| Expression farg; |
| |
| /* See function parameters which wound up |
| * as part of a template tuple parameter. |
| */ |
| if (fptupindex != IDX_NOTFOUND && parami == fptupindex) |
| { |
| assert(prmtype.ty == Tident); |
| TypeIdentifier tid = cast(TypeIdentifier)prmtype; |
| if (!declaredTuple) |
| { |
| /* The types of the function arguments |
| * now form the tuple argument. |
| */ |
| declaredTuple = new Tuple(); |
| (*dedargs)[parameters.dim - 1] = declaredTuple; |
| |
| /* Count function parameters with no defaults following a tuple parameter. |
| * void foo(U, T...)(int y, T, U, double, int bar = 0) {} // rem == 2 (U, double) |
| */ |
| size_t rem = 0; |
| for (size_t j = parami + 1; j < nfparams; j++) |
| { |
| Parameter p = fparameters[j]; |
| if (p.defaultArg) |
| { |
| break; |
| } |
| if (!reliesOnTemplateParameters(p.type, (*parameters)[inferStart .. parameters.dim])) |
| { |
| Type pt = p.type.syntaxCopy().typeSemantic(fd.loc, paramscope); |
| rem += pt.ty == Ttuple ? (cast(TypeTuple)pt).arguments.dim : 1; |
| } |
| else |
| { |
| ++rem; |
| } |
| } |
| |
| if (nfargs2 - argi < rem) |
| return nomatch(); |
| declaredTuple.objects.setDim(nfargs2 - argi - rem); |
| for (size_t i = 0; i < declaredTuple.objects.dim; i++) |
| { |
| farg = (*fargs)[argi + i]; |
| |
| // Check invalid arguments to detect errors early. |
| if (farg.op == EXP.error || farg.type.ty == Terror) |
| return nomatch(); |
| |
| if (!fparam.isLazy() && farg.type.ty == Tvoid) |
| return nomatch(); |
| |
| Type tt; |
| MATCH m; |
| if (ubyte wm = deduceWildHelper(farg.type, &tt, tid)) |
| { |
| wildmatch |= wm; |
| m = MATCH.constant; |
| } |
| else |
| { |
| m = deduceTypeHelper(farg.type, &tt, tid); |
| } |
| if (m == MATCH.nomatch) |
| return nomatch(); |
| if (m < match) |
| match = m; |
| |
| /* Remove top const for dynamic array types and pointer types |
| */ |
| if ((tt.ty == Tarray || tt.ty == Tpointer) && !tt.isMutable() && (!(fparam.storageClass & STC.ref_) || (fparam.storageClass & STC.auto_) && !farg.isLvalue())) |
| { |
| tt = tt.mutableOf(); |
| } |
| declaredTuple.objects[i] = tt; |
| } |
| declareParameter(paramscope, tp, declaredTuple); |
| } |
| else |
| { |
| // https://issues.dlang.org/show_bug.cgi?id=6810 |
| // If declared tuple is not a type tuple, |
| // it cannot be function parameter types. |
| for (size_t i = 0; i < declaredTuple.objects.dim; i++) |
| { |
| if (!isType(declaredTuple.objects[i])) |
| return nomatch(); |
| } |
| } |
| assert(declaredTuple); |
| argi += declaredTuple.objects.dim; |
| continue; |
| } |
| |
| // If parameter type doesn't depend on inferred template parameters, |
| // semantic it to get actual type. |
| if (!reliesOnTemplateParameters(prmtype, (*parameters)[inferStart .. parameters.dim])) |
| { |
| // should copy prmtype to avoid affecting semantic result |
| prmtype = prmtype.syntaxCopy().typeSemantic(fd.loc, paramscope); |
| |
| if (prmtype.ty == Ttuple) |
| { |
| TypeTuple tt = cast(TypeTuple)prmtype; |
| size_t tt_dim = tt.arguments.dim; |
| for (size_t j = 0; j < tt_dim; j++, ++argi) |
| { |
| Parameter p = (*tt.arguments)[j]; |
| if (j == tt_dim - 1 && fparameters.varargs == VarArg.typesafe && |
| parami + 1 == nfparams && argi < nfargs) |
| { |
| prmtype = p.type; |
| goto Lvarargs; |
| } |
| if (argi >= nfargs) |
| { |
| if (p.defaultArg) |
| continue; |
| |
| // https://issues.dlang.org/show_bug.cgi?id=19888 |
| if (fparam.defaultArg) |
| break; |
| |
| return nomatch(); |
| } |
| farg = (*fargs)[argi]; |
| if (!farg.implicitConvTo(p.type)) |
| return nomatch(); |
| } |
| continue; |
| } |
| } |
| |
| if (argi >= nfargs) // if not enough arguments |
| { |
| if (!fparam.defaultArg) |
| goto Lvarargs; |
| |
| /* https://issues.dlang.org/show_bug.cgi?id=2803 |
| * Before the starting of type deduction from the function |
| * default arguments, set the already deduced parameters into paramscope. |
| * It's necessary to avoid breaking existing acceptable code. Cases: |
| * |
| * 1. Already deduced template parameters can appear in fparam.defaultArg: |
| * auto foo(A, B)(A a, B b = A.stringof); |
| * foo(1); |
| * // at fparam == 'B b = A.string', A is equivalent with the deduced type 'int' |
| * |
| * 2. If prmtype depends on default-specified template parameter, the |
| * default type should be preferred. |
| * auto foo(N = size_t, R)(R r, N start = 0) |
| * foo([1,2,3]); |
| * // at fparam `N start = 0`, N should be 'size_t' before |
| * // the deduction result from fparam.defaultArg. |
| */ |
| if (argi == nfargs) |
| { |
| foreach (ref dedtype; *dedtypes) |
| { |
| Type at = isType(dedtype); |
| if (at && at.ty == Tnone) |
| { |
| TypeDeduced xt = cast(TypeDeduced)at; |
| dedtype = xt.tded; // 'unbox' |
| } |
| } |
| for (size_t i = ntargs; i < dedargs.dim; i++) |
| { |
| TemplateParameter tparam = (*parameters)[i]; |
| |
| RootObject oarg = (*dedargs)[i]; |
| RootObject oded = (*dedtypes)[i]; |
| if (oarg) |
| continue; |
| |
| if (oded) |
| { |
| if (tparam.specialization() || !tparam.isTemplateTypeParameter()) |
| { |
| /* The specialization can work as long as afterwards |
| * the oded == oarg |
| */ |
| (*dedargs)[i] = oded; |
| MATCH m2 = tparam.matchArg(instLoc, paramscope, dedargs, i, parameters, dedtypes, null); |
| //printf("m2 = %d\n", m2); |
| if (m2 == MATCH.nomatch) |
| return nomatch(); |
| if (m2 < matchTiargs) |
| matchTiargs = m2; // pick worst match |
| if (!(*dedtypes)[i].equals(oded)) |
| error("specialization not allowed for deduced parameter `%s`", tparam.ident.toChars()); |
| } |
| else |
| { |
| if (MATCH.convert < matchTiargs) |
| matchTiargs = MATCH.convert; |
| } |
| (*dedargs)[i] = declareParameter(paramscope, tparam, oded); |
| } |
| else |
| { |
| inuse++; |
| oded = tparam.defaultArg(instLoc, paramscope); |
| inuse--; |
| if (oded) |
| (*dedargs)[i] = declareParameter(paramscope, tparam, oded); |
| } |
| } |
| } |
| nfargs2 = argi + 1; |
| |
| /* If prmtype does not depend on any template parameters: |
| * |
| * auto foo(T)(T v, double x = 0); |
| * foo("str"); |
| * // at fparam == 'double x = 0' |
| * |
| * or, if all template parameters in the prmtype are already deduced: |
| * |
| * auto foo(R)(R range, ElementType!R sum = 0); |
| * foo([1,2,3]); |
| * // at fparam == 'ElementType!R sum = 0' |
| * |
| * Deducing prmtype from fparam.defaultArg is not necessary. |
| */ |
| if (prmtype.deco || prmtype.syntaxCopy().trySemantic(loc, paramscope)) |
| { |
| ++argi; |
| continue; |
| } |
| |
| // Deduce prmtype from the defaultArg. |
| farg = fparam.defaultArg.syntaxCopy(); |
| farg = farg.expressionSemantic(paramscope); |
| farg = resolveProperties(paramscope, farg); |
| } |
| else |
| { |
| farg = (*fargs)[argi]; |
| } |
| { |
| // Check invalid arguments to detect errors early. |
| if (farg.op == EXP.error || farg.type.ty == Terror) |
| return nomatch(); |
| |
| Type att = null; |
| Lretry: |
| version (none) |
| { |
| printf("\tfarg.type = %s\n", farg.type.toChars()); |
| printf("\tfparam.type = %s\n", prmtype.toChars()); |
| } |
| Type argtype = farg.type; |
| |
| if (!fparam.isLazy() && argtype.ty == Tvoid && farg.op != EXP.function_) |
| return nomatch(); |
| |
| // https://issues.dlang.org/show_bug.cgi?id=12876 |
| // Optimize argument to allow CT-known length matching |
| farg = farg.optimize(WANTvalue, fparam.isReference()); |
| //printf("farg = %s %s\n", farg.type.toChars(), farg.toChars()); |
| |
| RootObject oarg = farg; |
| if ((fparam.storageClass & STC.ref_) && (!(fparam.storageClass & STC.auto_) || farg.isLvalue())) |
| { |
| /* Allow expressions that have CT-known boundaries and type [] to match with [dim] |
| */ |
| Type taai; |
| if (argtype.ty == Tarray && (prmtype.ty == Tsarray || prmtype.ty == Taarray && (taai = (cast(TypeAArray)prmtype).index).ty == Tident && (cast(TypeIdentifier)taai).idents.dim == 0)) |
| { |
| if (farg.op == EXP.string_) |
| { |
| StringExp se = cast(StringExp)farg; |
| argtype = se.type.nextOf().sarrayOf(se.len); |
| } |
| else if (farg.op == EXP.arrayLiteral) |
| { |
| ArrayLiteralExp ae = cast(ArrayLiteralExp)farg; |
| argtype = ae.type.nextOf().sarrayOf(ae.elements.dim); |
| } |
| else if (farg.op == EXP.slice) |
| { |
| SliceExp se = cast(SliceExp)farg; |
| if (Type tsa = toStaticArrayType(se)) |
| argtype = tsa; |
| } |
| } |
| |
| oarg = argtype; |
| } |
| else if ((fparam.storageClass & STC.out_) == 0 && (argtype.ty == Tarray || argtype.ty == Tpointer) && templateParameterLookup(prmtype, parameters) != IDX_NOTFOUND && (cast(TypeIdentifier)prmtype).idents.dim == 0) |
| { |
| /* The farg passing to the prmtype always make a copy. Therefore, |
| * we can shrink the set of the deduced type arguments for prmtype |
| * by adjusting top-qualifier of the argtype. |
| * |
| * prmtype argtype ta |
| * T <- const(E)[] const(E)[] |
| * T <- const(E[]) const(E)[] |
| * qualifier(T) <- const(E)[] const(E[]) |
| * qualifier(T) <- const(E[]) const(E[]) |
| */ |
| Type ta = argtype.castMod(prmtype.mod ? argtype.nextOf().mod : 0); |
| if (ta != argtype) |
| { |
| Expression ea = farg.copy(); |
| ea.type = ta; |
| oarg = ea; |
| } |
| } |
| |
| if (fparameters.varargs == VarArg.typesafe && parami + 1 == nfparams && argi + 1 < nfargs) |
| goto Lvarargs; |
| |
| uint wm = 0; |
| MATCH m = deduceType(oarg, paramscope, prmtype, parameters, dedtypes, &wm, inferStart); |
| //printf("\tL%d deduceType m = %d, wm = x%x, wildmatch = x%x\n", __LINE__, m, wm, wildmatch); |
| wildmatch |= wm; |
| |
| /* If no match, see if the argument can be matched by using |
| * implicit conversions. |
| */ |
| if (m == MATCH.nomatch && prmtype.deco) |
| m = farg.implicitConvTo(prmtype); |
| |
| if (m == MATCH.nomatch) |
| { |
| AggregateDeclaration ad = isAggregate(farg.type); |
| if (ad && ad.aliasthis && !isRecursiveAliasThis(att, argtype)) |
| { |
| // https://issues.dlang.org/show_bug.cgi?id=12537 |
| // The isRecursiveAliasThis() call above |
| |
| /* If a semantic error occurs while doing alias this, |
| * eg purity(https://issues.dlang.org/show_bug.cgi?id=7295), |
| * just regard it as not a match. |
| * |
| * We also save/restore sc.func.flags to avoid messing up |
| * attribute inference in the evaluation. |
| */ |
| const oldflags = sc.func ? sc.func.flags : 0; |
| auto e = resolveAliasThis(sc, farg, true); |
| if (sc.func) |
| sc.func.flags = oldflags; |
| if (e) |
| { |
| farg = e; |
| goto Lretry; |
| } |
| } |
| } |
| |
| if (m > MATCH.nomatch && (fparam.storageClass & (STC.ref_ | STC.auto_)) == STC.ref_) |
| { |
| if (!farg.isLvalue()) |
| { |
| if ((farg.op == EXP.string_ || farg.op == EXP.slice) && (prmtype.ty == Tsarray || prmtype.ty == Taarray)) |
| { |
| // Allow conversion from T[lwr .. upr] to ref T[upr-lwr] |
| } |
| else if (global.params.rvalueRefParam == FeatureState.enabled) |
| { |
| // Allow implicit conversion to ref |
| } |
| else |
| return nomatch(); |
| } |
| } |
| if (m > MATCH.nomatch && (fparam.storageClass & STC.out_)) |
| { |
| if (!farg.isLvalue()) |
| return nomatch(); |
| if (!farg.type.isMutable()) // https://issues.dlang.org/show_bug.cgi?id=11916 |
| return nomatch(); |
| } |
| if (m == MATCH.nomatch && fparam.isLazy() && prmtype.ty == Tvoid && farg.type.ty != Tvoid) |
| m = MATCH.convert; |
| if (m != MATCH.nomatch) |
| { |
| if (m < match) |
| match = m; // pick worst match |
| argi++; |
| continue; |
| } |
| } |
| |
| Lvarargs: |
| /* The following code for variadic arguments closely |
| * matches TypeFunction.callMatch() |
| */ |
| if (!(fparameters.varargs == VarArg.typesafe && parami + 1 == nfparams)) |
| return nomatch(); |
| |
| /* Check for match with function parameter T... |
| */ |
| Type tb = prmtype.toBasetype(); |
| switch (tb.ty) |
| { |
| // 6764 fix - TypeAArray may be TypeSArray have not yet run semantic(). |
| case Tsarray: |
| case Taarray: |
| { |
| // Perhaps we can do better with this, see TypeFunction.callMatch() |
| if (tb.ty == Tsarray) |
| { |
| TypeSArray tsa = cast(TypeSArray)tb; |
| dinteger_t sz = tsa.dim.toInteger(); |
| if (sz != nfargs - argi) |
| return nomatch(); |
| } |
| else if (tb.ty == Taarray) |
| { |
| TypeAArray taa = cast(TypeAArray)tb; |
| Expression dim = new IntegerExp(instLoc, nfargs - argi, Type.tsize_t); |
| |
| size_t i = templateParameterLookup(taa.index, parameters); |
| if (i == IDX_NOTFOUND) |
| { |
| Expression e; |
| Type t; |
| Dsymbol s; |
| Scope *sco; |
| |
| uint errors = global.startGagging(); |
| /* ref: https://issues.dlang.org/show_bug.cgi?id=11118 |
| * The parameter isn't part of the template |
| * ones, let's try to find it in the |
| * instantiation scope 'sc' and the one |
| * belonging to the template itself. */ |
| sco = sc; |
| taa.index.resolve(instLoc, sco, e, t, s); |
| if (!e) |
| { |
| sco = paramscope; |
| taa.index.resolve(instLoc, sco, e, t, s); |
| } |
| global.endGagging(errors); |
| |
| if (!e) |
| return nomatch(); |
| |
| e = e.ctfeInterpret(); |
| e = e.implicitCastTo(sco, Type.tsize_t); |
| e = e.optimize(WANTvalue); |
| if (!dim.equals(e)) |
| return nomatch(); |
| } |
| else |
| { |
| // This code matches code in TypeInstance.deduceType() |
| TemplateParameter tprm = (*parameters)[i]; |
| TemplateValueParameter tvp = tprm.isTemplateValueParameter(); |
| if (!tvp) |
| return nomatch(); |
| Expression e = cast(Expression)(*dedtypes)[i]; |
| if (e) |
| { |
| if (!dim.equals(e)) |
| return nomatch(); |
| } |
| else |
| { |
| Type vt = tvp.valType.typeSemantic(Loc.initial, sc); |
| MATCH m = dim.implicitConvTo(vt); |
| if (m == MATCH.nomatch) |
| return nomatch(); |
| (*dedtypes)[i] = dim; |
| } |
| } |
| } |
| goto case Tarray; |
| } |
| case Tarray: |
| { |
| TypeArray ta = cast(TypeArray)tb; |
| Type tret = fparam.isLazyArray(); |
| for (; argi < nfargs; argi++) |
| { |
| Expression arg = (*fargs)[argi]; |
| assert(arg); |
| |
| MATCH m; |
| /* If lazy array of delegates, |
| * convert arg(s) to delegate(s) |
| */ |
| if (tret) |
| { |
| if (ta.next.equals(arg.type)) |
| { |
| m = MATCH.exact; |
| } |
| else |
| { |
| m = arg.implicitConvTo(tret); |
| if (m == MATCH.nomatch) |
| { |
| if (tret.toBasetype().ty == Tvoid) |
| m = MATCH.convert; |
| } |
| } |
| } |
| else |
| { |
| uint wm = 0; |
| m = deduceType(arg, paramscope, ta.next, parameters, dedtypes, &wm, inferStart); |
| wildmatch |= wm; |
| } |
| if (m == MATCH.nomatch) |
| return nomatch(); |
| if (m < match) |
| match = m; |
| } |
| goto Lmatch; |
| } |
| case Tclass: |
| case Tident: |
| goto Lmatch; |
| |
| default: |
| return nomatch(); |
| } |
| assert(0); |
| } |
| //printf(". argi = %d, nfargs = %d, nfargs2 = %d\n", argi, nfargs, nfargs2); |
| if (argi != nfargs2 && fparameters.varargs == VarArg.none) |
| return nomatch(); |
| } |
| |
| Lmatch: |
| foreach (ref dedtype; *dedtypes) |
| { |
| Type at = isType(dedtype); |
| if (at) |
| { |
| if (at.ty == Tnone) |
| { |
| TypeDeduced xt = cast(TypeDeduced)at; |
| at = xt.tded; // 'unbox' |
| } |
| dedtype = at.merge2(); |
| } |
| } |
| for (size_t i = ntargs; i < dedargs.dim; i++) |
| { |
| TemplateParameter tparam = (*parameters)[i]; |
| //printf("tparam[%d] = %s\n", i, tparam.ident.toChars()); |
| |
| /* For T:T*, the dedargs is the T*, dedtypes is the T |
| * But for function templates, we really need them to match |
| */ |
| RootObject oarg = (*dedargs)[i]; |
| RootObject oded = (*dedtypes)[i]; |
| //printf("1dedargs[%d] = %p, dedtypes[%d] = %p\n", i, oarg, i, oded); |
| //if (oarg) printf("oarg: %s\n", oarg.toChars()); |
| //if (oded) printf("oded: %s\n", oded.toChars()); |
| if (oarg) |
| continue; |
| |
| if (oded) |
| { |
| if (tparam.specialization() || !tparam.isTemplateTypeParameter()) |
| { |
| /* The specialization can work as long as afterwards |
| * the oded == oarg |
| */ |
| (*dedargs)[i] = oded; |
| MATCH m2 = tparam.matchArg(instLoc, paramscope, dedargs, i, parameters, dedtypes, null); |
| //printf("m2 = %d\n", m2); |
| if (m2 == MATCH.nomatch) |
| return nomatch(); |
| if (m2 < matchTiargs) |
| matchTiargs = m2; // pick worst match |
| if (!(*dedtypes)[i].equals(oded)) |
| error("specialization not allowed for deduced parameter `%s`", tparam.ident.toChars()); |
| } |
| else |
| { |
| // Discussion: https://issues.dlang.org/show_bug.cgi?id=16484 |
| if (MATCH.convert < matchTiargs) |
| matchTiargs = MATCH.convert; |
| } |
| } |
| else |
| { |
| inuse++; |
| oded = tparam.defaultArg(instLoc, paramscope); |
| inuse--; |
| if (!oded) |
| { |
| // if tuple parameter and |
| // tuple parameter was not in function parameter list and |
| // we're one or more arguments short (i.e. no tuple argument) |
| if (tparam == tp && |
| fptupindex == IDX_NOTFOUND && |
| ntargs <= dedargs.dim - 1) |
| { |
| // make tuple argument an empty tuple |
| oded = new Tuple(); |
| } |
| else |
| return nomatch(); |
| } |
| if (isError(oded)) |
| return matcherror(); |
| ntargs++; |
| |
| /* At the template parameter T, the picked default template argument |
| * X!int should be matched to T in order to deduce dependent |
| * template parameter A. |
| * auto foo(T : X!A = X!int, A...)() { ... } |
| * foo(); // T <-- X!int, A <-- (int) |
| */ |
| if (tparam.specialization()) |
| { |
| (*dedargs)[i] = oded; |
| MATCH m2 = tparam.matchArg(instLoc, paramscope, dedargs, i, parameters, dedtypes, null); |
| //printf("m2 = %d\n", m2); |
| if (m2 == MATCH.nomatch) |
| return nomatch(); |
| if (m2 < matchTiargs) |
| matchTiargs = m2; // pick worst match |
| if (!(*dedtypes)[i].equals(oded)) |
| error("specialization not allowed for deduced parameter `%s`", tparam.ident.toChars()); |
| } |
| } |
| oded = declareParameter(paramscope, tparam, oded); |
| (*dedargs)[i] = oded; |
| } |
| |
| /* https://issues.dlang.org/show_bug.cgi?id=7469 |
| * As same as the code for 7469 in findBestMatch, |
| * expand a Tuple in dedargs to normalize template arguments. |
| */ |
| if (auto d = dedargs.dim) |
| { |
| if (auto va = isTuple((*dedargs)[d - 1])) |
| { |
| dedargs.setDim(d - 1); |
| dedargs.insert(d - 1, &va.objects); |
| } |
| } |
| ti.tiargs = dedargs; // update to the normalized template arguments. |
| |
| // Partially instantiate function for constraint and fd.leastAsSpecialized() |
| { |
| assert(paramscope.scopesym); |
| Scope* sc2 = _scope; |
| sc2 = sc2.push(paramscope.scopesym); |
| sc2 = sc2.push(ti); |
| sc2.parent = ti; |
| sc2.tinst = ti; |
| sc2.minst = sc.minst; |
| sc2.stc |= fd.storage_class & STC.deprecated_; |
| |
| fd = doHeaderInstantiation(ti, sc2, fd, tthis, fargs); |
| |
| sc2 = sc2.pop(); |
| sc2 = sc2.pop(); |
| |
| if (!fd) |
| return nomatch(); |
| } |
| |
| if (constraint) |
| { |
| if (!evaluateConstraint(ti, sc, paramscope, dedargs, fd)) |
| return nomatch(); |
| } |
| |
| version (none) |
| { |
| for (size_t i = 0; i < dedargs.dim; i++) |
| { |
| RootObject o = (*dedargs)[i]; |
| printf("\tdedargs[%d] = %d, %s\n", i, o.dyncast(), o.toChars()); |
| } |
| } |
| |
| paramscope.pop(); |
| //printf("\tmatch %d\n", match); |
| return MATCHpair(matchTiargs, match); |
| } |
| |
| /************************************************** |
| * Declare template parameter tp with value o, and install it in the scope sc. |
| */ |
| RootObject declareParameter(Scope* sc, TemplateParameter tp, RootObject o) |
| { |
| //printf("TemplateDeclaration.declareParameter('%s', o = %p)\n", tp.ident.toChars(), o); |
| Type ta = isType(o); |
| Expression ea = isExpression(o); |
| Dsymbol sa = isDsymbol(o); |
| Tuple va = isTuple(o); |
| |
| Declaration d; |
| VarDeclaration v = null; |
| |
| if (ea && ea.op == EXP.type) |
| ta = ea.type; |
| else if (ea && ea.op == EXP.scope_) |
| sa = (cast(ScopeExp)ea).sds; |
| else if (ea && (ea.op == EXP.this_ || ea.op == EXP.super_)) |
| sa = (cast(ThisExp)ea).var; |
| else if (ea && ea.op == EXP.function_) |
| { |
| if ((cast(FuncExp)ea).td) |
| sa = (cast(FuncExp)ea).td; |
| else |
| sa = (cast(FuncExp)ea).fd; |
| } |
| |
| if (ta) |
| { |
| //printf("type %s\n", ta.toChars()); |
| auto ad = new AliasDeclaration(Loc.initial, tp.ident, ta); |
| ad.storage_class |= STC.templateparameter; |
| d = ad; |
| } |
| else if (sa) |
| { |
| //printf("Alias %s %s;\n", sa.ident.toChars(), tp.ident.toChars()); |
| auto ad = new AliasDeclaration(Loc.initial, tp.ident, sa); |
| ad.storage_class |= STC.templateparameter; |
| d = ad; |
| } |
| else if (ea) |
| { |
| // tdtypes.data[i] always matches ea here |
| Initializer _init = new ExpInitializer(loc, ea); |
| TemplateValueParameter tvp = tp.isTemplateValueParameter(); |
| Type t = tvp ? tvp.valType : null; |
| v = new VarDeclaration(loc, t, tp.ident, _init); |
| v.storage_class = STC.manifest | STC.templateparameter; |
| d = v; |
| } |
| else if (va) |
| { |
| //printf("\ttuple\n"); |
| d = new TupleDeclaration(loc, tp.ident, &va.objects); |
| } |
| else |
| { |
| assert(0); |
| } |
| d.storage_class |= STC.templateparameter; |
| |
| if (ta) |
| { |
| Type t = ta; |
| // consistent with Type.checkDeprecated() |
| while (t.ty != Tenum) |
| { |
| if (!t.nextOf()) |
| break; |
| t = (cast(TypeNext)t).next; |
| } |
| if (Dsymbol s = t.toDsymbol(sc)) |
| { |
| if (s.isDeprecated()) |
| d.storage_class |= STC.deprecated_; |
| } |
| } |
| else if (sa) |
| { |
| if (sa.isDeprecated()) |
| d.storage_class |= STC.deprecated_; |
| } |
| |
| if (!sc.insert(d)) |
| error("declaration `%s` is already defined", tp.ident.toChars()); |
| d.dsymbolSemantic(sc); |
| /* So the caller's o gets updated with the result of semantic() being run on o |
| */ |
| if (v) |
| o = v._init.initializerToExpression(); |
| return o; |
| } |
| |
| /************************************************* |
| * Limited function template instantiation for using fd.leastAsSpecialized() |
| */ |
| extern (D) FuncDeclaration doHeaderInstantiation(TemplateInstance ti, Scope* sc2, FuncDeclaration fd, Type tthis, Expressions* fargs) |
| { |
| assert(fd); |
| version (none) |
| { |
| printf("doHeaderInstantiation this = %s\n", toChars()); |
| } |
| |
| // function body and contracts are not need |
| if (fd.isCtorDeclaration()) |
| fd = new CtorDeclaration(fd.loc, fd.endloc, fd.storage_class, fd.type.syntaxCopy()); |
| else |
| fd = new FuncDeclaration(fd.loc, fd.endloc, fd.ident, fd.storage_class, fd.type.syntaxCopy()); |
| fd.parent = ti; |
| |
| assert(fd.type.ty == Tfunction); |
| auto tf = fd.type.isTypeFunction(); |
| tf.fargs = fargs; |
| |
| if (tthis) |
| { |
| // Match 'tthis' to any TemplateThisParameter's |
| bool hasttp = false; |
| foreach (tp; *parameters) |
| { |
| TemplateThisParameter ttp = tp.isTemplateThisParameter(); |
| if (ttp) |
| hasttp = true; |
| } |
| if (hasttp) |
| { |
| tf = cast(TypeFunction)tf.addSTC(ModToStc(tthis.mod)); |
| assert(!tf.deco); |
| } |
| } |
| |
| Scope* scx = sc2.push(); |
| |
| // Shouldn't run semantic on default arguments and return type. |
| foreach (ref params; *tf.parameterList.parameters) |
| params.defaultArg = null; |
| tf.incomplete = true; |
| |
| if (fd.isCtorDeclaration()) |
| { |
| // For constructors, emitting return type is necessary for |
| // isReturnIsolated() in functionResolve. |
| tf.isctor = true; |
| |
| Dsymbol parent = toParentDecl(); |
| Type tret; |
| AggregateDeclaration ad = parent.isAggregateDeclaration(); |
| if (!ad || parent.isUnionDeclaration()) |
| { |
| tret = Type.tvoid; |
| } |
| else |
| { |
| tret = ad.handleType(); |
| assert(tret); |
| tret = tret.addStorageClass(fd.storage_class | scx.stc); |
| tret = tret.addMod(tf.mod); |
| } |
| tf.next = tret; |
| if (ad && ad.isStructDeclaration()) |
| tf.isref = 1; |
| //printf("tf = %s\n", tf.toChars()); |
| } |
| else |
| tf.next = null; |
| fd.type = tf; |
| fd.type = fd.type.addSTC(scx.stc); |
| fd.type = fd.type.typeSemantic(fd.loc, scx); |
| scx = scx.pop(); |
| |
| if (fd.type.ty != Tfunction) |
| return null; |
| |
| fd.originalType = fd.type; // for mangling |
| //printf("\t[%s] fd.type = %s, mod = %x, ", loc.toChars(), fd.type.toChars(), fd.type.mod); |
| //printf("fd.needThis() = %d\n", fd.needThis()); |
| |
| return fd; |
| } |
| |
| debug (FindExistingInstance) |
| { |
| __gshared uint nFound, nNotFound, nAdded, nRemoved; |
| |
| shared static ~this() |
| { |
| printf("debug (FindExistingInstance) nFound %u, nNotFound: %u, nAdded: %u, nRemoved: %u\n", |
| nFound, nNotFound, nAdded, nRemoved); |
| } |
| } |
| |
| /**************************************************** |
| * Given a new instance tithis of this TemplateDeclaration, |
| * see if there already exists an instance. |
| * If so, return that existing instance. |
| */ |
| extern (D) TemplateInstance findExistingInstance(TemplateInstance tithis, Expressions* fargs) |
| { |
| //printf("findExistingInstance() %s\n", tithis.toChars()); |
| tithis.fargs = fargs; |
| auto tibox = TemplateInstanceBox(tithis); |
| auto p = tibox in instances; |
| debug (FindExistingInstance) ++(p ? nFound : nNotFound); |
| //if (p) printf("\tfound %p\n", *p); else printf("\tnot found\n"); |
| return p ? *p : null; |
| } |
| |
| /******************************************** |
| * Add instance ti to TemplateDeclaration's table of instances. |
| * Return a handle we can use to later remove it if it fails instantiation. |
| */ |
| extern (D) TemplateInstance addInstance(TemplateInstance ti) |
| { |
| //printf("addInstance() %p %s\n", instances, ti.toChars()); |
| auto tibox = TemplateInstanceBox(ti); |
| instances[tibox] = ti; |
| debug (FindExistingInstance) ++nAdded; |
| return ti; |
| } |
| |
| /******************************************* |
| * Remove TemplateInstance from table of instances. |
| * Input: |
| * handle returned by addInstance() |
| */ |
| extern (D) void removeInstance(TemplateInstance ti) |
| { |
| //printf("removeInstance() %s\n", ti.toChars()); |
| auto tibox = TemplateInstanceBox(ti); |
| debug (FindExistingInstance) ++nRemoved; |
| instances.remove(tibox); |
| } |
| |
| override inout(TemplateDeclaration) isTemplateDeclaration() inout |
| { |
| return this; |
| } |
| |
| /** |
| * Check if the last template parameter is a tuple one, |
| * and returns it if so, else returns `null`. |
| * |
| * Returns: |
| * The last template parameter if it's a `TemplateTupleParameter` |
| */ |
| TemplateTupleParameter isVariadic() |
| { |
| size_t dim = parameters.dim; |
| if (dim == 0) |
| return null; |
| return (*parameters)[dim - 1].isTemplateTupleParameter(); |
| } |
| |
| extern(C++) override bool isDeprecated() const |
| { |
| return this.deprecated_; |
| } |
| |
| /*********************************** |
| * We can overload templates. |
| */ |
| override bool isOverloadable() const |
| { |
| return true; |
| } |
| |
| override void accept(Visitor v) |
| { |
| v.visit(this); |
| } |
| } |
| |
| extern (C++) final class TypeDeduced : Type |
| { |
| Type tded; |
| Expressions argexps; // corresponding expressions |
| Types tparams; // tparams[i].mod |
| |
| extern (D) this(Type tt, Expression e, Type tparam) |
| { |
| super(Tnone); |
| tded = tt; |
| argexps.push(e); |
| tparams.push(tparam); |
| } |
| |
| void update(Expression e, Type tparam) |
| { |
| argexps.push(e); |
| tparams.push(tparam); |
| } |
| |
| void update(Type tt, Expression e, Type tparam) |
| { |
| tded = tt; |
| argexps.push(e); |
| tparams.push(tparam); |
| } |
| |
| MATCH matchAll(Type tt) |
| { |
| MATCH match = MATCH.exact; |
| foreach (j, e; argexps) |
| { |
| assert(e); |
| if (e == emptyArrayElement) |
| continue; |
| |
| Type t = tt.addMod(tparams[j].mod).substWildTo(MODFlags.const_); |
| |
| MATCH m = e.implicitConvTo(t); |
| if (match > m) |
| match = m; |
| if (match == MATCH.nomatch) |
| break; |
| } |
| return match; |
| } |
| } |
| |
| |
| /************************************************* |
| * Given function arguments, figure out which template function |
| * to expand, and return matching result. |
| * Params: |
| * m = matching result |
| * dstart = the root of overloaded function templates |
| * loc = instantiation location |
| * sc = instantiation scope |
| * tiargs = initial list of template arguments |
| * tthis = if !NULL, the 'this' pointer argument |
| * fargs = arguments to function |
| * pMessage = address to store error message, or null |
| */ |
| void functionResolve(ref MatchAccumulator m, Dsymbol dstart, Loc loc, Scope* sc, Objects* tiargs, |
| Type tthis, Expressions* fargs, const(char)** pMessage = null) |
| { |
| Expression[] fargs_ = fargs.peekSlice(); |
| version (none) |
| { |
| printf("functionResolve() dstart = %s\n", dstart.toChars()); |
| printf(" tiargs:\n"); |
| if (tiargs) |
| { |
| for (size_t i = 0; i < tiargs.dim; i++) |
| { |
| RootObject arg = (*tiargs)[i]; |
| printf("\t%s\n", arg.toChars()); |
| } |
| } |
| printf(" fargs:\n"); |
| for (size_t i = 0; i < (fargs ? fargs.dim : 0); i++) |
| { |
| Expression arg = (*fargs)[i]; |
| printf("\t%s %s\n", arg.type.toChars(), arg.toChars()); |
| //printf("\tty = %d\n", arg.type.ty); |
| } |
| //printf("stc = %llx\n", dstart._scope.stc); |
| //printf("match:t/f = %d/%d\n", ta_last, m.last); |
| } |
| |
| // results |
| int property = 0; // 0: uninitialized |
| // 1: seen @property |
| // 2: not @property |
| size_t ov_index = 0; |
| TemplateDeclaration td_best; |
| TemplateInstance ti_best; |
| MATCH ta_last = m.last != MATCH.nomatch ? MATCH.exact : MATCH.nomatch; |
| Type tthis_best; |
| |
| int applyFunction(FuncDeclaration fd) |
| { |
| // skip duplicates |
| if (fd == m.lastf) |
| return 0; |
| // explicitly specified tiargs never match to non template function |
| if (tiargs && tiargs.dim > 0) |
| return 0; |
| |
| // constructors need a valid scope in order to detect semantic errors |
| if (!fd.isCtorDeclaration && |
| fd.semanticRun < PASS.semanticdone) |
| { |
| Ungag ungag = fd.ungagSpeculative(); |
| fd.dsymbolSemantic(null); |
| } |
| if (fd.semanticRun < PASS.semanticdone) |
| { |
| .error(loc, "forward reference to template `%s`", fd.toChars()); |
| return 1; |
| } |
| //printf("fd = %s %s, fargs = %s\n", fd.toChars(), fd.type.toChars(), fargs.toChars()); |
| auto tf = cast(TypeFunction)fd.type; |
| |
| int prop = tf.isproperty ? 1 : 2; |
| if (property == 0) |
| property = prop; |
| else if (property != prop) |
| error(fd.loc, "cannot overload both property and non-property functions"); |
| |
| /* For constructors, qualifier check will be opposite direction. |
| * Qualified constructor always makes qualified object, then will be checked |
| * that it is implicitly convertible to tthis. |
| */ |
| Type tthis_fd = fd.needThis() ? tthis : null; |
| bool isCtorCall = tthis_fd && fd.isCtorDeclaration(); |
| if (isCtorCall) |
| { |
| //printf("%s tf.mod = x%x tthis_fd.mod = x%x %d\n", tf.toChars(), |
| // tf.mod, tthis_fd.mod, fd.isReturnIsolated()); |
| if (MODimplicitConv(tf.mod, tthis_fd.mod) || |
| tf.isWild() && tf.isShared() == tthis_fd.isShared() || |
| fd.isReturnIsolated()) |
| { |
| /* && tf.isShared() == tthis_fd.isShared()*/ |
| // Uniquely constructed object can ignore shared qualifier. |
| // TODO: Is this appropriate? |
| tthis_fd = null; |
| } |
| else |
| return 0; // MATCH.nomatch |
| } |
| /* Fix Issue 17970: |
| If a struct is declared as shared the dtor is automatically |
| considered to be shared, but when the struct is instantiated |
| the instance is no longer considered to be shared when the |
| function call matching is done. The fix makes it so that if a |
| struct declaration is shared, when the destructor is called, |
| the instantiated struct is also considered shared. |
| */ |
| if (auto dt = fd.isDtorDeclaration()) |
| { |
| auto dtmod = dt.type.toTypeFunction(); |
| auto shared_dtor = dtmod.mod & MODFlags.shared_; |
| auto shared_this = tthis_fd !is null ? |
| tthis_fd.mod & MODFlags.shared_ : 0; |
| if (shared_dtor && !shared_this) |
| tthis_fd = dtmod; |
| else if (shared_this && !shared_dtor && tthis_fd !is null) |
| tf.mod = tthis_fd.mod; |
| } |
| MATCH mfa = tf.callMatch(tthis_fd, fargs_, 0, pMessage, sc); |
| //printf("test1: mfa = %d\n", mfa); |
| if (mfa == MATCH.nomatch) |
| return 0; |
| |
| int firstIsBetter() |
| { |
| td_best = null; |
| ti_best = null; |
| ta_last = MATCH.exact; |
| m.last = mfa; |
| m.lastf = fd; |
| tthis_best = tthis_fd; |
| ov_index = 0; |
| m.count = 1; |
| return 0; |
| } |
| |
| if (mfa > m.last) return firstIsBetter(); |
| if (mfa < m.last) return 0; |
| |
| /* See if one of the matches overrides the other. |
| */ |
| assert(m.lastf); |
| if (m.lastf.overrides(fd)) return 0; |
| if (fd.overrides(m.lastf)) return firstIsBetter(); |
| |
| /* Try to disambiguate using template-style partial ordering rules. |
| * In essence, if f() and g() are ambiguous, if f() can call g(), |
| * but g() cannot call f(), then pick f(). |
| * This is because f() is "more specialized." |
| */ |
| { |
| MATCH c1 = fd.leastAsSpecialized(m.lastf); |
| MATCH c2 = m.lastf.leastAsSpecialized(fd); |
| //printf("c1 = %d, c2 = %d\n", c1, c2); |
| if (c1 > c2) return firstIsBetter(); |
| if (c1 < c2) return 0; |
| } |
| |
| /* The 'overrides' check above does covariant checking only |
| * for virtual member functions. It should do it for all functions, |
| * but in order to not risk breaking code we put it after |
| * the 'leastAsSpecialized' check. |
| * In the future try moving it before. |
| * I.e. a not-the-same-but-covariant match is preferred, |
| * as it is more restrictive. |
| */ |
| if (!m.lastf.type.equals(fd.type)) |
| { |
| //printf("cov: %d %d\n", m.lastf.type.covariant(fd.type), fd.type.covariant(m.lastf.type)); |
| const lastCovariant = m.lastf.type.covariant(fd.type); |
| const firstCovariant = fd.type.covariant(m.lastf.type); |
| |
| if (lastCovariant == Covariant.yes || lastCovariant == Covariant.no) |
| { |
| if (firstCovariant != Covariant.yes && firstCovariant != Covariant.no) |
| { |
| return 0; |
| } |
| } |
| else if (firstCovariant == Covariant.yes || firstCovariant == Covariant.no) |
| { |
| return firstIsBetter(); |
| } |
| } |
| |
| /* If the two functions are the same function, like: |
| * int foo(int); |
| * int foo(int x) { ... } |
| * then pick the one with the body. |
| * |
| * If none has a body then don't care because the same |
| * real function would be linked to the decl (e.g from object file) |
| */ |
| if (tf.equals(m.lastf.type) && |
| fd.storage_class == m.lastf.storage_class && |
| fd.parent == m.lastf.parent && |
| fd.visibility == m.lastf.visibility && |
| fd._linkage == m.lastf._linkage) |
| { |
| if (fd.fbody && !m.lastf.fbody) |
| return firstIsBetter(); |
| if (!fd.fbody) |
| return 0; |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=14450 |
| // Prefer exact qualified constructor for the creating object type |
| if (isCtorCall && tf.mod != m.lastf.type.mod) |
| { |
| if (tthis.mod == tf.mod) return firstIsBetter(); |
| if (tthis.mod == m.lastf.type.mod) return 0; |
| } |
| |
| m.nextf = fd; |
| m.count++; |
| return 0; |
| } |
| |
| int applyTemplate(TemplateDeclaration td) |
| { |
| //printf("applyTemplate()\n"); |
| if (td.inuse) |
| { |
| td.error(loc, "recursive template expansion"); |
| return 1; |
| } |
| if (td == td_best) // skip duplicates |
| return 0; |
| |
| if (!sc) |
| sc = td._scope; // workaround for Type.aliasthisOf |
| |
| if (td.semanticRun == PASS.initial && td._scope) |
| { |
| // Try to fix forward reference. Ungag errors while doing so. |
| Ungag ungag = td.ungagSpeculative(); |
| td.dsymbolSemantic(td._scope); |
| } |
| if (td.semanticRun == PASS.initial) |
| { |
| .error(loc, "forward reference to template `%s`", td.toChars()); |
| Lerror: |
| m.lastf = null; |
| m.count = 0; |
| m.last = MATCH.nomatch; |
| return 1; |
| } |
| //printf("td = %s\n", td.toChars()); |
| |
| auto f = td.onemember ? td.onemember.isFuncDeclaration() : null; |
| if (!f) |
| { |
| if (!tiargs) |
| tiargs = new Objects(); |
| auto ti = new TemplateInstance(loc, td, tiargs); |
| Objects dedtypes = Objects(td.parameters.dim); |
| assert(td.semanticRun != PASS.initial); |
| MATCH mta = td.matchWithInstance(sc, ti, &dedtypes, fargs, 0); |
| //printf("matchWithInstance = %d\n", mta); |
| if (mta == MATCH.nomatch || mta < ta_last) // no match or less match |
| return 0; |
| |
| ti.templateInstanceSemantic(sc, fargs); |
| if (!ti.inst) // if template failed to expand |
| return 0; |
| |
| Dsymbol s = ti.inst.toAlias(); |
| FuncDeclaration fd; |
| if (auto tdx = s.isTemplateDeclaration()) |
| { |
| Objects dedtypesX; // empty tiargs |
| |
| // https://issues.dlang.org/show_bug.cgi?id=11553 |
| // Check for recursive instantiation of tdx. |
| for (TemplatePrevious* p = tdx.previous; p; p = p.prev) |
| { |
| if (arrayObjectMatch(p.dedargs, &dedtypesX)) |
| { |
| //printf("recursive, no match p.sc=%p %p %s\n", p.sc, this, this.toChars()); |
| /* It must be a subscope of p.sc, other scope chains are not recursive |
| * instantiations. |
| */ |
| for (Scope* scx = sc; scx; scx = scx.enclosing) |
| { |
| if (scx == p.sc) |
| { |
| error(loc, "recursive template expansion while looking for `%s.%s`", ti.toChars(), tdx.toChars()); |
| goto Lerror; |
| } |
| } |
| } |
| /* BUG: should also check for ref param differences |
| */ |
| } |
| |
| TemplatePrevious pr; |
| pr.prev = tdx.previous; |
| pr.sc = sc; |
| pr.dedargs = &dedtypesX; |
| tdx.previous = ≺ // add this to threaded list |
| |
| fd = resolveFuncCall(loc, sc, s, null, tthis, fargs, FuncResolveFlag.quiet); |
| |
| tdx.previous = pr.prev; // unlink from threaded list |
| } |
| else if (s.isFuncDeclaration()) |
| { |
| fd = resolveFuncCall(loc, sc, s, null, tthis, fargs, FuncResolveFlag.quiet); |
| } |
| else |
| goto Lerror; |
| |
| if (!fd) |
| return 0; |
| |
| if (fd.type.ty != Tfunction) |
| { |
| m.lastf = fd; // to propagate "error match" |
| m.count = 1; |
| m.last = MATCH.nomatch; |
| return 1; |
| } |
| |
| Type tthis_fd = fd.needThis() && !fd.isCtorDeclaration() ? tthis : null; |
| |
| auto tf = cast(TypeFunction)fd.type; |
| MATCH mfa = tf.callMatch(tthis_fd, fargs_, 0, null, sc); |
| if (mfa < m.last) |
| return 0; |
| |
| if (mta < ta_last) goto Ltd_best2; |
| if (mta > ta_last) goto Ltd2; |
| |
| if (mfa < m.last) goto Ltd_best2; |
| if (mfa > m.last) goto Ltd2; |
| |
| // td_best and td are ambiguous |
| //printf("Lambig2\n"); |
| m.nextf = fd; |
| m.count++; |
| return 0; |
| |
| Ltd_best2: |
| return 0; |
| |
| Ltd2: |
| // td is the new best match |
| assert(td._scope); |
| td_best = td; |
| ti_best = null; |
| property = 0; // (backward compatibility) |
| |