﻿id	summary	reporter	owner	description	type	status	priority	component	version	resolution	keywords	cc
272	Compiler crash when referencing generic member of generic struct	mlbrooks		"Possible overlap with #166, but the specific failure cases and symptom here are different.  Some of the test-case driving presented here might help drive a similar breadth of cases for working #166.

Headline case:

{{{
forall( E & ) struct inner { E * item; };
forall( E & ) struct outer { inner(E) wrapped; };

forall(E &)
void f( outer(E) & out, E * elem ) { 
    out.wrapped.item = elem;
}
}}}

ACUTAL: Assertion failure in `Type::genericSubstitution()` during new-to-old AST conversion.

EXPECTED: Compiles successfully. If run with the harness below, prints `-1.000000 3.140000 1.414000`.

The breadth of cases that follows illustrates when the issue does (not) happen.  I don't expect you to read the preprocessor acrobatics, but I hope you can understand the -E output of each concrete case.  The headline case above is a small formatting edit of the -E output of the no-defines case, whose explicit coordinates are `-DRUNCASE=1 -DTYPECASE_INNER_GENER -DTYPECASE_OUTER_GENER -DTYPECASE_WRAPPER_TRAD`.

demo.cfa:
{{{
#if 1 < ((defined TYPECASE_INNER_GENER) + (defined TYPECASE_INNER_CONCR))
#error multiple TYPECASE_INNER_X
#endif

#if 1 < ((defined TYPECASE_OUTER_GENER) + (defined TYPECASE_OUTER_CONCR))
#error multiple TYPECASE_OUTER_X
#endif

#if 1 < ((defined TYPECASE_WRAPPER_TRAD) + (defined TYPECASE_WRAPPER_PLN9))
#error multiple TYPECASE_WRAPPER_X
#endif


#if (!defined TYPECASE_INNER_CONCR) && (!defined TYPECASE_INNER_GENER)
#define TYPECASE_INNER_GENER
#endif

#if (!defined TYPECASE_OUTER_CONCR) && (!defined TYPECASE_OUTER_GENER)
#define TYPECASE_OUTER_GENER
#endif

#if (!defined TYPECASE_WRAPPER_PLN9) && (!defined TYPECASE_WRAPPER_TRAD)
#define TYPECASE_WRAPPER_TRAD
#endif



                        #define DEFTY_INNER_FRAG(T) struct inner { T * item; };
#if defined TYPECASE_INNER_GENER
                        #define DEFTY_INNER  forall( E & ) DEFTY_INNER_FRAG(E)
                        #define TY_INNER(T)  inner(T)
#else
                        #define DEFTY_INNER  DEFTY_INNER_FRAG(float)
                        #define TY_INNER(T)  inner
#endif

#if defined TYPECASE_WRAPPER_TRAD
                        #define DEFFLD(ty)   ty wrapped;
                        #define FLD(obj)     obj.wrapped
#else
                        #define DEFFLD(ty)   inline ty;
                        #define FLD(obj)     obj
#endif

                        #define DEFTY_OUTER_FRAG(T) struct outer { DEFFLD(TY_INNER(T)) };
#if defined TYPECASE_OUTER_GENER
                        #define DEFTY_OUTER  forall( E & ) DEFTY_OUTER_FRAG(E)
                        #define TY_OUTER(T)  outer(T)
                        #define DEFFUNC(...) forall(E &) { __VA_ARGS__ }
#else
                        #define DEFTY_OUTER  DEFTY_OUTER_FRAG(float)
                        #define TY_OUTER(T)  outer
                        #define DEFFUNC(...) __VA_ARGS__
#endif
                        #define GENTY_OUTER  TY_OUTER(E)

#if (defined TYPECASE_INNER_GENER) && (defined TYPECASE_OUTER_GENER)
                        #define ELEM E
#else
                        #define ELEM float
#endif



DEFTY_INNER
DEFTY_OUTER


                                 #if   RUNCASE==4 || RUNCASE==5 || RUNCASE==6 || RUNCASE==7
DEFFUNC (
    void rval_helper( ELEM * ) {
        // this helper is a nop
        // SUT action to be done as follow-up step
    }
    void lval_helper( ELEM * & tgt, ELEM * val ) {
        // this helper does the SUT action
        tgt = val;
    }
    void mockStep( ELEM * & tgt, ELEM * val ) {
    }
)
// follow-up step for no-op variations
// applies workaround B (assumes it succeeds)
// (cases that use the mock action only illustrate wheteher the compiler claims success)
#define MOCK_SUT_ACTION ((TY_INNER(ELEM)&)(FLD(out))).item = elem; 

                                 #endif

DEFFUNC (
    void f( GENTY_OUTER & out, ELEM * elem ) {
                                 #if   RUNCASE==1  // headline, maximally broken
        FLD(out).item = elem;
                                 #elif RUNCASE==2  // workaround A, successful sometimes
        (*&(FLD(out))).item = elem;
                                 #elif RUNCASE==3  // workaround B, successful always
        ((TY_INNER(ELEM)&)(FLD(out))).item = elem;
                                 #elif RUNCASE==4  // variation as top-level expr, not broken
        FLD(out).item;
        MOCK_SUT_ACTION
                                 #elif RUNCASE==5  // variation as simple r-value, not broken
        rval_helper( FLD(out).item );
        MOCK_SUT_ACTION
                                 #elif RUNCASE==6  // variation as simple l-value, blocked by #166
        lval_helper( FLD(out).item, elem );
                                 #elif RUNCASE==7  // variation as hacked l-value, maximally broken
        // here, *& is a #166-workaround
        // looks like workaround A: coincidence?
        lval_helper( *& FLD(out).item, elem );
                                 #else
                                 #error bad RUNCASE
                                 #endif
    }
)

// Gives a demonstration of ""compiler does right thing,"" as more than ""compiler claims success.""
// Except for runcases 4,5.
// While the innermost datum being a pointer adds complexity at the harness level, it removes a
// confounding factor from the core repro: the failure happens with `forall(E&)`.  Switching the
// core datum to a non-pointer would make these declarations `forall(E)`, which would add boxing
// to the polymorphic calls.
int main() {

    TY_OUTER(float) obj;

    // `obj` content test point: because ""proper"" access via fields is SUT,
    // circumvent that for the test harness
    float * & objContTP = * (float **) & obj;

    // state 1, via TP: obj points to oldbox
    float oldbox = -1.0;
    objContTP = &oldbox;
    printf(""%f "", *objContTP);

    // state 2, via SUT: obj points to newbox
    float newbox = 3.14;
    f(obj, &newbox);
    printf(""%f "", *objContTP);

    // mutation in state 2: show state 2 has the pointing expected
    newbox = 1.414;
    printf(""%f\n"", *objContTP);
}
}}}

