Mercurial > hg > CbC > CbC_gcc
diff gcc/d/dmd/initsem.c @ 145:1830386684a0
gcc-9.2.0
author | anatofuz |
---|---|
date | Thu, 13 Feb 2020 11:34:05 +0900 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gcc/d/dmd/initsem.c Thu Feb 13 11:34:05 2020 +0900 @@ -0,0 +1,917 @@ + +/* 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 + */ + +#include "root/checkedint.h" +#include "mars.h" +#include "init.h" +#include "expression.h" +#include "statement.h" +#include "declaration.h" +#include "aggregate.h" +#include "scope.h" +#include "mtype.h" +#include "template.h" +#include "id.h" + +FuncDeclaration *isFuncAddress(Expression *e, bool *hasOverloads = NULL); +Expression *semantic(Expression *e, Scope *sc); +Initializer *inferType(Initializer *init, Scope *sc); +Initializer *semantic(Initializer *init, Scope *sc, Type *t, NeedInterpret needInterpret); +bool hasNonConstPointers(Expression *e); + +class InitializerSemanticVisitor : public Visitor +{ +public: + Initializer *result; + Scope *sc; + Type *t; + NeedInterpret needInterpret; + + InitializerSemanticVisitor(Scope *sc, Type *t, NeedInterpret needInterpret) + { + this->result = NULL; + this->sc = sc; + this->t = t; + this->needInterpret = needInterpret; + } + + void visit(ErrorInitializer *i) + { + //printf("ErrorInitializer::semantic(t = %p)\n", t); + result = i; + } + + void visit(VoidInitializer *i) + { + //printf("VoidInitializer::semantic(t = %p)\n", t); + i->type = t; + result = i; + } + + void visit(StructInitializer *i) + { + //printf("StructInitializer::semantic(t = %s) %s\n", t->toChars(), toChars()); + t = t->toBasetype(); + if (t->ty == Tsarray && t->nextOf()->toBasetype()->ty == Tstruct) + t = t->nextOf()->toBasetype(); + if (t->ty == Tstruct) + { + StructDeclaration *sd = ((TypeStruct *)t)->sym; + if (sd->ctor) + { + error(i->loc, "%s %s has constructors, cannot use { initializers }, use %s( initializers ) instead", + sd->kind(), sd->toChars(), sd->toChars()); + result = new ErrorInitializer(); + return; + } + sd->size(i->loc); + if (sd->sizeok != SIZEOKdone) + { + result = new ErrorInitializer(); + return; + } + size_t nfields = sd->fields.dim - sd->isNested(); + + //expandTuples for non-identity arguments? + + Expressions *elements = new Expressions(); + elements->setDim(nfields); + for (size_t j = 0; j < elements->dim; j++) + (*elements)[j] = NULL; + + // Run semantic for explicitly given initializers + // TODO: this part is slightly different from StructLiteralExp::semantic. + bool errors = false; + for (size_t fieldi = 0, j = 0; j < i->field.dim; j++) + { + if (Identifier *id = i->field[j]) + { + Dsymbol *s = sd->search(i->loc, id); + if (!s) + { + s = sd->search_correct(id); + if (s) + error(i->loc, "'%s' is not a member of '%s', did you mean %s '%s'?", + id->toChars(), sd->toChars(), s->kind(), s->toChars()); + else + error(i->loc, "'%s' is not a member of '%s'", id->toChars(), sd->toChars()); + result = new ErrorInitializer(); + return; + } + s = s->toAlias(); + + // Find out which field index it is + for (fieldi = 0; 1; fieldi++) + { + if (fieldi >= nfields) + { + error(i->loc, "%s.%s is not a per-instance initializable field", + sd->toChars(), s->toChars()); + result = new ErrorInitializer(); + return; + } + if (s == sd->fields[fieldi]) + break; + } + } + else if (fieldi >= nfields) + { + error(i->loc, "too many initializers for %s", sd->toChars()); + result = new ErrorInitializer(); + return; + } + + VarDeclaration *vd = sd->fields[fieldi]; + if ((*elements)[fieldi]) + { + error(i->loc, "duplicate initializer for field '%s'", vd->toChars()); + errors = true; + continue; + } + for (size_t k = 0; k < nfields; k++) + { + VarDeclaration *v2 = sd->fields[k]; + if (vd->isOverlappedWith(v2) && (*elements)[k]) + { + error(i->loc, "overlapping initialization for field %s and %s", + v2->toChars(), vd->toChars()); + errors = true; + continue; + } + } + + assert(sc); + Initializer *iz = i->value[j]; + iz = ::semantic(iz, sc, vd->type->addMod(t->mod), needInterpret); + Expression *ex = initializerToExpression(iz); + if (ex->op == TOKerror) + { + errors = true; + continue; + } + i->value[j] = iz; + (*elements)[fieldi] = doCopyOrMove(sc, ex); + ++fieldi; + } + if (errors) + { + result = new ErrorInitializer(); + return; + } + + StructLiteralExp *sle = new StructLiteralExp(i->loc, sd, elements, t); + if (!sd->fill(i->loc, elements, false)) + { + result = new ErrorInitializer(); + return; + } + sle->type = t; + + ExpInitializer *ie = new ExpInitializer(i->loc, sle); + result = ::semantic(ie, sc, t, needInterpret); + return; + } + else if ((t->ty == Tdelegate || (t->ty == Tpointer && t->nextOf()->ty == Tfunction)) && i->value.dim == 0) + { + TOK tok = (t->ty == Tdelegate) ? TOKdelegate : TOKfunction; + /* Rewrite as empty delegate literal { } + */ + Parameters *parameters = new Parameters; + Type *tf = new TypeFunction(parameters, NULL, 0, LINKd); + FuncLiteralDeclaration *fd = new FuncLiteralDeclaration(i->loc, Loc(), tf, tok, NULL); + fd->fbody = new CompoundStatement(i->loc, new Statements()); + fd->endloc = i->loc; + Expression *e = new FuncExp(i->loc, fd); + ExpInitializer *ie = new ExpInitializer(i->loc, e); + result = ::semantic(ie, sc, t, needInterpret); + return; + } + + error(i->loc, "a struct is not a valid initializer for a %s", t->toChars()); + result = new ErrorInitializer(); + } + + void visit(ArrayInitializer *i) + { + unsigned length; + const unsigned amax = 0x80000000; + bool errors = false; + + //printf("ArrayInitializer::semantic(%s)\n", t->toChars()); + if (i->sem) // if semantic() already run + { + result = i; + return; + } + i->sem = true; + t = t->toBasetype(); + switch (t->ty) + { + case Tsarray: + case Tarray: + break; + + case Tvector: + t = ((TypeVector *)t)->basetype; + break; + + case Taarray: + case Tstruct: // consider implicit constructor call + { + Expression *e; + // note: MyStruct foo = [1:2, 3:4] is correct code if MyStruct has a this(int[int]) + if (t->ty == Taarray || i->isAssociativeArray()) + e = i->toAssocArrayLiteral(); + else + e = initializerToExpression(i); + if (!e) // Bugzilla 13987 + { + error(i->loc, "cannot use array to initialize %s", t->toChars()); + goto Lerr; + } + ExpInitializer *ei = new ExpInitializer(e->loc, e); + result = ::semantic(ei, sc, t, needInterpret); + return; + } + case Tpointer: + if (t->nextOf()->ty != Tfunction) + break; + /* fall through */ + + default: + error(i->loc, "cannot use array to initialize %s", t->toChars()); + goto Lerr; + } + + i->type = t; + + length = 0; + for (size_t j = 0; j < i->index.dim; j++) + { + Expression *idx = i->index[j]; + if (idx) + { + sc = sc->startCTFE(); + idx = ::semantic(idx, sc); + sc = sc->endCTFE(); + idx = idx->ctfeInterpret(); + i->index[j] = idx; + const uinteger_t idxvalue = idx->toInteger(); + if (idxvalue >= amax) + { + error(i->loc, "array index %llu overflow", (ulonglong)idxvalue); + errors = true; + } + length = (unsigned)idx->toInteger(); + if (idx->op == TOKerror) + errors = true; + } + + Initializer *val = i->value[j]; + ExpInitializer *ei = val->isExpInitializer(); + if (ei && !idx) + ei->expandTuples = true; + val = ::semantic(val, sc, t->nextOf(), needInterpret); + if (val->isErrorInitializer()) + errors = true; + + ei = val->isExpInitializer(); + // found a tuple, expand it + if (ei && ei->exp->op == TOKtuple) + { + TupleExp *te = (TupleExp *)ei->exp; + i->index.remove(j); + i->value.remove(j); + + for (size_t k = 0; k < te->exps->dim; ++k) + { + Expression *e = (*te->exps)[k]; + i->index.insert(j + k, (Expression *)NULL); + i->value.insert(j + k, new ExpInitializer(e->loc, e)); + } + j--; + continue; + } + else + { + i->value[j] = val; + } + + length++; + if (length == 0) + { + error(i->loc, "array dimension overflow"); + goto Lerr; + } + if (length > i->dim) + i->dim = length; + } + if (t->ty == Tsarray) + { + uinteger_t edim = ((TypeSArray *)t)->dim->toInteger(); + if (i->dim > edim) + { + error(i->loc, "array initializer has %u elements, but array length is %llu", i->dim, (ulonglong)edim); + goto Lerr; + } + } + if (errors) + goto Lerr; + else + { + d_uns64 sz = t->nextOf()->size(); + bool overflow = false; + const d_uns64 max = mulu((d_uns64)i->dim, sz, overflow); + if (overflow || max > amax) + { + error(i->loc, "array dimension %llu exceeds max of %llu", (ulonglong)i->dim, (ulonglong)(amax / sz)); + goto Lerr; + } + result = i; + return; + } + + Lerr: + result = new ErrorInitializer(); + } + + void visit(ExpInitializer *i) + { + //printf("ExpInitializer::semantic(%s), type = %s\n", i->exp->toChars(), t->toChars()); + if (needInterpret) sc = sc->startCTFE(); + i->exp = ::semantic(i->exp, sc); + i->exp = resolveProperties(sc, i->exp); + if (needInterpret) sc = sc->endCTFE(); + if (i->exp->op == TOKerror) + { + result = new ErrorInitializer(); + return; + } + + unsigned int olderrors = global.errors; + if (needInterpret) + { + // If the result will be implicitly cast, move the cast into CTFE + // to avoid premature truncation of polysemous types. + // eg real [] x = [1.1, 2.2]; should use real precision. + if (i->exp->implicitConvTo(t)) + { + i->exp = i->exp->implicitCastTo(sc, t); + } + if (!global.gag && olderrors != global.errors) + { + result = i; + return; + } + i->exp = i->exp->ctfeInterpret(); + } + else + { + i->exp = i->exp->optimize(WANTvalue); + } + if (!global.gag && olderrors != global.errors) + { + result = i; // Failed, suppress duplicate error messages + return; + } + + if (i->exp->type->ty == Ttuple && ((TypeTuple *)i->exp->type)->arguments->dim == 0) + { + Type *et = i->exp->type; + i->exp = new TupleExp(i->exp->loc, new Expressions()); + i->exp->type = et; + } + if (i->exp->op == TOKtype) + { + i->exp->error("initializer must be an expression, not '%s'", i->exp->toChars()); + result = new ErrorInitializer(); + return; + } + + // Make sure all pointers are constants + if (needInterpret && hasNonConstPointers(i->exp)) + { + i->exp->error("cannot use non-constant CTFE pointer in an initializer '%s'", i->exp->toChars()); + result = new ErrorInitializer(); + return; + } + + Type *tb = t->toBasetype(); + Type *ti = i->exp->type->toBasetype(); + + if (i->exp->op == TOKtuple && i->expandTuples && !i->exp->implicitConvTo(t)) + { + result = new ExpInitializer(i->loc, i->exp); + return; + } + + /* Look for case of initializing a static array with a too-short + * string literal, such as: + * char[5] foo = "abc"; + * Allow this by doing an explicit cast, which will lengthen the string + * literal. + */ + if (i->exp->op == TOKstring && tb->ty == Tsarray) + { + StringExp *se = (StringExp *)i->exp; + Type *typeb = se->type->toBasetype(); + TY tynto = tb->nextOf()->ty; + if (!se->committed && + (typeb->ty == Tarray || typeb->ty == Tsarray) && + (tynto == Tchar || tynto == Twchar || tynto == Tdchar) && + se->numberOfCodeUnits(tynto) < ((TypeSArray *)tb)->dim->toInteger()) + { + i->exp = se->castTo(sc, t); + goto L1; + } + } + + // Look for implicit constructor call + if (tb->ty == Tstruct && + !(ti->ty == Tstruct && tb->toDsymbol(sc) == ti->toDsymbol(sc)) && + !i->exp->implicitConvTo(t)) + { + StructDeclaration *sd = ((TypeStruct *)tb)->sym; + if (sd->ctor) + { + // Rewrite as S().ctor(exp) + Expression *e; + e = new StructLiteralExp(i->loc, sd, NULL); + e = new DotIdExp(i->loc, e, Id::ctor); + e = new CallExp(i->loc, e, i->exp); + e = ::semantic(e, sc); + if (needInterpret) + i->exp = e->ctfeInterpret(); + else + i->exp = e->optimize(WANTvalue); + } + } + + // Look for the case of statically initializing an array + // with a single member. + if (tb->ty == Tsarray && + !tb->nextOf()->equals(ti->toBasetype()->nextOf()) && + i->exp->implicitConvTo(tb->nextOf()) + ) + { + /* If the variable is not actually used in compile time, array creation is + * redundant. So delay it until invocation of toExpression() or toDt(). + */ + t = tb->nextOf(); + } + + if (i->exp->implicitConvTo(t)) + { + i->exp = i->exp->implicitCastTo(sc, t); + } + else + { + // Look for mismatch of compile-time known length to emit + // better diagnostic message, as same as AssignExp::semantic. + if (tb->ty == Tsarray && + i->exp->implicitConvTo(tb->nextOf()->arrayOf()) > MATCHnomatch) + { + uinteger_t dim1 = ((TypeSArray *)tb)->dim->toInteger(); + uinteger_t dim2 = dim1; + if (i->exp->op == TOKarrayliteral) + { + ArrayLiteralExp *ale = (ArrayLiteralExp *)i->exp; + dim2 = ale->elements ? ale->elements->dim : 0; + } + else if (i->exp->op == TOKslice) + { + Type *tx = toStaticArrayType((SliceExp *)i->exp); + if (tx) + dim2 = ((TypeSArray *)tx)->dim->toInteger(); + } + if (dim1 != dim2) + { + i->exp->error("mismatched array lengths, %d and %d", (int)dim1, (int)dim2); + i->exp = new ErrorExp(); + } + } + i->exp = i->exp->implicitCastTo(sc, t); + } + L1: + if (i->exp->op == TOKerror) + { + result = i; + return; + } + if (needInterpret) + i->exp = i->exp->ctfeInterpret(); + else + i->exp = i->exp->optimize(WANTvalue); + //printf("-ExpInitializer::semantic(): "); i->exp->print(); + result = i; + } +}; + +// Performs semantic analisys on Initializer AST nodes +Initializer *semantic(Initializer *init, Scope *sc, Type *t, NeedInterpret needInterpret) +{ + InitializerSemanticVisitor v = InitializerSemanticVisitor(sc, t, needInterpret); + init->accept(&v); + return v.result; +} + +class InferTypeVisitor : public Visitor +{ +public: + Initializer *result; + Scope *sc; + + InferTypeVisitor(Scope *sc) + { + this->result = NULL; + this->sc = sc; + } + + void visit(ErrorInitializer *i) + { + result = i; + } + + void visit(VoidInitializer *i) + { + error(i->loc, "cannot infer type from void initializer"); + result = new ErrorInitializer(); + } + + void visit(StructInitializer *i) + { + error(i->loc, "cannot infer type from struct initializer"); + result = new ErrorInitializer(); + } + + void visit(ArrayInitializer *init) + { + //printf("ArrayInitializer::inferType() %s\n", init->toChars()); + Expressions *keys = NULL; + Expressions *values; + if (init->isAssociativeArray()) + { + keys = new Expressions(); + keys->setDim(init->value.dim); + values = new Expressions(); + values->setDim(init->value.dim); + + for (size_t i = 0; i < init->value.dim; i++) + { + Expression *e = init->index[i]; + if (!e) + goto Lno; + (*keys)[i] = e; + + Initializer *iz = init->value[i]; + if (!iz) + goto Lno; + iz = inferType(iz, sc); + if (iz->isErrorInitializer()) + { + result = iz; + return; + } + assert(iz->isExpInitializer()); + (*values)[i] = ((ExpInitializer *)iz)->exp; + assert((*values)[i]->op != TOKerror); + } + + Expression *e = new AssocArrayLiteralExp(init->loc, keys, values); + ExpInitializer *ei = new ExpInitializer(init->loc, e); + result = inferType(ei, sc); + return; + } + else + { + Expressions *elements = new Expressions(); + elements->setDim(init->value.dim); + elements->zero(); + + for (size_t i = 0; i < init->value.dim; i++) + { + assert(!init->index[i]); // already asserted by isAssociativeArray() + + Initializer *iz = init->value[i]; + if (!iz) + goto Lno; + iz = inferType(iz, sc); + if (iz->isErrorInitializer()) + { + result = iz; + return; + } + assert(iz->isExpInitializer()); + (*elements)[i] = ((ExpInitializer *)iz)->exp; + assert((*elements)[i]->op != TOKerror); + } + + Expression *e = new ArrayLiteralExp(init->loc, NULL, elements); + ExpInitializer *ei = new ExpInitializer(init->loc, e); + result = inferType(ei, sc); + return; + } + Lno: + if (keys) + { + delete keys; + delete values; + error(init->loc, "not an associative array initializer"); + } + else + { + error(init->loc, "cannot infer type from array initializer"); + } + result = new ErrorInitializer(); + } + + void visit(ExpInitializer *init) + { + //printf("ExpInitializer::inferType() %s\n", init->toChars()); + init->exp = ::semantic(init->exp, sc); + init->exp = resolveProperties(sc, init->exp); + + if (init->exp->op == TOKscope) + { + ScopeExp *se = (ScopeExp *)init->exp; + TemplateInstance *ti = se->sds->isTemplateInstance(); + if (ti && ti->semanticRun == PASSsemantic && !ti->aliasdecl) + se->error("cannot infer type from %s %s, possible circular dependency", se->sds->kind(), se->toChars()); + else + se->error("cannot infer type from %s %s", se->sds->kind(), se->toChars()); + result = new ErrorInitializer(); + return; + } + + // Give error for overloaded function addresses + bool hasOverloads = false; + if (FuncDeclaration *f = isFuncAddress(init->exp, &hasOverloads)) + { + if (f->checkForwardRef(init->loc)) + { + result = new ErrorInitializer(); + return; + } + + if (hasOverloads && !f->isUnique()) + { + init->exp->error("cannot infer type from overloaded function symbol %s", init->exp->toChars()); + result = new ErrorInitializer(); + return; + } + } + if (init->exp->op == TOKaddress) + { + AddrExp *ae = (AddrExp *)init->exp; + if (ae->e1->op == TOKoverloadset) + { + init->exp->error("cannot infer type from overloaded function symbol %s", init->exp->toChars()); + result = new ErrorInitializer(); + return; + } + } + + if (init->exp->op == TOKerror) + { + result = new ErrorInitializer(); + return; + } + if (!init->exp->type) + { + result = new ErrorInitializer(); + return; + } + result = init; + } +}; + +/* Translates to an expression to infer type. + * Returns ExpInitializer or ErrorInitializer. + */ +Initializer *inferType(Initializer *init, Scope *sc) +{ + InferTypeVisitor v = InferTypeVisitor(sc); + init->accept(&v); + return v.result; +} + +class InitToExpressionVisitor : public Visitor +{ +public: + Expression *result; + Type *itype; + + InitToExpressionVisitor(Type *itype) + { + this->result = NULL; + this->itype = itype; + } + + void visit(ErrorInitializer *) + { + result = new ErrorExp(); + } + + void visit(VoidInitializer *) + { + result = NULL; + } + + /*************************************** + * This works by transforming a struct initializer into + * a struct literal. In the future, the two should be the + * same thing. + */ + void visit(StructInitializer *) + { + // cannot convert to an expression without target 'ad' + result = NULL; + } + + /******************************** + * If possible, convert array initializer to array literal. + * Otherwise return NULL. + */ + + void visit(ArrayInitializer *init) + { + //printf("ArrayInitializer::toExpression(), dim = %d\n", init->dim); + //static int i; if (++i == 2) halt(); + + Expressions *elements; + unsigned edim; + const unsigned amax = 0x80000000; + Type *t = NULL; + if (init->type) + { + if (init->type == Type::terror) + { + result = new ErrorExp(); + return; + } + + t = init->type->toBasetype(); + switch (t->ty) + { + case Tvector: + t = ((TypeVector *)t)->basetype; + /* fall through */ + + case Tsarray: + { + uinteger_t adim = ((TypeSArray *)t)->dim->toInteger(); + if (adim >= amax) + goto Lno; + edim = (unsigned)adim; + break; + } + + case Tpointer: + case Tarray: + edim = init->dim; + break; + + default: + assert(0); + } + } + else + { + edim = (unsigned)init->value.dim; + for (size_t i = 0, j = 0; i < init->value.dim; i++, j++) + { + if (init->index[i]) + { + if (init->index[i]->op == TOKint64) + { + const uinteger_t idxval = init->index[i]->toInteger(); + if (idxval >= amax) + goto Lno; + j = (size_t)idxval; + } + else + goto Lno; + } + if (j >= edim) + edim = (unsigned)(j + 1); + } + } + + elements = new Expressions(); + elements->setDim(edim); + elements->zero(); + for (size_t i = 0, j = 0; i < init->value.dim; i++, j++) + { + if (init->index[i]) + j = (size_t)(init->index[i])->toInteger(); + assert(j < edim); + Initializer *iz = init->value[i]; + if (!iz) + goto Lno; + Expression *ex = initializerToExpression(iz); + if (!ex) + { + goto Lno; + } + (*elements)[j] = ex; + } + + /* Fill in any missing elements with the default initializer + */ + { + Expression *_init = NULL; + for (size_t i = 0; i < edim; i++) + { + if (!(*elements)[i]) + { + if (!init->type) + goto Lno; + if (!_init) + _init = ((TypeNext *)t)->next->defaultInit(); + (*elements)[i] = _init; + } + } + + /* Expand any static array initializers that are a single expression + * into an array of them + */ + if (t) + { + Type *tn = t->nextOf()->toBasetype(); + if (tn->ty == Tsarray) + { + size_t dim = ((TypeSArray *)tn)->dim->toInteger(); + Type *te = tn->nextOf()->toBasetype(); + for (size_t i = 0; i < elements->dim; i++) + { + Expression *e = (*elements)[i]; + if (te->equals(e->type)) + { + Expressions *elements2 = new Expressions(); + elements2->setDim(dim); + for (size_t j = 0; j < dim; j++) + (*elements2)[j] = e; + e = new ArrayLiteralExp(e->loc, tn, elements2); + (*elements)[i] = e; + } + } + } + } + + /* If any elements are errors, then the whole thing is an error + */ + for (size_t i = 0; i < edim; i++) + { + Expression *e = (*elements)[i]; + if (e->op == TOKerror) + { + result = e; + return; + } + } + + Expression *e = new ArrayLiteralExp(init->loc, init->type, elements); + result = e; + return; + } + + Lno: + result = NULL; + } + + void visit(ExpInitializer *i) + { + if (itype) + { + //printf("ExpInitializer::toExpression(t = %s) exp = %s\n", itype->toChars(), i->exp->toChars()); + Type *tb = itype->toBasetype(); + Expression *e = (i->exp->op == TOKconstruct || i->exp->op == TOKblit) ? ((AssignExp *)i->exp)->e2 : i->exp; + if (tb->ty == Tsarray && e->implicitConvTo(tb->nextOf())) + { + TypeSArray *tsa = (TypeSArray *)tb; + size_t d = (size_t)tsa->dim->toInteger(); + Expressions *elements = new Expressions(); + elements->setDim(d); + for (size_t i = 0; i < d; i++) + (*elements)[i] = e; + ArrayLiteralExp *ae = new ArrayLiteralExp(e->loc, itype, elements); + result = ae; + return; + } + } + result = i->exp; + } +}; + +Expression *initializerToExpression(Initializer *i, Type *t) +{ + InitToExpressionVisitor v = InitToExpressionVisitor(t); + i->accept(&v); + return v.result; +}