Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Refactoring/improvement of issue 8408, and additionally fixing Issue 8998 #1519

Merged
merged 4 commits into from

2 participants

@9rnsr
Collaborator

Essentially, there is no relation between PUREstrong and the isolation of returned value.
I had misunderstood about that, and mis-implemented it in pull #1110 for issue 8408.
So this also is a refactoring change of #1110.

Additional fix:
Issue 8998 - 'inout pure' returns immutable, which in reality is mutable

By fixing issue 7769 (relax inout rule), we cannot assume a function with inout parameters as PUREstrong,
because the return type may not have any inout indirections.

Requires: #1512

9rnsr added some commits
@9rnsr 9rnsr Revert 9rnsr/fix9230 and 9rnsr/fix8408
- Revert "Merge pull request #1418 from 9rnsr/fix9230"

  This reverts commit c42d35b, reversing
  changes made to 9f2d9ea.

- Revert "Merge pull request #1110 from 9rnsr/fix8408"

  This reverts commit b6a8093, reversing
  changes made to 3b06132.
b65d12a
@9rnsr 9rnsr Remove unused code which will never be activated
0a57b20
@9rnsr 9rnsr fix Issue 8998 - 'inout pure' returns immutable, which in reality is …
…mutable

By fixing issue 7769 (relax inout rule), we cannot assume a function with inout parameters as PUREstrong, because the return type may not have any inout indirections.
49b4d94
@9rnsr 9rnsr Separate returned value isolation check from purity level calculation f3b5817
@WalterBright WalterBright merged commit ee06e4a into from
@ghost Unknown referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
@ghost Unknown referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
@ghost Unknown referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
@ghost Unknown referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 5, 2013
  1. @9rnsr

    Revert 9rnsr/fix9230 and 9rnsr/fix8408

    9rnsr authored
    - Revert "Merge pull request #1418 from 9rnsr/fix9230"
    
      This reverts commit c42d35b, reversing
      changes made to 9f2d9ea.
    
    - Revert "Merge pull request #1110 from 9rnsr/fix8408"
    
      This reverts commit b6a8093, reversing
      changes made to 3b06132.
  2. @9rnsr
  3. @9rnsr

    fix Issue 8998 - 'inout pure' returns immutable, which in reality is …

    9rnsr authored
    …mutable
    
    By fixing issue 7769 (relax inout rule), we cannot assume a function with inout parameters as PUREstrong, because the return type may not have any inout indirections.
  4. @9rnsr