run.sh:
{{{
dotest() {
#   (set -x; $cfa demo.cfa $RUNCASE $TYPECASE_INNER $TYPECASE_OUTER $TYPECASE_WRAPPER -E           )
    (set -x; $cfa demo.cfa $RUNCASE $TYPECASE_INNER $TYPECASE_OUTER $TYPECASE_WRAPPER    && ./a.out)
}

# Failures that are success-adjacent, and successes that are failure-adjacent, are driven here.

for RUNCASE in '-DRUNCASE=1'
do
for TYPECASE_INNER in '-DTYPECASE_INNER_GENER' '-DTYPECASE_INNER_CONCR'
do
for TYPECASE_OUTER in '-DTYPECASE_OUTER_GENER' '-DTYPECASE_OUTER_CONCR'
do
for TYPECASE_WRAPPER in '-DTYPECASE_WRAPPER_TRAD' '-DTYPECASE_WRAPPER_PLN9'
do
    dotest
done
done
done
done

for RUNCASE in '-DRUNCASE=2' '-DRUNCASE=3'
do
for TYPECASE_INNER in '-DTYPECASE_INNER_GENER'
do
for TYPECASE_OUTER in '-DTYPECASE_OUTER_GENER'
do
for TYPECASE_WRAPPER in '-DTYPECASE_WRAPPER_TRAD' '-DTYPECASE_WRAPPER_PLN9'
do
    dotest
done
done
done
done

for RUNCASE in '-DRUNCASE=4' '-DRUNCASE=5' '-DRUNCASE=6' '-DRUNCASE=7'
do
for TYPECASE_INNER in '-DTYPECASE_INNER_GENER'
do
for TYPECASE_OUTER in '-DTYPECASE_OUTER_GENER'
do
for TYPECASE_WRAPPER in '-DTYPECASE_WRAPPER_TRAD'
do
    dotest
done
done
done
done


# All combinations are driven here
# ""All combinations pass"" is a fine condition for statisfaction.

# for RUNCASE in '-DRUNCASE=1' '-DRUNCASE=2' '-DRUNCASE=3' '-DRUNCASE=4' '-DRUNCASE=5' '-DRUNCASE=6' '-DRUNCASE=7'
# do
# for TYPECASE_INNER in '-DTYPECASE_INNER_GENER' '-DTYPECASE_INNER_CONCR'
# do
# for TYPECASE_OUTER in '-DTYPECASE_OUTER_GENER' '-DTYPECASE_OUTER_CONCR'
# do
# for TYPECASE_WRAPPER in '-DTYPECASE_WRAPPER_TRAD' '-DTYPECASE_WRAPPER_PLN9'
# do
#     dotest
# done
# done
# done
# done
}}}

Invoking `./run.sh` gives

ACUTAL:  mix of crashes, as in headline-actual, and successful runs, as in headline-expected, according to the case summary following, except with RUNCASE=6 being an occurrence of #166

EXPECTED:  always a successful run, as in headline-expected, except with RUNCASE=6 being either a successful run or an occurrence of #166

Summary of the cases and actual outcomes (to be read along with the -E dump):
- RUNCASE=1 is the vanilla version.  Running it across varied generic/concrete options for TYPECASE_INNER and TYPECASE_OUTER shows that it only happens when both levels are generic.  Running it across varied traditional/plan-9 options for TYPECASE_WRAPPER shows that it is unaffected by using a plan-9 wrapping of the inner within the outer.
- RUNCASE=2 (add `*&`) and RUNCASE=3 (add a cast to the desired reference type) are possible workarounds.  Adding `*&` works for a traditional wrapper but not for a plan-9 wrapper.  Adding a cast always works.
- The variations for RUNCASE in 4..7 show that no magic coming from the assignment operator is at issue, rather, that seeking a _reference_ to the contained element seems to be the problem (as in 6 and 7 being broken), while mere mention of the contained element works (as in 4 and 5 succeeding).
"	defect	new	major	cfa-cc	1.0			
