From f3b5817a3542f4fa4eb4a6e70658854e0d8e4aa3 Mon Sep 17 00:00:00 2001 From: k-hara Date: Mon, 21 Jan 2013 00:48:24 +0900 Subject: [PATCH] Separate returned value isolation check from purity level calculation --- src/cast.c | 2 +- src/declaration.h | 2 + src/func.c | 162 ++++++++++++++++++++++++++ src/statement.c | 10 +- test/fail_compilation/testInference.d | 93 +++++++++++++++ test/runnable/testconst.d | 77 ++++++++++++ 6 files changed, 341 insertions(+), 5 deletions(-) diff --git a/src/cast.c b/src/cast.c index 26caaf69080b..800a5b60f6c8 100644 --- a/src/cast.c +++ b/src/cast.c @@ -638,7 +638,7 @@ MATCH CallExp::implicitConvTo(Type *t) /* Allow the result of strongly pure functions to * convert to immutable */ - if (f && f->isPure() == PUREstrong && !f->type->hasWild()) + if (f && f->isolateReturn()) return type->invariantOf()->implicitConvTo(t); return MATCHnomatch; diff --git a/src/declaration.h b/src/declaration.h index 1d5930449137..658b18bdc1e2 100644 --- a/src/declaration.h +++ b/src/declaration.h @@ -683,6 +683,8 @@ struct FuncDeclaration : Declaration bool isSafeBypassingInference(); int isTrusted(); bool setUnsafe(); + bool isolateReturn(); + bool parametersIntersect(Type *t); virtual int isNested(); int needThis(); int isVirtualMethod(); diff --git a/src/func.c b/src/func.c index 4f871126f71d..57e25a8ce814 100644 --- a/src/func.c +++ b/src/func.c @@ -3120,6 +3120,168 @@ bool FuncDeclaration::setUnsafe() return FALSE; } +/************************************** + * Returns an indirect type one step from t. + */ + +Type *getIndirection(Type *t) +{ + t = t->toBasetype(); + + if (t->ty == Tsarray) + { while (t->ty == Tsarray) + t = t->nextOf()->toBasetype(); + } + if (t->ty == Tarray || t->ty == Tpointer) + return t->nextOf()->toBasetype(); + if (t->ty == Taarray || t->ty == Tclass) + return t; + if (t->ty == Tstruct) + return t->hasPointers() ? t : NULL; // TODO + + // should consider TypeDelegate? + return NULL; +} + +/************************************** + * Traverse this and t, and then check the indirections convertibility. + */ + +int traverseIndirections(Type *ta, Type *tb, void *p = NULL, bool a2b = true) +{ + if (a2b) // check ta appears in tb + { + //printf("\ttraverse(1) %s appears in %s\n", ta->toChars(), tb->toChars()); + if (ta->constConv(tb)) + return 1; + else if (ta->invariantOf()->equals(tb->invariantOf())) + return 0; + } + else // check tb appears in ta + { + //printf("\ttraverse(2) %s appears in %s\n", tb->toChars(), ta->toChars()); + if (tb->constConv(ta)) + return 1; + else if (tb->invariantOf()->equals(ta->invariantOf())) + return 0; + } + + // context date to detect circular look up + struct Ctxt + { + Ctxt *prev; + Type *type; + }; + Ctxt *ctxt = (Ctxt *)p; + + Type *tbb = tb->toBasetype(); + if (tbb != tb) + return traverseIndirections(ta, tbb, ctxt, a2b); + + if (tb->ty == Tsarray) + { while (tb->toBasetype()->ty == Tsarray) + tb = tb->toBasetype()->nextOf(); + } + if (tb->ty == Tclass || tb->ty == Tstruct) + { + for (Ctxt *c = ctxt; c; c = c->prev) + if (tb == c->type) return 0; + Ctxt c; + c.prev = ctxt; + c.type = tb; + + AggregateDeclaration *sym = tb->toDsymbol(NULL)->isAggregateDeclaration(); + for (size_t i = 0; i < sym->fields.dim; i++) + { + VarDeclaration *v = sym->fields[i]; + Type *tprmi = v->type->addMod(tb->mod); + if (!(v->storage_class & STCref)) + tprmi = getIndirection(tprmi); + if (!tprmi) + continue; + + //printf("\ttb = %s, tprmi = %s\n", tb->toChars(), tprmi->toChars()); + if (traverseIndirections(ta, tprmi, &c, a2b)) + return 1; + } + } + else if (tb->ty == Tarray || tb->ty == Taarray || tb->ty == Tpointer) + { + Type *tind = tb->nextOf(); + if (traverseIndirections(ta, tind, ctxt, a2b)) + return 1; + } + else if (tb->hasPointers()) + { + // FIXME: function pointer/delegate types should be considered. + return 1; + } + if (a2b) + return traverseIndirections(tb, ta, ctxt, false); + + return 0; +} + +/******************************************** + * Returns true if the function return value has no indirection + * which comes from the parameters. + */ + +bool FuncDeclaration::isolateReturn() +{ + assert(type->ty == Tfunction); + TypeFunction *tf = (TypeFunction *)type; + assert(tf->next); + + Type *treti = tf->next; + treti = tf->isref ? treti : getIndirection(treti); + if (!treti) + return true; // target has no mutable indirection + return parametersIntersect(treti); +} + +/******************************************** + * Returns true if an object typed t can have indirections + * which come from the parameters. + */ + +bool FuncDeclaration::parametersIntersect(Type *t) +{ + assert(t); + if (!isPureBypassingInference() || isNested()) + return false; + + assert(type->ty == Tfunction); + TypeFunction *tf = (TypeFunction *)type; + + //printf("parametersIntersect(%s) t = %s\n", tf->toChars(), t->toChars()); + + size_t dim = Parameter::dim(tf->parameters); + for (size_t i = 0; i < dim; i++) + { + Parameter *fparam = Parameter::getNth(tf->parameters, i); + if (!fparam->type) + continue; + Type *tprmi = (fparam->storageClass & (STClazy | STCout | STCref)) + ? fparam->type : getIndirection(fparam->type); + if (!tprmi) + continue; // there is no mutable indirection + + //printf("\t[%d] tprmi = %d %s\n", i, tprmi->ty, tprmi->toChars()); + if (traverseIndirections(tprmi, t)) + return false; + } + if (AggregateDeclaration *ad = isThis()) + { + Type *tthis = ad ? ad->getType()->addMod(tf->mod) : NULL; + //printf("\ttthis = %s\n", tthis->toChars()); + if (traverseIndirections(tthis, t)) + return false; + } + + return true; +} + // Determine if function needs // a static frame pointer to its lexically enclosing function diff --git a/src/statement.c b/src/statement.c index faebda0e600f..5a702998d8b4 100644 --- a/src/statement.c +++ b/src/statement.c @@ -3922,11 +3922,13 @@ Statement *ReturnStatement::semantic(Scope *sc) } else if (tbret->ty != Tvoid) { - if (fd->isPureBypassingInference() == PUREstrong && - !exp->type->implicitConvTo(tret) && - exp->type->invariantOf()->implicitConvTo(tret)) + if (!exp->type->implicitConvTo(tret) && + fd->parametersIntersect(exp->type)) { - exp = exp->castTo(sc, exp->type->invariantOf()); + if (exp->type->invariantOf()->implicitConvTo(tret)) + exp = exp->castTo(sc, exp->type->invariantOf()); + else if (exp->type->wildOf()->implicitConvTo(tret)) + exp = exp->castTo(sc, exp->type->wildOf()); } if (fd->tintro) exp = exp->implicitCastTo(sc, fd->type->nextOf()); diff --git a/test/fail_compilation/testInference.d b/test/fail_compilation/testInference.d index a7ab14f5f1fc..2b0eb1d56a3e 100644 --- a/test/fail_compilation/testInference.d +++ b/test/fail_compilation/testInference.d @@ -24,3 +24,96 @@ class C8998 return a; // should be error } } + +/* +TEST_OUTPUT: +--- +fail_compilation/testInference.d(39): Error: cannot implicitly convert expression (s) of type const(char[]) to string +fail_compilation/testInference.d(44): Error: cannot implicitly convert expression (a) of type int[] to immutable(int[]) +fail_compilation/testInference.d(49): Error: cannot implicitly convert expression (a) of type int[] to immutable(int[]) +fail_compilation/testInference.d(54): Error: cannot implicitly convert expression (a) of type int[] to immutable(int[]) +--- +*/ +string foo(in char[] s) pure +{ + return s; // +} +immutable(int[]) x1() /*pure*/ +{ + int[] a = new int[](10); + return a; // +} +immutable(int[]) x2(int len) /*pure*/ +{ + int[] a = new int[](len); + return a; +} +immutable(int[]) x3(immutable(int[]) org) /*pure*/ +{ + int[] a = new int[](org.length); + return a; // +} + + +/* +TEST_OUTPUT: +--- +fail_compilation/testInference.d(94): Error: cannot implicitly convert expression (c) of type testInference.C1 to immutable(C1) +fail_compilation/testInference.d(95): Error: cannot implicitly convert expression (c) of type testInference.C1 to immutable(C1) +fail_compilation/testInference.d(96): Error: cannot implicitly convert expression (c) of type testInference.C3 to immutable(C3) +fail_compilation/testInference.d(97): Error: cannot implicitly convert expression (c) of type testInference.C3 to immutable(C3) +fail_compilation/testInference.d(100): Error: undefined identifier X1, did you mean function x1? +fail_compilation/testInference.d(106): Error: cannot implicitly convert expression (s) of type S1 to immutable(S1) +fail_compilation/testInference.d(109): Error: cannot implicitly convert expression (a) of type int*[] to immutable(int*[]) +fail_compilation/testInference.d(110): Error: cannot implicitly convert expression (a) of type const(int)*[] to immutable(int*[]) +fail_compilation/testInference.d(114): Error: cannot implicitly convert expression (s) of type S2 to immutable(S2) +fail_compilation/testInference.d(115): Error: cannot implicitly convert expression (s) of type S2 to immutable(S2) +fail_compilation/testInference.d(116): Error: cannot implicitly convert expression (s) of type S2 to immutable(S2) +fail_compilation/testInference.d(118): Error: cannot implicitly convert expression (a) of type const(int)*[] to immutable(int*[]) +--- +*/ +immutable(Object) get(inout int*) pure +{ + auto o = new Object; + return o; // should be ok +} +immutable(Object) get(immutable Object) pure +{ + auto o = new Object; + return o; // should be ok +} +inout(Object) get(inout Object) pure +{ + auto o = new Object; + return o; // should be ok (, but cannot in current conservative rule) +} + +class C1 { C2 c2; } +class C2 { C3 c3; } +class C3 { C1 c1; } +immutable(C1) recursive1(C3 pc) pure { auto c = new C1(); return c; } // should be error, because pc.c1 == C1 +immutable(C1) recursive2(C2 pc) pure { auto c = new C1(); return c; } // should be error, because pc.c3.c1 == C1 +immutable(C3) recursive3(C1 pc) pure { auto c = new C3(); return c; } // should be error, c.c1 may be pc +immutable(C3) recursive4(C2 pc) pure { auto c = new C3(); return c; } // should be error, c.c1.c2 may be pc +immutable(C1) recursive5(shared C2 pc) pure { auto c = new C1(); return c; } +immutable(C1) recursive6(immutable C2 pc) pure { auto c = new C1(); return c; } +immutable(C1) recursiveE(immutable C2 pc) pure { auto c = new X1(); return c; } + +class C4 { C4[] arr; } +immutable(C4)[] recursive7(int[] arr) pure { auto a = new C4[](1); return a; } + +struct S1 { int* ptr; } +immutable(S1) foo1a( int*[] prm) pure { S1 s; return s; } // NG +immutable(S1) foo1b( const int*[] prm) pure { S1 s; return s; } // OK +immutable(S1) foo1c(immutable int*[] prm) pure { S1 s; return s; } // OK +immutable(int*[]) bar1a( S1 prm) pure { int *[] a; return a; } // NG +immutable(int*[]) bar1b( S1 prm) pure { const(int)*[] a; return a; } // NG +immutable(int*[]) bar1c( S1 prm) pure { immutable(int)*[] a; return a; } // OK + +struct S2 { const(int)* ptr; } +immutable(S2) foo2a( int*[] prm) pure { S2 s; return s; } // OK +immutable(S2) foo2b( const int*[] prm) pure { S2 s; return s; } // NG +immutable(S2) foo2c(immutable int*[] prm) pure { S2 s; return s; } // NG +immutable(int*[]) bar2a( S2 prm) pure { int *[] a; return a; } // OK +immutable(int*[]) bar2b( S2 prm) pure { const(int)*[] a; return a; } // NG +immutable(int*[]) bar2c( S2 prm) pure { immutable(int)*[] a; return a; } // OK diff --git a/test/runnable/testconst.d b/test/runnable/testconst.d index 23b53979c3d3..40f07136aaa0 100644 --- a/test/runnable/testconst.d +++ b/test/runnable/testconst.d @@ -2743,6 +2743,82 @@ void test8212() S8212 s2 = s8212; } +/************************************/ +// 8408 + +template hasMutableIndirection8408(T) +{ + template Unqual(T) + { + static if (is(T U == shared(const U))) alias U Unqual; + else static if (is(T U == const U )) alias U Unqual; + else static if (is(T U == immutable U )) alias U Unqual; + else static if (is(T U == inout U )) alias U Unqual; + else static if (is(T U == shared U )) alias U Unqual; + else alias T Unqual; + } + + enum hasMutableIndirection8408 = !is(typeof({ Unqual!T t = void; immutable T u = t; })); +} +static assert(!hasMutableIndirection8408!(int)); +static assert(!hasMutableIndirection8408!(int[3])); +static assert( hasMutableIndirection8408!(Object)); + +auto dup8408(E)(inout(E)[] arr) pure @trusted +{ + static if (hasMutableIndirection8408!E) + { + auto copy = new E[](arr.length); + copy[] = cast(E[])arr[]; // assume constant + return cast(inout(E)[])copy; // assume constant + } + else + { + auto copy = new E[](arr.length); + copy[] = arr[]; + return copy; + } +} + +void test8408() +{ + void test(E, bool constConv)() + { + E[] marr = [E.init, E.init, E.init]; + immutable E[] iarr = [E.init, E.init, E.init]; + + E[] m2m = marr.dup8408(); assert(m2m == marr); + immutable E[] i2i = iarr.dup8408(); assert(i2i == iarr); + + static if (constConv) + { // If dup() hss strong purity, implicit conversion is allowed + immutable E[] m2i = marr.dup8408(); assert(m2i == marr); + E[] i2m = iarr.dup8408(); assert(i2m == iarr); + } + else + { + static assert(!is(typeof({ immutable E[] m2i = marr.dup8408(); }))); + static assert(!is(typeof({ E[] i2m = iarr.dup8408(); }))); + } + } + + class C {} + struct S1 { long n; } + struct S2 { int* p; } + struct T1 { S1 s; } + struct T2 { S2 s; } + struct T3 { S1 s1; S2 s2; } + + test!(int , true )(); + test!(int[3], true )(); + test!(C , false)(); + test!(S1 , true )(); + test!(S2 , false)(); + test!(T1 , true )(); + test!(T2 , false)(); + test!(T3 , false)(); +} + /************************************/ // 8688 @@ -2925,6 +3001,7 @@ int main() test8099(); test8201(); test8212(); + test8408(); test8688(); test9046(); test9090();