This page is out of date. Refresh to see the latest.
View
2  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;
View
2  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();
View
172 src/func.c
@@ -1022,8 +1022,6 @@ void FuncDeclaration::semantic3(Scope *sc)
arg->ident = id = Identifier::generateId("_param_", i);
}
Type *vtype = arg->type;
- //if (isPure())
- //vtype = vtype->addMod(MODconst);
VarDeclaration *v = new VarDeclaration(loc, vtype, id, NULL);
//printf("declaring parameter %s of type %s\n", v->toChars(), v->type->toChars());
v->storage_class |= STCparameter;
@@ -1798,8 +1796,6 @@ VarDeclaration *FuncDeclaration::declareThis(Scope *sc, AggregateDeclaration *ad
Type *thandle = ad->handle;
thandle = thandle->addMod(type->mod);
thandle = thandle->addStorageClass(storage_class);
- //if (isPure())
- //thandle = thandle->addMod(MODconst);
v = new ThisDeclaration(loc, thandle);
//v = new ThisDeclaration(loc, isCtorDeclaration() ? ad->handle : thandle);
v->storage_class |= STCparameter;
@@ -3046,9 +3042,9 @@ enum PURE FuncDeclaration::isPure()
purity = PUREweak;
if (purity > PUREweak && needThis())
{ // The attribute of the 'this' reference affects purity strength
- if (type->mod & (MODimmutable | MODwild))
+ if (type->mod & MODimmutable)
;
- else if (type->mod & MODconst && purity >= PUREconst)
+ else if (type->mod & (MODconst | MODwild) && purity >= PUREconst)
purity = PUREconst;
else
purity = PUREweak;
@@ -3063,8 +3059,6 @@ enum PURE FuncDeclaration::isPureBypassingInference()
{
if (flags & FUNCFLAGpurityInprocess)
return PUREfwdref;
- else if (type->nextOf() == NULL)
- return PUREfwdref;
else
return isPure();
}
@@ -3126,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
View
174 src/mtype.c
@@ -5778,168 +5778,78 @@ Type *TypeFunction::semantic(Loc loc, Scope *sc)
}
-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;
-}
-
/********************************************
* Do this lazily, as the parameter types might be forward referenced.
*/
void TypeFunction::purityLevel()
{
- //printf("purityLevel(%s)\n", toChars());
-
TypeFunction *tf = this;
- if (tf->purity == PUREfwdref && tf->next)
+ if (tf->purity == PUREfwdref)
{ /* Evaluate what kind of purity based on the modifiers for the parameters
*/
- enum PURE purity = PUREstrong; // assume strong until something weakens it
- size_t dim = Parameter::dim(tf->parameters);
-
- if (dim)
+ tf->purity = PUREstrong; // assume strong until something weakens it
+ if (tf->parameters)
{
- Type *tret = tf->next;
- assert(tret);
- Type *treti = tf->isref ? tret->toBasetype() : getIndirection(tret);
- if (treti && (treti->mod & MODimmutable))
- treti = NULL; // indirection is immutable
- //printf(" tret = %s, treti = %s\n", tret->toChars(), treti ? treti->toChars() : "NULL");
-
+ size_t dim = Parameter::dim(tf->parameters);
for (size_t i = 0; i < dim; i++)
{ Parameter *fparam = Parameter::getNth(tf->parameters, i);
if (fparam->storageClass & STClazy)
{
- purity = PUREweak;
+ tf->purity = PUREweak;
break;
}
if (fparam->storageClass & STCout)
{
- purity = PUREweak;
+ tf->purity = PUREweak;
break;
}
if (!fparam->type)
continue;
-
- Type *tprm = fparam->type;
- Type *tprmi = fparam->storageClass & STCref ? tprm->toBasetype() : getIndirection(tprm);
- //printf(" [%d] tprm = %s, tprmi = %s\n", i, tprm->toChars(), tprmi ? tprmi->toChars() : "NULL");
-
- if (!tprmi || (tprmi->mod & MODimmutable))
- continue; // there is no mutable indirection
- if (tprmi->isMutable())
- { purity = PUREweak; // indirection is mutable
- break;
+ if (fparam->storageClass & STCref)
+ {
+ if (!(fparam->type->mod & (MODconst | MODimmutable | MODwild)))
+ { tf->purity = PUREweak;
+ break;
+ }
+ if (fparam->type->mod & MODconst)
+ { tf->purity = PUREconst;
+ continue;
+ }
}
- if (!treti)
- continue; // mutable indirection is never returned
-
- if (purity < PUREstrong)
+ Type *t = fparam->type->toBasetype();
+ if (!t->hasPointers())
continue;
-
- // Determine the parameter is really PUREconst or not
- assert(tprmi->mod & (MODconst | MODwild));
- if (tprmi->constConv(treti)) // simple case
- purity = PUREconst;
- else if (tprmi->invariantOf()->equals(treti->invariantOf()))
+ if (t->mod & MODimmutable)
+ continue;
+ /* The rest of this is too strict; fix later.
+ * For example, the only pointer members of a struct may be immutable,
+ * which would maintain strong purity.
+ */
+ if (t->mod & (MODconst | MODwild))
+ { tf->purity = PUREconst;
continue;
- else
- {
- /* The rest of this is little strict; fix later.
- * For example:
- *
- * struct S { immutable* p; }
- * pure S foo(const int* p);
- *
- * which would maintain strong purity.
- */
- if (tprmi->hasPointers() || treti->hasPointers())
- purity = PUREconst;
}
-
+ Type *tn = t->nextOf();
+ if (tn)
+ { tn = tn->toBasetype();
+ if (tn->ty == Tpointer || tn->ty == Tarray)
+ { /* Accept immutable(T)* and immutable(T)[] as being strongly pure
+ */
+ if (tn->mod & MODimmutable)
+ continue;
+ if (tn->mod & (MODconst | MODwild))
+ { tf->purity = PUREconst;
+ continue;
+ }
+ }
+ }
/* Should catch delegates and function pointers, and fold in their purity
*/
+ tf->purity = PUREweak; // err on the side of too strict
+ break;
}
}
-
- //printf(" --> purity: %d\n", purity);
- tf->purity = purity;
- }
-}
-
-/********************************************
- * FIXME: This function is a workaround for fixing Bugzilla 9210.
- * In 2.061, TypeFunction::purityLevel() improved to make more functions
- * strong purity, but immutable conversion on return statemet had broken by that.
- * Because, it is essentially unrelated to PUREstrong. This function is
- * necessary to check the convertibility.
- */
-bool TypeFunction::hasMutableIndirectionParams()
-{
- TypeFunction *tf = this;
- size_t dim = Parameter::dim(tf->parameters);
- for (size_t i = 0; i < dim; i++)
- {
- Parameter *fparam = Parameter::getNth(tf->parameters, i);
- if (fparam->storageClass & STClazy)
- {
- return true;
- }
- if (fparam->storageClass & STCout)
- {
- return true;
- }
- if (!fparam->type)
- continue;
- if (fparam->storageClass & STCref)
- {
- if (!(fparam->type->mod & (MODconst | MODimmutable | MODwild)))
- return true;
- if (fparam->type->mod & MODconst)
- return true;
- }
- Type *t = fparam->type->toBasetype();
- if (!t->hasPointers())
- continue;
- if (t->mod & (MODimmutable | MODwild))
- continue;
- /* The rest of this is too strict; fix later.
- * For example, the only pointer members of a struct may be immutable,
- * which would maintain strong purity.
- */
- if (t->mod & MODconst)
- return true;
- Type *tn = t->nextOf();
- if (tn)
- { tn = tn->toBasetype();
- if (tn->ty == Tpointer || tn->ty == Tarray)
- { /* Accept immutable(T)* and immutable(T)[] as being strongly pure
- */
- if (tn->mod & (MODimmutable | MODwild))
- continue;
- if (tn->mod & MODconst)
- return true;
- }
- }
- /* Should catch delegates and function pointers, and fold in their purity
- */
- return true;
}
- return false;
}
View
1  src/mtype.h
@@ -657,7 +657,6 @@ struct TypeFunction : TypeNext
Type *syntaxCopy();
Type *semantic(Loc loc, Scope *sc);
void purityLevel();
- bool hasMutableIndirectionParams();
void toDecoBuffer(OutBuffer *buf, int flag);
void toCBuffer(OutBuffer *buf, Identifier *ident, HdrGenState *hgs);
void toCBufferWithAttributes(OutBuffer *buf, Identifier *ident, HdrGenState* hgs, TypeFunction *attrs, TemplateDeclaration *td);
View
13 src/statement.c
@@ -3922,14 +3922,13 @@ Statement *ReturnStatement::semantic(Scope *sc)
}
else if (tbret->ty != Tvoid)
{
- assert(fd->type->ty == Tfunction);
- TypeFunction *tf = (TypeFunction *)fd->type;
- if (fd->isPureBypassingInference() != PUREimpure &&
- !tf->hasMutableIndirectionParams() &&
- !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());
View
3  src/template.c
@@ -674,9 +674,6 @@ void TemplateDeclaration::makeParamNamesVisibleInConstraint(Scope *paramscope, E
{
Parameter *fparam = Parameter::getNth(fparameters, i);
// Remove addMod same as func.d L1065 of FuncDeclaration::semantic3
- //Type *vtype = fparam->type;
- //if (fd->type && fd->isPure())
- // vtype = vtype->addMod(MODconst);
fparam->storageClass &= (STCin | STCout | STCref | STClazy | STCfinal | STC_TYPECTOR | STCnodtor);
fparam->storageClass |= STCparameter;
if (fvarargs == 2 && i + 1 == nfparams)
View
29 test/fail_compilation/test9230.d
@@ -1,29 +0,0 @@
-/*
-TEST_OUTPUT:
----
-fail_compilation/test9230.d(12): Error: cannot implicitly convert expression (s) of type const(char[]) to string
-fail_compilation/test9230.d(18): Error: cannot implicitly convert expression (a) of type int[] to immutable(int[])
-fail_compilation/test9230.d(23): Error: cannot implicitly convert expression (a) of type int[] to immutable(int[])
-fail_compilation/test9230.d(28): Error: cannot implicitly convert expression (a) of type int[] to immutable(int[])
----
-*/
-
-string foo(in char[] s) pure {
- return s; //
-}
-
-/*pure*/ immutable(int[]) x1()
-{
- int[] a = new int[](10);
- return a;
-}
-/*pure */immutable(int[]) x2(int len)
-{
- int[] a = new int[](len);
- return a;
-}
-/*pure */immutable(int[]) x3(immutable(int[]) org)
-{
- int[] a = new int[](org.length);
- return a;
-}
View
119 test/fail_compilation/testInference.d
@@ -0,0 +1,119 @@
+/*
+TEST_OUTPUT:
+---
+fail_compilation/testInference.d(24): Error: cannot implicitly convert expression (this.a) of type inout(A8998) to immutable(A8998)
+---
+*/
+
+class A8998
+{
+ int i;
+}
+class C8998
+{
+ A8998 a;
+
+ this()
+ {
+ a = new A8998();
+ }
+
+ // WRONG: Returns immutable(A8998)
+ immutable(A8998) get() inout pure
+ {
+ 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
Something went wrong with that request. Please try again.