/* Compiler implementation of the D programming language * Copyright (C) 1999-2019 by The D Language Foundation, All Rights Reserved * written by Walter Bright * http://www.digitalmars.com * Distributed under the Boost Software License, Version 1.0. * http://www.boost.org/LICENSE_1_0.txt * https://github.com/D-Programming-Language/dmd/blob/master/src/opover.c */ #include "root/dsystem.h" // memset() #include "root/rmem.h" #include "mars.h" #include "mtype.h" #include "init.h" #include "expression.h" #include "statement.h" #include "scope.h" #include "id.h" #include "declaration.h" #include "aggregate.h" #include "template.h" #include "tokens.h" static Dsymbol *inferApplyArgTypesX(Expression *ethis, FuncDeclaration *fstart, Parameters *parameters); static int inferApplyArgTypesY(TypeFunction *tf, Parameters *parameters, int flags = 0); Expression *compare_overload(BinExp *e, Scope *sc, Identifier *id); bool MODimplicitConv(MOD modfrom, MOD modto); Expression *trySemantic(Expression *e, Scope *sc); Expression *binSemanticProp(BinExp *e, Scope *sc); Expression *semantic(Expression *e, Scope *sc); /******************************** Expression **************************/ /*********************************** * Determine if operands of binary op can be reversed * to fit operator overload. */ bool isCommutative(TOK op) { switch (op) { case TOKadd: case TOKmul: case TOKand: case TOKor: case TOKxor: // EqualExp case TOKequal: case TOKnotequal: // CmpExp case TOKlt: case TOKle: case TOKgt: case TOKge: return true; default: break; } return false; } /*********************************** * Get Identifier for operator overload. */ static Identifier *opId(Expression *e) { class OpIdVisitor : public Visitor { public: Identifier *id; void visit(Expression *) { assert(0); } void visit(UAddExp *) { id = Id::uadd; } void visit(NegExp *) { id = Id::neg; } void visit(ComExp *) { id = Id::com; } void visit(CastExp *) { id = Id::_cast; } void visit(InExp *) { id = Id::opIn; } void visit(PostExp *e) { id = (e->op == TOKplusplus) ? Id::postinc : Id::postdec; } void visit(AddExp *) { id = Id::add; } void visit(MinExp *) { id = Id::sub; } void visit(MulExp *) { id = Id::mul; } void visit(DivExp *) { id = Id::div; } void visit(ModExp *) { id = Id::mod; } void visit(PowExp *) { id = Id::pow; } void visit(ShlExp *) { id = Id::shl; } void visit(ShrExp *) { id = Id::shr; } void visit(UshrExp *) { id = Id::ushr; } void visit(AndExp *) { id = Id::iand; } void visit(OrExp *) { id = Id::ior; } void visit(XorExp *) { id = Id::ixor; } void visit(CatExp *) { id = Id::cat; } void visit(AssignExp *) { id = Id::assign; } void visit(AddAssignExp *) { id = Id::addass; } void visit(MinAssignExp *) { id = Id::subass; } void visit(MulAssignExp *) { id = Id::mulass; } void visit(DivAssignExp *) { id = Id::divass; } void visit(ModAssignExp *) { id = Id::modass; } void visit(AndAssignExp *) { id = Id::andass; } void visit(OrAssignExp *) { id = Id::orass; } void visit(XorAssignExp *) { id = Id::xorass; } void visit(ShlAssignExp *) { id = Id::shlass; } void visit(ShrAssignExp *) { id = Id::shrass; } void visit(UshrAssignExp *) { id = Id::ushrass; } void visit(CatAssignExp *) { id = Id::catass; } void visit(PowAssignExp *) { id = Id::powass; } void visit(EqualExp *) { id = Id::eq; } void visit(CmpExp *) { id = Id::cmp; } void visit(ArrayExp *) { id = Id::index; } void visit(PtrExp *) { id = Id::opStar; } }; OpIdVisitor v; e->accept(&v); return v.id; } /*********************************** * Get Identifier for reverse operator overload, * NULL if not supported for this operator. */ static Identifier *opId_r(Expression *e) { class OpIdRVisitor : public Visitor { public: Identifier *id; void visit(Expression *) { id = NULL; } void visit(InExp *) { id = Id::opIn_r; } void visit(AddExp *) { id = Id::add_r; } void visit(MinExp *) { id = Id::sub_r; } void visit(MulExp *) { id = Id::mul_r; } void visit(DivExp *) { id = Id::div_r; } void visit(ModExp *) { id = Id::mod_r; } void visit(PowExp *) { id = Id::pow_r; } void visit(ShlExp *) { id = Id::shl_r; } void visit(ShrExp *) { id = Id::shr_r; } void visit(UshrExp *) { id = Id::ushr_r; } void visit(AndExp *) { id = Id::iand_r; } void visit(OrExp *) { id = Id::ior_r; } void visit(XorExp *) { id = Id::ixor_r; } void visit(CatExp *) { id = Id::cat_r; } }; OpIdRVisitor v; e->accept(&v); return v.id; } /************************************ * If type is a class or struct, return the symbol for it, * else NULL */ AggregateDeclaration *isAggregate(Type *t) { t = t->toBasetype(); if (t->ty == Tclass) { return ((TypeClass *)t)->sym; } else if (t->ty == Tstruct) { return ((TypeStruct *)t)->sym; } return NULL; } /******************************************* * Helper function to turn operator into template argument list */ Objects *opToArg(Scope *sc, TOK op) { /* Remove the = from op= */ switch (op) { case TOKaddass: op = TOKadd; break; case TOKminass: op = TOKmin; break; case TOKmulass: op = TOKmul; break; case TOKdivass: op = TOKdiv; break; case TOKmodass: op = TOKmod; break; case TOKandass: op = TOKand; break; case TOKorass: op = TOKor; break; case TOKxorass: op = TOKxor; break; case TOKshlass: op = TOKshl; break; case TOKshrass: op = TOKshr; break; case TOKushrass: op = TOKushr; break; case TOKcatass: op = TOKcat; break; case TOKpowass: op = TOKpow; break; default: break; } Expression *e = new StringExp(Loc(), const_cast(Token::toChars(op))); e = semantic(e, sc); Objects *tiargs = new Objects(); tiargs->push(e); return tiargs; } /************************************ * Operator overload. * Check for operator overload, if so, replace * with function call. * Return NULL if not an operator overload. */ Expression *op_overload(Expression *e, Scope *sc) { class OpOverload : public Visitor { public: Scope *sc; Expression *result; OpOverload(Scope *sc) : sc(sc) { result = NULL; } void visit(Expression *) { assert(0); } void visit(UnaExp *e) { //printf("UnaExp::op_overload() (%s)\n", e->toChars()); if (e->e1->op == TOKarray) { ArrayExp *ae = (ArrayExp *)e->e1; ae->e1 = semantic(ae->e1, sc); ae->e1 = resolveProperties(sc, ae->e1); Expression *ae1old = ae->e1; const bool maybeSlice = (ae->arguments->dim == 0 || (ae->arguments->dim == 1 && (*ae->arguments)[0]->op == TOKinterval)); IntervalExp *ie = NULL; if (maybeSlice && ae->arguments->dim) { assert((*ae->arguments)[0]->op == TOKinterval); ie = (IntervalExp *)(*ae->arguments)[0]; } while (true) { if (ae->e1->op == TOKerror) { result = ae->e1; return; } Expression *e0 = NULL; Expression *ae1save = ae->e1; ae->lengthVar = NULL; Type *t1b = ae->e1->type->toBasetype(); AggregateDeclaration *ad = isAggregate(t1b); if (!ad) break; if (search_function(ad, Id::opIndexUnary)) { // Deal with $ result = resolveOpDollar(sc, ae, &e0); if (!result) // op(a[i..j]) might be: a.opSliceUnary!(op)(i, j) goto Lfallback; if (result->op == TOKerror) return; /* Rewrite op(a[arguments]) as: * a.opIndexUnary!(op)(arguments) */ Expressions *a = (Expressions *)ae->arguments->copy(); Objects *tiargs = opToArg(sc, e->op); result = new DotTemplateInstanceExp(e->loc, ae->e1, Id::opIndexUnary, tiargs); result = new CallExp(e->loc, result, a); if (maybeSlice) // op(a[]) might be: a.opSliceUnary!(op)() result = trySemantic(result, sc); else result = semantic(result, sc); if (result) { result = Expression::combine(e0, result); return; } } Lfallback: if (maybeSlice && search_function(ad, Id::opSliceUnary)) { // Deal with $ result = resolveOpDollar(sc, ae, ie, &e0); if (result->op == TOKerror) return; /* Rewrite op(a[i..j]) as: * a.opSliceUnary!(op)(i, j) */ Expressions *a = new Expressions(); if (ie) { a->push(ie->lwr); a->push(ie->upr); } Objects *tiargs = opToArg(sc, e->op); result = new DotTemplateInstanceExp(e->loc, ae->e1, Id::opSliceUnary, tiargs); result = new CallExp(e->loc, result, a); result = semantic(result, sc); result = Expression::combine(e0, result); return; } // Didn't find it. Forward to aliasthis if (ad->aliasthis && t1b != ae->att1) { if (!ae->att1 && t1b->checkAliasThisRec()) ae->att1 = t1b; /* Rewrite op(a[arguments]) as: * op(a.aliasthis[arguments]) */ ae->e1 = resolveAliasThis(sc, ae1save, true); if (ae->e1) continue; } break; } ae->e1 = ae1old; // recovery ae->lengthVar = NULL; } e->e1 = semantic(e->e1, sc); e->e1 = resolveProperties(sc, e->e1); if (e->e1->op == TOKerror) { result = e->e1; return; } AggregateDeclaration *ad = isAggregate(e->e1->type); if (ad) { Dsymbol *fd = NULL; #if 1 // Old way, kept for compatibility with D1 if (e->op != TOKpreplusplus && e->op != TOKpreminusminus) { fd = search_function(ad, opId(e)); if (fd) { // Rewrite +e1 as e1.add() result = build_overload(e->loc, sc, e->e1, NULL, fd); return; } } #endif /* Rewrite as: * e1.opUnary!(op)() */ fd = search_function(ad, Id::opUnary); if (fd) { Objects *tiargs = opToArg(sc, e->op); result = new DotTemplateInstanceExp(e->loc, e->e1, fd->ident, tiargs); result = new CallExp(e->loc, result); result = semantic(result, sc); return; } // Didn't find it. Forward to aliasthis if (ad->aliasthis && e->e1->type != e->att1) { /* Rewrite op(e1) as: * op(e1.aliasthis) */ //printf("att una %s e1 = %s\n", Token::toChars(op), this->e1->type->toChars()); Expression *e1 = new DotIdExp(e->loc, e->e1, ad->aliasthis->ident); UnaExp *ue = (UnaExp *)e->copy(); if (!ue->att1 && e->e1->type->checkAliasThisRec()) ue->att1 = e->e1->type; ue->e1 = e1; result = trySemantic(ue, sc); return; } } } void visit(ArrayExp *ae) { //printf("ArrayExp::op_overload() (%s)\n", ae->toChars()); ae->e1 = semantic(ae->e1, sc); ae->e1 = resolveProperties(sc, ae->e1); Expression *ae1old = ae->e1; const bool maybeSlice = (ae->arguments->dim == 0 || (ae->arguments->dim == 1 && (*ae->arguments)[0]->op == TOKinterval)); IntervalExp *ie = NULL; if (maybeSlice && ae->arguments->dim) { assert((*ae->arguments)[0]->op == TOKinterval); ie = (IntervalExp *)(*ae->arguments)[0]; } while (true) { if (ae->e1->op == TOKerror) { result = ae->e1; return; } Expression *e0 = NULL; Expression *ae1save = ae->e1; ae->lengthVar = NULL; Type *t1b = ae->e1->type->toBasetype(); AggregateDeclaration *ad = isAggregate(t1b); if (!ad) { // If the non-aggregate expression ae->e1 is indexable or sliceable, // convert it to the corresponding concrete expression. if (t1b->ty == Tpointer || t1b->ty == Tsarray || t1b->ty == Tarray || t1b->ty == Taarray || t1b->ty == Ttuple || t1b->ty == Tvector || ae->e1->op == TOKtype) { // Convert to SliceExp if (maybeSlice) { result = new SliceExp(ae->loc, ae->e1, ie); result = semantic(result, sc); return; } // Convert to IndexExp if (ae->arguments->dim == 1) { result = new IndexExp(ae->loc, ae->e1, (*ae->arguments)[0]); result = semantic(result, sc); return; } } break; } if (search_function(ad, Id::index)) { // Deal with $ result = resolveOpDollar(sc, ae, &e0); if (!result) // a[i..j] might be: a.opSlice(i, j) goto Lfallback; if (result->op == TOKerror) return; /* Rewrite e1[arguments] as: * e1.opIndex(arguments) */ Expressions *a = (Expressions *)ae->arguments->copy(); result = new DotIdExp(ae->loc, ae->e1, Id::index); result = new CallExp(ae->loc, result, a); if (maybeSlice) // a[] might be: a.opSlice() result = trySemantic(result, sc); else result = semantic(result, sc); if (result) { result = Expression::combine(e0, result); return; } } Lfallback: if (maybeSlice && ae->e1->op == TOKtype) { result = new SliceExp(ae->loc, ae->e1, ie); result = semantic(result, sc); result = Expression::combine(e0, result); return; } if (maybeSlice && search_function(ad, Id::slice)) { // Deal with $ result = resolveOpDollar(sc, ae, ie, &e0); if (result->op == TOKerror) return; /* Rewrite a[i..j] as: * a.opSlice(i, j) */ Expressions *a = new Expressions(); if (ie) { a->push(ie->lwr); a->push(ie->upr); } result = new DotIdExp(ae->loc, ae->e1, Id::slice); result = new CallExp(ae->loc, result, a); result = semantic(result, sc); result = Expression::combine(e0, result); return; } // Didn't find it. Forward to aliasthis if (ad->aliasthis && t1b != ae->att1) { if (!ae->att1 && t1b->checkAliasThisRec()) ae->att1 = t1b; //printf("att arr e1 = %s\n", this->e1->type->toChars()); /* Rewrite op(a[arguments]) as: * op(a.aliasthis[arguments]) */ ae->e1 = resolveAliasThis(sc, ae1save, true); if (ae->e1) continue; } break; } ae->e1 = ae1old; // recovery ae->lengthVar = NULL; } /*********************************************** * This is mostly the same as UnaryExp::op_overload(), but has * a different rewrite. */ void visit(CastExp *e) { //printf("CastExp::op_overload() (%s)\n", e->toChars()); AggregateDeclaration *ad = isAggregate(e->e1->type); if (ad) { Dsymbol *fd = NULL; /* Rewrite as: * e1.opCast!(T)() */ fd = search_function(ad, Id::_cast); if (fd) { #if 1 // Backwards compatibility with D1 if opCast is a function, not a template if (fd->isFuncDeclaration()) { // Rewrite as: e1.opCast() result = build_overload(e->loc, sc, e->e1, NULL, fd); return; } #endif Objects *tiargs = new Objects(); tiargs->push(e->to); result = new DotTemplateInstanceExp(e->loc, e->e1, fd->ident, tiargs); result = new CallExp(e->loc, result); result = semantic(result, sc); return; } // Didn't find it. Forward to aliasthis if (ad->aliasthis) { /* Rewrite op(e1) as: * op(e1.aliasthis) */ Expression *e1 = new DotIdExp(e->loc, e->e1, ad->aliasthis->ident); result = e->copy(); ((UnaExp *)result)->e1 = e1; result = trySemantic(result, sc); return; } } } void visit(BinExp *e) { //printf("BinExp::op_overload() (%s)\n", e->toChars()); Identifier *id = opId(e); Identifier *id_r = opId_r(e); Expressions args1; Expressions args2; int argsset = 0; AggregateDeclaration *ad1 = isAggregate(e->e1->type); AggregateDeclaration *ad2 = isAggregate(e->e2->type); if (e->op == TOKassign && ad1 == ad2) { StructDeclaration *sd = ad1->isStructDeclaration(); if (sd && !sd->hasIdentityAssign) { /* This is bitwise struct assignment. */ return; } } Dsymbol *s = NULL; Dsymbol *s_r = NULL; #if 1 // the old D1 scheme if (ad1 && id) { s = search_function(ad1, id); } if (ad2 && id_r) { s_r = search_function(ad2, id_r); // Bugzilla 12778: If both x.opBinary(y) and y.opBinaryRight(x) found, // and they are exactly same symbol, x.opBinary(y) should be preferred. if (s_r && s_r == s) s_r = NULL; } #endif Objects *tiargs = NULL; if (e->op == TOKplusplus || e->op == TOKminusminus) { // Bug4099 fix if (ad1 && search_function(ad1, Id::opUnary)) return; } if (!s && !s_r && e->op != TOKequal && e->op != TOKnotequal && e->op != TOKassign && e->op != TOKplusplus && e->op != TOKminusminus) { /* Try the new D2 scheme, opBinary and opBinaryRight */ if (ad1) { s = search_function(ad1, Id::opBinary); if (s && !s->isTemplateDeclaration()) { e->e1->error("%s.opBinary isn't a template", e->e1->toChars()); result = new ErrorExp(); return; } } if (ad2) { s_r = search_function(ad2, Id::opBinaryRight); if (s_r && !s_r->isTemplateDeclaration()) { e->e2->error("%s.opBinaryRight isn't a template", e->e2->toChars()); result = new ErrorExp(); return; } if (s_r && s_r == s) // Bugzilla 12778 s_r = NULL; } // Set tiargs, the template argument list, which will be the operator string if (s || s_r) { id = Id::opBinary; id_r = Id::opBinaryRight; tiargs = opToArg(sc, e->op); } } if (s || s_r) { /* Try: * a.opfunc(b) * b.opfunc_r(a) * and see which is better. */ args1.setDim(1); args1[0] = e->e1; expandTuples(&args1); args2.setDim(1); args2[0] = e->e2; expandTuples(&args2); argsset = 1; Match m; memset(&m, 0, sizeof(m)); m.last = MATCHnomatch; if (s) { functionResolve(&m, s, e->loc, sc, tiargs, e->e1->type, &args2); if (m.lastf && (m.lastf->errors || m.lastf->semantic3Errors)) { result = new ErrorExp(); return; } } FuncDeclaration *lastf = m.lastf; if (s_r) { functionResolve(&m, s_r, e->loc, sc, tiargs, e->e2->type, &args1); if (m.lastf && (m.lastf->errors || m.lastf->semantic3Errors)) { result = new ErrorExp(); return; } } if (m.count > 1) { // Error, ambiguous e->error("overloads %s and %s both match argument list for %s", m.lastf->type->toChars(), m.nextf->type->toChars(), m.lastf->toChars()); } else if (m.last <= MATCHnomatch) { m.lastf = m.anyf; if (tiargs) goto L1; } if (e->op == TOKplusplus || e->op == TOKminusminus) { // Kludge because operator overloading regards e++ and e-- // as unary, but it's implemented as a binary. // Rewrite (e1 ++ e2) as e1.postinc() // Rewrite (e1 -- e2) as e1.postdec() result = build_overload(e->loc, sc, e->e1, NULL, m.lastf ? m.lastf : s); } else if ((lastf && m.lastf == lastf) || (!s_r && m.last <= MATCHnomatch)) { // Rewrite (e1 op e2) as e1.opfunc(e2) result = build_overload(e->loc, sc, e->e1, e->e2, m.lastf ? m.lastf : s); } else { // Rewrite (e1 op e2) as e2.opfunc_r(e1) result = build_overload(e->loc, sc, e->e2, e->e1, m.lastf ? m.lastf : s_r); } return; } L1: #if 1 // Retained for D1 compatibility if (isCommutative(e->op) && !tiargs) { s = NULL; s_r = NULL; if (ad1 && id_r) { s_r = search_function(ad1, id_r); } if (ad2 && id) { s = search_function(ad2, id); if (s && s == s_r) // Bugzilla 12778 s = NULL; } if (s || s_r) { /* Try: * a.opfunc_r(b) * b.opfunc(a) * and see which is better. */ if (!argsset) { args1.setDim(1); args1[0] = e->e1; expandTuples(&args1); args2.setDim(1); args2[0] = e->e2; expandTuples(&args2); } Match m; memset(&m, 0, sizeof(m)); m.last = MATCHnomatch; if (s_r) { functionResolve(&m, s_r, e->loc, sc, tiargs, e->e1->type, &args2); if (m.lastf && (m.lastf->errors || m.lastf->semantic3Errors)) { result = new ErrorExp(); return; } } FuncDeclaration *lastf = m.lastf; if (s) { functionResolve(&m, s, e->loc, sc, tiargs, e->e2->type, &args1); if (m.lastf && (m.lastf->errors || m.lastf->semantic3Errors)) { result = new ErrorExp(); return; } } if (m.count > 1) { // Error, ambiguous e->error("overloads %s and %s both match argument list for %s", m.lastf->type->toChars(), m.nextf->type->toChars(), m.lastf->toChars()); } else if (m.last <= MATCHnomatch) { m.lastf = m.anyf; } if ((lastf && m.lastf == lastf) || (!s && m.last <= MATCHnomatch)) { // Rewrite (e1 op e2) as e1.opfunc_r(e2) result = build_overload(e->loc, sc, e->e1, e->e2, m.lastf ? m.lastf : s_r); } else { // Rewrite (e1 op e2) as e2.opfunc(e1) result = build_overload(e->loc, sc, e->e2, e->e1, m.lastf ? m.lastf : s); } // When reversing operands of comparison operators, // need to reverse the sense of the op switch (e->op) { case TOKlt: e->op = TOKgt; break; case TOKgt: e->op = TOKlt; break; case TOKle: e->op = TOKge; break; case TOKge: e->op = TOKle; break; default: break; } return; } } #endif // Try alias this on first operand if (ad1 && ad1->aliasthis && !(e->op == TOKassign && ad2 && ad1 == ad2)) // See Bugzilla 2943 { /* Rewrite (e1 op e2) as: * (e1.aliasthis op e2) */ if (e->att1 && e->e1->type == e->att1) return; //printf("att bin e1 = %s\n", this->e1->type->toChars()); Expression *e1 = new DotIdExp(e->loc, e->e1, ad1->aliasthis->ident); BinExp *be = (BinExp *)e->copy(); if (!be->att1 && e->e1->type->checkAliasThisRec()) be->att1 = e->e1->type; be->e1 = e1; result = trySemantic(be, sc); return; } // Try alias this on second operand /* Bugzilla 2943: make sure that when we're copying the struct, we don't * just copy the alias this member */ if (ad2 && ad2->aliasthis && !(e->op == TOKassign && ad1 && ad1 == ad2)) { /* Rewrite (e1 op e2) as: * (e1 op e2.aliasthis) */ if (e->att2 && e->e2->type == e->att2) return; //printf("att bin e2 = %s\n", e->e2->type->toChars()); Expression *e2 = new DotIdExp(e->loc, e->e2, ad2->aliasthis->ident); BinExp *be = (BinExp *)e->copy(); if (!be->att2 && e->e2->type->checkAliasThisRec()) be->att2 = e->e2->type; be->e2 = e2; result = trySemantic(be, sc); return; } return; } static bool needsDirectEq(Type *t1, Type *t2, Scope *sc) { Type *t1n = t1->nextOf()->toBasetype(); Type *t2n = t2->nextOf()->toBasetype(); if (((t1n->ty == Tchar || t1n->ty == Twchar || t1n->ty == Tdchar) && (t2n->ty == Tchar || t2n->ty == Twchar || t2n->ty == Tdchar)) || (t1n->ty == Tvoid || t2n->ty == Tvoid)) { return false; } if (t1n->constOf() != t2n->constOf()) return true; Type *t = t1n; while (t->toBasetype()->nextOf()) t = t->nextOf()->toBasetype(); if (t->ty != Tstruct) return false; if (global.params.useTypeInfo && Type::dtypeinfo) semanticTypeInfo(sc, t); return ((TypeStruct *)t)->sym->hasIdentityEquals; } void visit(EqualExp *e) { //printf("EqualExp::op_overload() (%s)\n", e->toChars()); Type *t1 = e->e1->type->toBasetype(); Type *t2 = e->e2->type->toBasetype(); /* Check for array equality. */ if ((t1->ty == Tarray || t1->ty == Tsarray) && (t2->ty == Tarray || t2->ty == Tsarray)) { if (needsDirectEq(t1, t2, sc)) { /* Rewrite as: * __ArrayEq(e1, e2) */ Expression *eeq = new IdentifierExp(e->loc, Id::__ArrayEq); result = new CallExp(e->loc, eeq, e->e1, e->e2); if (e->op == TOKnotequal) result = new NotExp(e->loc, result); result = trySemantic(result, sc); // for better error message if (!result) { e->error("cannot compare %s and %s", t1->toChars(), t2->toChars()); result = new ErrorExp(); } return; } } /* Check for class equality with null literal or typeof(null). */ if ((t1->ty == Tclass && e->e2->op == TOKnull) || (t2->ty == Tclass && e->e1->op == TOKnull)) { e->error("use '%s' instead of '%s' when comparing with null", Token::toChars(e->op == TOKequal ? TOKidentity : TOKnotidentity), Token::toChars(e->op)); result = new ErrorExp(); return; } if ((t1->ty == Tclass && t2->ty == Tnull) || (t1->ty == Tnull && t2->ty == Tclass)) { // Comparing a class with typeof(null) should not call opEquals return; } /* Check for class equality. */ if (t1->ty == Tclass && t2->ty == Tclass) { ClassDeclaration *cd1 = t1->isClassHandle(); ClassDeclaration *cd2 = t2->isClassHandle(); if (!(cd1->isCPPclass() || cd2->isCPPclass())) { /* Rewrite as: * .object.opEquals(e1, e2) */ Expression *e1x = e->e1; Expression *e2x = e->e2; /* The explicit cast is necessary for interfaces, * see Bugzilla 4088. */ Type *to = ClassDeclaration::object->getType(); if (cd1->isInterfaceDeclaration()) e1x = new CastExp(e->loc, e->e1, t1->isMutable() ? to : to->constOf()); if (cd2->isInterfaceDeclaration()) e2x = new CastExp(e->loc, e->e2, t2->isMutable() ? to : to->constOf()); result = new IdentifierExp(e->loc, Id::empty); result = new DotIdExp(e->loc, result, Id::object); result = new DotIdExp(e->loc, result, Id::eq); result = new CallExp(e->loc, result, e1x, e2x); if (e->op == TOKnotequal) result = new NotExp(e->loc, result); result = semantic(result, sc); return; } } result = compare_overload(e, sc, Id::eq); if (result) { if (result->op == TOKcall && e->op == TOKnotequal) { result = new NotExp(result->loc, result); result = semantic(result, sc); } return; } /* Check for pointer equality. */ if (t1->ty == Tpointer || t2->ty == Tpointer) { /* Rewrite: * ptr1 == ptr2 * as: * ptr1 is ptr2 * * This is just a rewriting for deterministic AST representation * as the backend input. */ TOK op2 = e->op == TOKequal ? TOKidentity : TOKnotidentity; result = new IdentityExp(op2, e->loc, e->e1, e->e2); result = semantic(result, sc); return; } /* Check for struct equality without opEquals. */ if (t1->ty == Tstruct && t2->ty == Tstruct) { StructDeclaration *sd = ((TypeStruct *)t1)->sym; if (sd != ((TypeStruct *)t2)->sym) return; if (!needOpEquals(sd)) { // Use bitwise equality. TOK op2 = e->op == TOKequal ? TOKidentity : TOKnotidentity; result = new IdentityExp(op2, e->loc, e->e1, e->e2); result = semantic(result, sc); return; } /* Do memberwise equality. * Rewrite: * e1 == e2 * as: * e1.tupleof == e2.tupleof * * If sd is a nested struct, and if it's nested in a class, it will * also compare the parent class's equality. Otherwise, compares * the identity of parent context through void*. */ if (e->att1 && t1 == e->att1) return; if (e->att2 && t2 == e->att2) return; e = (EqualExp *)e->copy(); if (!e->att1) e->att1 = t1; if (!e->att2) e->att2 = t2; e->e1 = new DotIdExp(e->loc, e->e1, Id::_tupleof); e->e2 = new DotIdExp(e->loc, e->e2, Id::_tupleof); result = semantic(e, sc); /* Bugzilla 15292, if the rewrite result is same with the original, * the equality is unresolvable because it has recursive definition. */ if (result->op == e->op && ((EqualExp *)result)->e1->type->toBasetype() == t1) { e->error("cannot compare %s because its auto generated member-wise equality has recursive definition", t1->toChars()); result = new ErrorExp(); } return; } /* Check for tuple equality. */ if (e->e1->op == TOKtuple && e->e2->op == TOKtuple) { TupleExp *tup1 = (TupleExp *)e->e1; TupleExp *tup2 = (TupleExp *)e->e2; size_t dim = tup1->exps->dim; if (dim != tup2->exps->dim) { e->error("mismatched tuple lengths, %d and %d", (int)dim, (int)tup2->exps->dim); result = new ErrorExp(); return; } if (dim == 0) { // zero-length tuple comparison should always return true or false. result = new IntegerExp(e->loc, (e->op == TOKequal), Type::tbool); } else { for (size_t i = 0; i < dim; i++) { Expression *ex1 = (*tup1->exps)[i]; Expression *ex2 = (*tup2->exps)[i]; EqualExp *eeq = new EqualExp(e->op, e->loc, ex1, ex2); eeq->att1 = e->att1; eeq->att2 = e->att2; if (!result) result = eeq; else if (e->op == TOKequal) result = new AndAndExp(e->loc, result, eeq); else result = new OrOrExp(e->loc, result, eeq); } assert(result); } result = Expression::combine(Expression::combine(tup1->e0, tup2->e0), result); result = semantic(result, sc); return; } } void visit(CmpExp *e) { //printf("CmpExp::op_overload() (%s)\n", e->toChars()); result = compare_overload(e, sc, Id::cmp); } /********************************* * Operator overloading for op= */ void visit(BinAssignExp *e) { //printf("BinAssignExp::op_overload() (%s)\n", e->toChars()); if (e->e1->op == TOKarray) { ArrayExp *ae = (ArrayExp *)e->e1; ae->e1 = semantic(ae->e1, sc); ae->e1 = resolveProperties(sc, ae->e1); Expression *ae1old = ae->e1; const bool maybeSlice = (ae->arguments->dim == 0 || (ae->arguments->dim == 1 && (*ae->arguments)[0]->op == TOKinterval)); IntervalExp *ie = NULL; if (maybeSlice && ae->arguments->dim) { assert((*ae->arguments)[0]->op == TOKinterval); ie = (IntervalExp *)(*ae->arguments)[0]; } while (true) { if (ae->e1->op == TOKerror) { result = ae->e1; return; } Expression *e0 = NULL; Expression *ae1save = ae->e1; ae->lengthVar = NULL; Type *t1b = ae->e1->type->toBasetype(); AggregateDeclaration *ad = isAggregate(t1b); if (!ad) break; if (search_function(ad, Id::opIndexOpAssign)) { // Deal with $ result = resolveOpDollar(sc, ae, &e0); if (!result) // (a[i..j] op= e2) might be: a.opSliceOpAssign!(op)(e2, i, j) goto Lfallback; if (result->op == TOKerror) return; result = semantic(e->e2, sc); if (result->op == TOKerror) return; e->e2 = result; /* Rewrite a[arguments] op= e2 as: * a.opIndexOpAssign!(op)(e2, arguments) */ Expressions *a = (Expressions *)ae->arguments->copy(); a->insert(0, e->e2); Objects *tiargs = opToArg(sc, e->op); result = new DotTemplateInstanceExp(e->loc, ae->e1, Id::opIndexOpAssign, tiargs); result = new CallExp(e->loc, result, a); if (maybeSlice) // (a[] op= e2) might be: a.opSliceOpAssign!(op)(e2) result = trySemantic(result, sc); else result = semantic(result, sc); if (result) { result = Expression::combine(e0, result); return; } } Lfallback: if (maybeSlice && search_function(ad, Id::opSliceOpAssign)) { // Deal with $ result = resolveOpDollar(sc, ae, ie, &e0); if (result->op == TOKerror) return; result = semantic(e->e2, sc); if (result->op == TOKerror) return; e->e2 = result; /* Rewrite (a[i..j] op= e2) as: * a.opSliceOpAssign!(op)(e2, i, j) */ Expressions *a = new Expressions(); a->push(e->e2); if (ie) { a->push(ie->lwr); a->push(ie->upr); } Objects *tiargs = opToArg(sc, e->op); result = new DotTemplateInstanceExp(e->loc, ae->e1, Id::opSliceOpAssign, tiargs); result = new CallExp(e->loc, result, a); result = semantic(result, sc); result = Expression::combine(e0, result); return; } // Didn't find it. Forward to aliasthis if (ad->aliasthis && t1b != ae->att1) { if (!ae->att1 && t1b->checkAliasThisRec()) ae->att1 = t1b; /* Rewrite (a[arguments] op= e2) as: * a.aliasthis[arguments] op= e2 */ ae->e1 = resolveAliasThis(sc, ae1save, true); if (ae->e1) continue; } break; } ae->e1 = ae1old; // recovery ae->lengthVar = NULL; } result = binSemanticProp(e, sc); if (result) return; // Don't attempt 'alias this' if an error occured if (e->e1->type->ty == Terror || e->e2->type->ty == Terror) { result = new ErrorExp(); return; } Identifier *id = opId(e); Expressions args2; AggregateDeclaration *ad1 = isAggregate(e->e1->type); Dsymbol *s = NULL; #if 1 // the old D1 scheme if (ad1 && id) { s = search_function(ad1, id); } #endif Objects *tiargs = NULL; if (!s) { /* Try the new D2 scheme, opOpAssign */ if (ad1) { s = search_function(ad1, Id::opOpAssign); if (s && !s->isTemplateDeclaration()) { e->error("%s.opOpAssign isn't a template", e->e1->toChars()); result = new ErrorExp(); return; } } // Set tiargs, the template argument list, which will be the operator string if (s) { id = Id::opOpAssign; tiargs = opToArg(sc, e->op); } } if (s) { /* Try: * a.opOpAssign(b) */ args2.setDim(1); args2[0] = e->e2; expandTuples(&args2); Match m; memset(&m, 0, sizeof(m)); m.last = MATCHnomatch; if (s) { functionResolve(&m, s, e->loc, sc, tiargs, e->e1->type, &args2); if (m.lastf && (m.lastf->errors || m.lastf->semantic3Errors)) { result = new ErrorExp(); return; } } if (m.count > 1) { // Error, ambiguous e->error("overloads %s and %s both match argument list for %s", m.lastf->type->toChars(), m.nextf->type->toChars(), m.lastf->toChars()); } else if (m.last <= MATCHnomatch) { m.lastf = m.anyf; if (tiargs) goto L1; } // Rewrite (e1 op e2) as e1.opOpAssign(e2) result = build_overload(e->loc, sc, e->e1, e->e2, m.lastf ? m.lastf : s); return; } L1: // Try alias this on first operand if (ad1 && ad1->aliasthis) { /* Rewrite (e1 op e2) as: * (e1.aliasthis op e2) */ if (e->att1 && e->e1->type == e->att1) return; //printf("att %s e1 = %s\n", Token::toChars(e->op), e->e1->type->toChars()); Expression *e1 = new DotIdExp(e->loc, e->e1, ad1->aliasthis->ident); BinExp *be = (BinExp *)e->copy(); if (!be->att1 && e->e1->type->checkAliasThisRec()) be->att1 = e->e1->type; be->e1 = e1; result = trySemantic(be, sc); return; } // Try alias this on second operand AggregateDeclaration *ad2 = isAggregate(e->e2->type); if (ad2 && ad2->aliasthis) { /* Rewrite (e1 op e2) as: * (e1 op e2.aliasthis) */ if (e->att2 && e->e2->type == e->att2) return; //printf("att %s e2 = %s\n", Token::toChars(e->op), e->e2->type->toChars()); Expression *e2 = new DotIdExp(e->loc, e->e2, ad2->aliasthis->ident); BinExp *be = (BinExp *)e->copy(); if (!be->att2 && e->e2->type->checkAliasThisRec()) be->att2 = e->e2->type; be->e2 = e2; result = trySemantic(be, sc); return; } } }; OpOverload v(sc); e->accept(&v); return v.result; } /****************************************** * Common code for overloading of EqualExp and CmpExp */ Expression *compare_overload(BinExp *e, Scope *sc, Identifier *id) { //printf("BinExp::compare_overload(id = %s) %s\n", id->toChars(), e->toChars()); AggregateDeclaration *ad1 = isAggregate(e->e1->type); AggregateDeclaration *ad2 = isAggregate(e->e2->type); Dsymbol *s = NULL; Dsymbol *s_r = NULL; if (ad1) { s = search_function(ad1, id); } if (ad2) { s_r = search_function(ad2, id); if (s == s_r) s_r = NULL; } Objects *tiargs = NULL; if (s || s_r) { /* Try: * a.opEquals(b) * b.opEquals(a) * and see which is better. */ Expressions args1; Expressions args2; args1.setDim(1); args1[0] = e->e1; expandTuples(&args1); args2.setDim(1); args2[0] = e->e2; expandTuples(&args2); Match m; memset(&m, 0, sizeof(m)); m.last = MATCHnomatch; if (0 && s && s_r) { printf("s : %s\n", s->toPrettyChars()); printf("s_r: %s\n", s_r->toPrettyChars()); } if (s) { functionResolve(&m, s, e->loc, sc, tiargs, e->e1->type, &args2); if (m.lastf && (m.lastf->errors || m.lastf->semantic3Errors)) return new ErrorExp(); } FuncDeclaration *lastf = m.lastf; int count = m.count; if (s_r) { functionResolve(&m, s_r, e->loc, sc, tiargs, e->e2->type, &args1); if (m.lastf && (m.lastf->errors || m.lastf->semantic3Errors)) return new ErrorExp(); } if (m.count > 1) { /* The following if says "not ambiguous" if there's one match * from s and one from s_r, in which case we pick s. * This doesn't follow the spec, but is a workaround for the case * where opEquals was generated from templates and we cannot figure * out if both s and s_r came from the same declaration or not. * The test case is: * import std.typecons; * void main() { * assert(tuple("has a", 2u) == tuple("has a", 1)); * } */ if (!(m.lastf == lastf && m.count == 2 && count == 1)) { // Error, ambiguous e->error("overloads %s and %s both match argument list for %s", m.lastf->type->toChars(), m.nextf->type->toChars(), m.lastf->toChars()); } } else if (m.last <= MATCHnomatch) { m.lastf = m.anyf; } Expression *result; if ((lastf && m.lastf == lastf) || (!s_r && m.last <= MATCHnomatch)) { // Rewrite (e1 op e2) as e1.opfunc(e2) result = build_overload(e->loc, sc, e->e1, e->e2, m.lastf ? m.lastf : s); } else { // Rewrite (e1 op e2) as e2.opfunc_r(e1) result = build_overload(e->loc, sc, e->e2, e->e1, m.lastf ? m.lastf : s_r); // When reversing operands of comparison operators, // need to reverse the sense of the op switch (e->op) { case TOKlt: e->op = TOKgt; break; case TOKgt: e->op = TOKlt; break; case TOKle: e->op = TOKge; break; case TOKge: e->op = TOKle; break; // The rest are symmetric default: break; } } return result; } // Try alias this on first operand if (ad1 && ad1->aliasthis) { /* Rewrite (e1 op e2) as: * (e1.aliasthis op e2) */ if (e->att1 && e->e1->type == e->att1) return NULL; //printf("att cmp_bin e1 = %s\n", e->e1->type->toChars()); Expression *e1 = new DotIdExp(e->loc, e->e1, ad1->aliasthis->ident); BinExp *be = (BinExp *)e->copy(); if (!be->att1 && e->e1->type->checkAliasThisRec()) be->att1 = e->e1->type; be->e1 = e1; return trySemantic(be, sc); } // Try alias this on second operand if (ad2 && ad2->aliasthis) { /* Rewrite (e1 op e2) as: * (e1 op e2.aliasthis) */ if (e->att2 && e->e2->type == e->att2) return NULL; //printf("att cmp_bin e2 = %s\n", e->e2->type->toChars()); Expression *e2 = new DotIdExp(e->loc, e->e2, ad2->aliasthis->ident); BinExp *be = (BinExp *)e->copy(); if (!be->att2 && e->e2->type->checkAliasThisRec()) be->att2 = e->e2->type; be->e2 = e2; return trySemantic(be, sc); } return NULL; } /*********************************** * Utility to build a function call out of this reference and argument. */ Expression *build_overload(Loc loc, Scope *sc, Expression *ethis, Expression *earg, Dsymbol *d) { assert(d); Expression *e; //printf("build_overload(id = '%s')\n", id->toChars()); //earg->print(); //earg->type->print(); Declaration *decl = d->isDeclaration(); if (decl) e = new DotVarExp(loc, ethis, decl, false); else e = new DotIdExp(loc, ethis, d->ident); e = new CallExp(loc, e, earg); e = semantic(e, sc); return e; } /*************************************** * Search for function funcid in aggregate ad. */ Dsymbol *search_function(ScopeDsymbol *ad, Identifier *funcid) { Dsymbol *s = ad->search(Loc(), funcid); if (s) { //printf("search_function: s = '%s'\n", s->kind()); Dsymbol *s2 = s->toAlias(); //printf("search_function: s2 = '%s'\n", s2->kind()); FuncDeclaration *fd = s2->isFuncDeclaration(); if (fd && fd->type->ty == Tfunction) return fd; TemplateDeclaration *td = s2->isTemplateDeclaration(); if (td) return td; } return NULL; } bool inferAggregate(ForeachStatement *fes, Scope *sc, Dsymbol *&sapply) { //printf("inferAggregate(%s)\n", fes->aggr->toChars()); Identifier *idapply = (fes->op == TOKforeach) ? Id::apply : Id::applyReverse; Identifier *idfront = (fes->op == TOKforeach) ? Id::Ffront : Id::Fback; int sliced = 0; Type *tab; Type *att = NULL; Expression *aggr = fes->aggr; AggregateDeclaration *ad; while (1) { aggr = semantic(aggr, sc); aggr = resolveProperties(sc, aggr); aggr = aggr->optimize(WANTvalue); if (!aggr->type || aggr->op == TOKerror) goto Lerr; tab = aggr->type->toBasetype(); switch (tab->ty) { case Tarray: case Tsarray: case Ttuple: case Taarray: break; case Tclass: ad = ((TypeClass *)tab)->sym; goto Laggr; case Tstruct: ad = ((TypeStruct *)tab)->sym; goto Laggr; Laggr: if (!sliced) { sapply = search_function(ad, idapply); if (sapply) { // opApply aggregate break; } if (fes->aggr->op != TOKtype) { Expression *rinit = new ArrayExp(fes->aggr->loc, fes->aggr); rinit = trySemantic(rinit, sc); if (rinit) // if application of [] succeeded { aggr = rinit; sliced = 1; continue; } } } if (ad->search(Loc(), idfront)) { // range aggregate break; } if (ad->aliasthis) { if (att == tab) goto Lerr; if (!att && tab->checkAliasThisRec()) att = tab; aggr = resolveAliasThis(sc, aggr); continue; } goto Lerr; case Tdelegate: if (aggr->op == TOKdelegate) { sapply = ((DelegateExp *)aggr)->func; } break; case Terror: break; default: goto Lerr; } break; } fes->aggr = aggr; return true; Lerr: return false; } /***************************************** * Given array of parameters and an aggregate type, * if any of the parameter types are missing, attempt to infer * them from the aggregate type. */ bool inferApplyArgTypes(ForeachStatement *fes, Scope *sc, Dsymbol *&sapply) { if (!fes->parameters || !fes->parameters->dim) return false; if (sapply) // prefer opApply { for (size_t u = 0; u < fes->parameters->dim; u++) { Parameter *p = (*fes->parameters)[u]; if (p->type) { p->type = p->type->semantic(fes->loc, sc); p->type = p->type->addStorageClass(p->storageClass); } } Expression *ethis; Type *tab = fes->aggr->type->toBasetype(); if (tab->ty == Tclass || tab->ty == Tstruct) ethis = fes->aggr; else { assert(tab->ty == Tdelegate && fes->aggr->op == TOKdelegate); ethis = ((DelegateExp *)fes->aggr)->e1; } /* Look for like an * int opApply(int delegate(ref Type [, ...]) dg); * overload */ FuncDeclaration *fd = sapply->isFuncDeclaration(); if (fd) { sapply = inferApplyArgTypesX(ethis, fd, fes->parameters); } return sapply != NULL; } /* Return if no parameters need types. */ for (size_t u = 0; u < fes->parameters->dim; u++) { Parameter *p = (*fes->parameters)[u]; if (!p->type) break; } AggregateDeclaration *ad; Parameter *p = (*fes->parameters)[0]; Type *taggr = fes->aggr->type; assert(taggr); Type *tab = taggr->toBasetype(); switch (tab->ty) { case Tarray: case Tsarray: case Ttuple: if (fes->parameters->dim == 2) { if (!p->type) { p->type = Type::tsize_t; // key type p->type = p->type->addStorageClass(p->storageClass); } p = (*fes->parameters)[1]; } if (!p->type && tab->ty != Ttuple) { p->type = tab->nextOf(); // value type p->type = p->type->addStorageClass(p->storageClass); } break; case Taarray: { TypeAArray *taa = (TypeAArray *)tab; if (fes->parameters->dim == 2) { if (!p->type) { p->type = taa->index; // key type p->type = p->type->addStorageClass(p->storageClass); if (p->storageClass & STCref) // key must not be mutated via ref p->type = p->type->addMod(MODconst); } p = (*fes->parameters)[1]; } if (!p->type) { p->type = taa->next; // value type p->type = p->type->addStorageClass(p->storageClass); } break; } case Tclass: ad = ((TypeClass *)tab)->sym; goto Laggr; case Tstruct: ad = ((TypeStruct *)tab)->sym; goto Laggr; Laggr: if (fes->parameters->dim == 1) { if (!p->type) { /* Look for a front() or back() overload */ Identifier *id = (fes->op == TOKforeach) ? Id::Ffront : Id::Fback; Dsymbol *s = ad->search(Loc(), id); FuncDeclaration *fd = s ? s->isFuncDeclaration() : NULL; if (fd) { // Resolve inout qualifier of front type p->type = fd->type->nextOf(); if (p->type) { p->type = p->type->substWildTo(tab->mod); p->type = p->type->addStorageClass(p->storageClass); } } else if (s && s->isTemplateDeclaration()) ; else if (s && s->isDeclaration()) p->type = ((Declaration *)s)->type; else break; } break; } break; case Tdelegate: { if (!inferApplyArgTypesY((TypeFunction *)tab->nextOf(), fes->parameters)) return false; break; } default: break; // ignore error, caught later } return true; } static Dsymbol *inferApplyArgTypesX(Expression *ethis, FuncDeclaration *fstart, Parameters *parameters) { struct ParamOpOver { Parameters *parameters; MOD mod; MATCH match; FuncDeclaration *fd_best; FuncDeclaration *fd_ambig; static int fp(void *param, Dsymbol *s) { FuncDeclaration *f = s->isFuncDeclaration(); if (!f) return 0; ParamOpOver *p = (ParamOpOver *)param; TypeFunction *tf = (TypeFunction *)f->type; MATCH m = MATCHexact; if (f->isThis()) { if (!MODimplicitConv(p->mod, tf->mod)) m = MATCHnomatch; else if (p->mod != tf->mod) m = MATCHconst; } if (!inferApplyArgTypesY(tf, p->parameters, 1)) m = MATCHnomatch; if (m > p->match) { p->fd_best = f; p->fd_ambig = NULL; p->match = m; } else if (m == p->match) p->fd_ambig = f; return 0; } }; ParamOpOver p; p.parameters = parameters; p.mod = ethis->type->mod; p.match = MATCHnomatch; p.fd_best = NULL; p.fd_ambig = NULL; overloadApply(fstart, &p, &ParamOpOver::fp); if (p.fd_best) { inferApplyArgTypesY((TypeFunction *)p.fd_best->type, parameters); if (p.fd_ambig) { ::error(ethis->loc, "%s.%s matches more than one declaration:\n%s: %s\nand:\n%s: %s", ethis->toChars(), fstart->ident->toChars(), p.fd_best ->loc.toChars(), p.fd_best ->type->toChars(), p.fd_ambig->loc.toChars(), p.fd_ambig->type->toChars()); p.fd_best = NULL; } } return p.fd_best; } /****************************** * Infer parameters from type of function. * Returns: * 1 match for this function * 0 no match for this function */ static int inferApplyArgTypesY(TypeFunction *tf, Parameters *parameters, int flags) { size_t nparams; Parameter *p; if (Parameter::dim(tf->parameters) != 1) goto Lnomatch; p = Parameter::getNth(tf->parameters, 0); if (p->type->ty != Tdelegate) goto Lnomatch; tf = (TypeFunction *)p->type->nextOf(); assert(tf->ty == Tfunction); /* We now have tf, the type of the delegate. Match it against * the parameters, filling in missing parameter types. */ nparams = Parameter::dim(tf->parameters); if (nparams == 0 || tf->varargs) goto Lnomatch; // not enough parameters if (parameters->dim != nparams) goto Lnomatch; // not enough parameters for (size_t u = 0; u < nparams; u++) { p = (*parameters)[u]; Parameter *param = Parameter::getNth(tf->parameters, u); if (p->type) { if (!p->type->equals(param->type)) goto Lnomatch; } else if (!flags) { p->type = param->type; p->type = p->type->addStorageClass(p->storageClass); } } return 1; Lnomatch: return 0; }