Ignore:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/InitTweak/FixInit.cc

    re41306d r1bc749f  
    5454#include "SynTree/Type.h"              // for Type, Type::StorageClasses
    5555#include "SynTree/TypeSubstitution.h"  // for TypeSubstitution, operator<<
     56#include "SynTree/VarExprReplacer.h"   // for VarExprReplacer
    5657#include "SynTree/Visitor.h"           // for acceptAll, maybeAccept
    5758
     
    9394                        /// true if type does not need to be copy constructed to ensure correctness
    9495                        bool skipCopyConstruct( Type * type );
    95                         void copyConstructArg( Expression *& arg, ImplicitCopyCtorExpr * impCpCtorExpr, Type * formal );
     96                        void copyConstructArg( Expression *& arg, ImplicitCopyCtorExpr * impCpCtorExpr );
    9697                        void destructRet( ObjectDecl * ret, ImplicitCopyCtorExpr * impCpCtorExpr );
    9798
     
    158159                        using Parent::previsit;
    159160
    160                         void previsit( ObjectDecl * objDecl );
    161161                        void previsit( FunctionDecl * funcDecl );
    162162
    163                         void previsit( CompoundStmt * compoundStmt );
    164                         void postvisit( CompoundStmt * compoundStmt );
    165                         void previsit( ReturnStmt * returnStmt );
    166163                        void previsit( BranchStmt * stmt );
    167164                private:
     
    179176
    180177                        DeclarationWithType * postmutate( ObjectDecl *objDecl );
     178
     179                        DeclarationWithType * getDtorFunc( ObjectDecl * objDecl, Statement * dtor );
    181180
    182181                        std::list< Declaration * > staticDtorDecls;
     
    214213                        void emit( CodeLocation, const Params &... params );
    215214
    216                         FunctionDecl * function = nullptr;
     215                        FunctionDecl * function = 0;
    217216                        std::set< DeclarationWithType * > unhandled;
    218217                        std::map< DeclarationWithType *, CodeLocation > usedUninit;
    219                         ObjectDecl * thisParam = nullptr;
     218                        ObjectDecl * thisParam = 0;
    220219                        bool isCtor = false; // true if current function is a constructor
    221                         StructDecl * structDecl = nullptr;
     220                        StructDecl * structDecl = 0;
    222221                };
    223222
     
    260259
    261260                GenStructMemberCalls::generate( translationUnit );
    262 
    263261                // xxx - ctor expansion currently has to be after FixCopyCtors, because there is currently a
    264262                // hack in the way untyped assignments are generated, where the first argument cannot have
     
    290288                        for ( std::list< Declaration * >::iterator i = translationUnit.begin(); i != translationUnit.end(); ++i ) {
    291289                                try {
    292                                         maybeMutate( *i, fixer );
     290                                        *i = maybeMutate( *i, fixer );
    293291                                        translationUnit.splice( i, fixer.pass.staticDtorDecls );
    294292                                } catch( SemanticError &e ) {
     
    324322
    325323                Expression * InsertImplicitCalls::postmutate( ApplicationExpr * appExpr ) {
     324                        assert( appExpr );
     325
    326326                        if ( VariableExpr * function = dynamic_cast< VariableExpr * > ( appExpr->get_function() ) ) {
    327                                 if ( function->var->linkage.is_builtin ) {
     327                                if ( LinkageSpec::isBuiltin( function->get_var()->get_linkage() ) ) {
    328328                                        // optimization: don't need to copy construct in order to call intrinsic functions
    329329                                        return appExpr;
     
    331331                                        FunctionType * ftype = dynamic_cast< FunctionType * >( GenPoly::getFunctionType( funcDecl->get_type() ) );
    332332                                        assertf( ftype, "Function call without function type: %s", toString( funcDecl ).c_str() );
    333                                         if ( CodeGen::isConstructor( funcDecl->get_name() ) && ftype->parameters.size() == 2 ) {
    334                                                 Type * t1 = getPointerBase( ftype->parameters.front()->get_type() );
    335                                                 Type * t2 = ftype->parameters.back()->get_type();
     333                                        if ( CodeGen::isConstructor( funcDecl->get_name() ) && ftype->get_parameters().size() == 2 ) {
     334                                                Type * t1 = getPointerBase( ftype->get_parameters().front()->get_type() );
     335                                                Type * t2 = ftype->get_parameters().back()->get_type();
    336336                                                assert( t1 );
    337337
     
    366366                        ImplicitCtorDtorStmt * stmt = genCtorDtor( fname, var, cpArg );
    367367                        ExprStmt * exprStmt = strict_dynamic_cast< ExprStmt * >( stmt->get_callStmt() );
    368                         Expression * resolved = exprStmt->expr;
    369                         exprStmt->expr = nullptr; // take ownership of expr
     368                        Expression * untyped = exprStmt->get_expr();
    370369
    371370                        // resolve copy constructor
    372371                        // should only be one alternative for copy ctor and dtor expressions, since all arguments are fixed
    373372                        // (VariableExpr and already resolved expression)
    374                         CP_CTOR_PRINT( std::cerr << "ResolvingCtorDtor " << resolved << std::endl; )
    375                         ResolvExpr::findVoidExpression( resolved, indexer );
     373                        CP_CTOR_PRINT( std::cerr << "ResolvingCtorDtor " << untyped << std::endl; )
     374                        Expression * resolved = ResolvExpr::findVoidExpression( untyped, indexer );
    376375                        assert( resolved );
    377376                        if ( resolved->get_env() ) {
     
    381380                                resolved->set_env( nullptr );
    382381                        } // if
     382
    383383                        delete stmt;
    384384                        return resolved;
    385385                }
    386386
    387                 void ResolveCopyCtors::copyConstructArg( Expression *& arg, ImplicitCopyCtorExpr * impCpCtorExpr, Type * formal ) {
     387                void ResolveCopyCtors::copyConstructArg( Expression *& arg, ImplicitCopyCtorExpr * impCpCtorExpr ) {
    388388                        static UniqueName tempNamer("_tmp_cp");
    389389                        assert( env );
    390390                        CP_CTOR_PRINT( std::cerr << "Type Substitution: " << *env << std::endl; )
    391                         assert( arg->result );
    392                         Type * result = arg->result;
     391                        assert( arg->has_result() );
     392                        Type * result = arg->get_result();
    393393                        if ( skipCopyConstruct( result ) ) return; // skip certain non-copyable types
    394394
    395                         // type may involve type variables, so apply type substitution to get temporary variable's actual type.
    396                         // Use applyFree so that types bound in function pointers are not substituted, e.g. in forall(dtype T) void (*)(T).
     395                        // type may involve type variables, so apply type substitution to get temporary variable's actual type
    397396                        result = result->clone();
    398                         env->applyFree( result );
     397                        env->apply( result );
    399398                        ObjectDecl * tmp = ObjectDecl::newObject( "__tmp", result, nullptr );
    400399                        tmp->get_type()->set_const( false );
     
    407406                                // if the chosen constructor is intrinsic, the copy is unnecessary, so
    408407                                // don't create the temporary and don't call the copy constructor
    409                                 VariableExpr * function = strict_dynamic_cast< VariableExpr * >( appExpr->function );
    410                                 if ( function->var->linkage == LinkageSpec::Intrinsic ) {
    411                                         // arguments that need to be boxed need a temporary regardless of whether the copy constructor is intrinsic,
    412                                         // so that the object isn't changed inside of the polymorphic function
    413                                         if ( ! GenPoly::needsBoxing( formal, result, impCpCtorExpr->callExpr, env ) ) return;
    414                                 }
     408                                VariableExpr * function = dynamic_cast< VariableExpr * >( appExpr->get_function() );
     409                                assert( function );
     410                                if ( function->get_var()->get_linkage() == LinkageSpec::Intrinsic ) return;
    415411                        }
    416412
     
    420416                        // replace argument to function call with temporary
    421417                        arg = new CommaExpr( cpCtor, new VariableExpr( tmp ) );
    422                         impCpCtorExpr->tempDecls.push_back( tmp );
    423                         impCpCtorExpr->dtors.push_front( makeCtorDtor( "^?{}", tmp ) );
     418                        impCpCtorExpr->get_tempDecls().push_back( tmp );
     419                        impCpCtorExpr->get_dtors().push_front( makeCtorDtor( "^?{}", tmp ) );
    424420                }
    425421
     
    431427                        CP_CTOR_PRINT( std::cerr << "ResolveCopyCtors: " << impCpCtorExpr << std::endl; )
    432428
    433                         ApplicationExpr * appExpr = impCpCtorExpr->callExpr;
     429                        ApplicationExpr * appExpr = impCpCtorExpr->get_callExpr();
    434430
    435431                        // take each argument and attempt to copy construct it.
    436                         FunctionType * ftype = GenPoly::getFunctionType( appExpr->function->result );
    437                         assert( ftype );
    438                         auto & params = ftype->parameters;
    439                         auto iter = params.begin();
    440                         for ( Expression * & arg : appExpr->args ) {
    441                                 Type * formal = nullptr;
    442                                 if ( iter != params.end() ) {
    443                                         DeclarationWithType * param = *iter++;
    444                                         formal = param->get_type();
    445                                 }
    446 
    447                                 copyConstructArg( arg, impCpCtorExpr, formal );
     432                        for ( Expression * & arg : appExpr->get_args() ) {
     433                                copyConstructArg( arg, impCpCtorExpr );
    448434                        } // for
    449435
     
    451437                        // initialized with the return value and is destructed later
    452438                        // xxx - handle named return values?
    453                         Type * result = appExpr->result;
     439                        Type * result = appExpr->get_result();
    454440                        if ( ! result->isVoid() ) {
    455441                                static UniqueName retNamer("_tmp_cp_ret");
     
    457443                                env->apply( result );
    458444                                ObjectDecl * ret = ObjectDecl::newObject( retNamer.newName(), result, nullptr );
    459                                 ret->type->set_const( false );
    460                                 impCpCtorExpr->returnDecls.push_back( ret );
     445                                ret->get_type()->set_const( false );
     446                                impCpCtorExpr->get_returnDecls().push_back( ret );
    461447                                CP_CTOR_PRINT( std::cerr << "makeCtorDtor for a return" << std::endl; )
    462448                                if ( ! dynamic_cast< ReferenceType * >( result ) ) {
     
    565551                                Expression * retExpr = new CommaExpr( assign, new VariableExpr( returnDecl ) );
    566552                                // move env from callExpr to retExpr
    567                                 std::swap( retExpr->env, callExpr->env );
     553                                retExpr->set_env( callExpr->get_env() );
     554                                callExpr->set_env( nullptr );
    568555                                return retExpr;
    569556                        } else {
     
    636623                }
    637624
     625                DeclarationWithType * FixInit::getDtorFunc( ObjectDecl * objDecl, Statement * dtor ) {
     626                        if ( dynamic_cast< ExprStmt * >( dtor ) ) {
     627                                if ( DeclarationWithType * func = getFunction( getCtorDtorCall( dtor ) ) ) {
     628                                        // cleanup argument must be a function, not an object (including function pointer)
     629                                        if ( FunctionDecl * dtorFunc = dynamic_cast< FunctionDecl * > ( func ) ) {
     630                                                if ( dtorFunc->type->forall.empty() ) {
     631                                                        // simple case where the destructor is a monomorphic function call - can simply
     632                                                        // use that function as the cleanup function.
     633                                                        delete dtor;
     634                                                        return func;
     635                                                }
     636                                        }
     637                                }
     638                        }
     639
     640                        // otherwise the cleanup is more complicated - need to build a single argument cleanup function that
     641                        // wraps the more complicated code.
     642                        static UniqueName dtorNamer( "__cleanup_dtor" );
     643                        FunctionDecl * dtorFunc = FunctionDecl::newFunction( dtorNamer.newName(), SymTab::genDefaultType( objDecl->type ), new CompoundStmt( noLabels ) );
     644                        stmtsToAddBefore.push_back( new DeclStmt( noLabels, dtorFunc ) );
     645
     646                        // the original code contains uses of objDecl - replace them with the newly generated 'this' parameter.
     647                        ObjectDecl * thisParam = getThisParam( dtorFunc->type );
     648                        VarExprReplacer::replace( dtor, { std::make_pair( objDecl, thisParam ) } );
     649                        dtorFunc->statements->push_back( dtor );
     650
     651                        return dtorFunc;
     652                }
     653
    638654                DeclarationWithType * FixInit::postmutate( ObjectDecl *objDecl ) {
    639655                        // since this removes the init field from objDecl, it must occur after children are mutated (i.e. postmutate)
     
    734750                                        } else {
    735751                                                ImplicitCtorDtorStmt * implicit = strict_dynamic_cast< ImplicitCtorDtorStmt * > ( ctor );
    736                                                 ExprStmt * ctorStmt = dynamic_cast< ExprStmt * >( implicit->callStmt );
     752                                                ExprStmt * ctorStmt = dynamic_cast< ExprStmt * >( implicit->get_callStmt() );
    737753                                                ApplicationExpr * ctorCall = nullptr;
    738                                                 if ( ctorStmt && (ctorCall = isIntrinsicCallExpr( ctorStmt->expr )) && ctorCall->get_args().size() == 2 ) {
     754                                                if ( ctorStmt && (ctorCall = isIntrinsicCallExpr( ctorStmt->get_expr() )) && ctorCall->get_args().size() == 2 ) {
    739755                                                        // clean up intrinsic copy constructor calls by making them into SingleInits
    740                                                         Expression * ctorArg = ctorCall->args.back();
    741                                                         std::swap( ctorArg->env, ctorCall->env );
    742                                                         objDecl->init = new SingleInit( ctorArg );
    743 
    744                                                         ctorCall->args.pop_back();
     756                                                        objDecl->set_init( new SingleInit( ctorCall->get_args().back() ) );
     757                                                        ctorCall->get_args().pop_back();
    745758                                                } else {
    746759                                                        stmtsToAddAfter.push_back( ctor );
    747                                                         objDecl->init = nullptr;
    748                                                         ctorInit->ctor = nullptr;
     760                                                        objDecl->set_init( nullptr );
     761                                                        ctorInit->set_ctor( nullptr );
     762                                                }
     763
     764                                                Statement * dtor = ctorInit->dtor;
     765                                                if ( dtor ) {
     766                                                        ImplicitCtorDtorStmt * implicit = strict_dynamic_cast< ImplicitCtorDtorStmt * >( dtor );
     767                                                        Statement * dtorStmt = implicit->callStmt;
     768                                                        // don't need to call intrinsic dtor, because it does nothing, but
     769                                                        // non-intrinsic dtors must be called
     770                                                        if ( ! isIntrinsicSingleArgCallStmt( dtorStmt ) ) {
     771                                                                // set dtor location to the object's location for error messages
     772                                                                DeclarationWithType * dtorFunc = getDtorFunc( objDecl, dtorStmt );
     773                                                                objDecl->attributes.push_back( new Attribute( "cleanup", { new VariableExpr( dtorFunc ) } ) );
     774                                                                // objDecl->attributes.push_back( new Attribute( "cleanup", { new NameExpr( dtorFunc->name ) } ) );
     775                                                                ctorInit->dtor = nullptr;
     776                                                        } // if
    749777                                                }
    750778                                        } // if
    751                                 } else if ( Initializer * init = ctorInit->init ) {
    752                                         objDecl->init = init;
    753                                         ctorInit->init = nullptr;
     779                                } else if ( Initializer * init = ctorInit->get_init() ) {
     780                                        objDecl->set_init( init );
     781                                        ctorInit->set_init( nullptr );
    754782                                } else {
    755783                                        // no constructor and no initializer, which is okay
    756                                         objDecl->init = nullptr;
     784                                        objDecl->set_init( nullptr );
    757785                                } // if
    758786                                delete ctorInit;
     
    790818
    791819
    792                 template<typename Iterator, typename OutputIterator>
    793                 void insertDtors( Iterator begin, Iterator end, OutputIterator out ) {
    794                         for ( Iterator it = begin ; it != end ; ++it ) {
    795                                 // extract destructor statement from the object decl and insert it into the output. Note that this is
    796                                 // only called on lists of non-static objects with implicit non-intrinsic dtors, so if the user manually
    797                                 // calls an intrinsic dtor then the call must (and will) still be generated since the argument may
    798                                 // contain side effects.
    799                                 ObjectDecl * objDecl = *it;
    800                                 ConstructorInit * ctorInit = dynamic_cast< ConstructorInit * >( objDecl->get_init() );
    801                                 assert( ctorInit && ctorInit->get_dtor() );
    802                                 *out++ = ctorInit->get_dtor()->clone();
    803                         } // for
    804                 }
    805 
    806                 void InsertDtors::previsit( ObjectDecl * objDecl ) {
    807                         // remember non-static destructed objects so that their destructors can be inserted later
    808                         if ( ! objDecl->get_storageClasses().is_static ) {
    809                                 if ( ConstructorInit * ctorInit = dynamic_cast< ConstructorInit * >( objDecl->get_init() ) ) {
    810                                         // a decision should have been made by the resolver, so ctor and init are not both non-NULL
    811                                         assert( ! ctorInit->get_ctor() || ! ctorInit->get_init() );
    812                                         Statement * dtor = ctorInit->get_dtor();
    813                                         // don't need to call intrinsic dtor, because it does nothing, but
    814                                         // non-intrinsic dtors must be called
    815                                         if ( dtor && ! isIntrinsicSingleArgCallStmt( dtor ) ) {
    816                                                 // set dtor location to the object's location for error messages
    817                                                 ctorInit->dtor->location = objDecl->location;
    818                                                 reverseDeclOrder.front().push_front( objDecl );
    819                                         } // if
    820                                 } // if
    821                         } // if
    822                 }
    823 
    824820                void InsertDtors::previsit( FunctionDecl * funcDecl ) {
    825821                        // each function needs to have its own set of labels
    826822                        GuardValue( labelVars );
    827823                        labelVars.clear();
    828                         // LabelFinder does not recurse into FunctionDecl, so need to visit
    829                         // its children manually.
    830824                        maybeAccept( funcDecl->type, finder );
    831825                        maybeAccept( funcDecl->statements, finder );
    832826
    833827                        // all labels for this function have been collected, insert destructors as appropriate via implicit recursion.
    834                 }
    835 
    836                 void InsertDtors::previsit( CompoundStmt * compoundStmt ) {
    837                         // visit statements - this will also populate reverseDeclOrder list.  don't want to dump all destructors
    838                         // when block is left, just the destructors associated with variables defined in this block, so push a new
    839                         // list to the top of the stack so that we can differentiate scopes
    840                         reverseDeclOrder.push_front( OrderedDecls() );
    841                         Parent::previsit( compoundStmt );
    842                 }
    843 
    844                 void InsertDtors::postvisit( CompoundStmt * compoundStmt ) {
    845                         // add destructors for the current scope that we're exiting, unless the last statement is a return, which
    846                         // causes unreachable code warnings
    847                         std::list< Statement * > & statements = compoundStmt->get_kids();
    848                         if ( ! statements.empty() && ! dynamic_cast< ReturnStmt * >( statements.back() ) ) {
    849                                 insertDtors( reverseDeclOrder.front().begin(), reverseDeclOrder.front().end(), back_inserter( statements ) );
    850                         }
    851                         reverseDeclOrder.pop_front();
    852                 }
    853 
    854                 void InsertDtors::previsit( ReturnStmt * ) {
    855                         // return exits all scopes, so dump destructors for all scopes
    856                         for ( OrderedDecls & od : reverseDeclOrder ) {
    857                                 insertDtors( od.begin(), od.end(), back_inserter( stmtsToAddBefore ) );
    858                         } // for
    859828                }
    860829
     
    886855                        if ( ! diff.empty() ) {
    887856                                throw SemanticError( std::string("jump to label '") + stmt->get_target().get_name() + "' crosses initialization of " + (*diff.begin())->get_name() + " ", stmt );
    888                         } // if
    889                         // S_G-S_L results in set of objects that must be destructed
    890                         diff.clear();
    891                         std::set_difference( curVars.begin(), curVars.end(), lvars.begin(), lvars.end(), std::inserter( diff, diff.end() ) );
    892                         DTOR_PRINT(
    893                                 std::cerr << "S_G-S_L = " << printSet( diff ) << std::endl;
    894                         )
    895                         if ( ! diff.empty() ) {
    896                                 // create an auxilliary set for fast lookup -- can't make diff a set, because diff ordering should be consistent for error messages.
    897                                 std::unordered_set<ObjectDecl *> needsDestructor( diff.begin(), diff.end() );
    898 
    899                                 // go through decl ordered list of objectdecl. for each element that occurs in diff, output destructor
    900                                 OrderedDecls ordered;
    901                                 for ( OrderedDecls & rdo : reverseDeclOrder ) {
    902                                         // add elements from reverseDeclOrder into ordered if they occur in diff - it is key that this happens in reverse declaration order.
    903                                         copy_if( rdo.begin(), rdo.end(), back_inserter( ordered ), [&]( ObjectDecl * objDecl ) { return needsDestructor.count( objDecl ); } );
    904                                 } // for
    905                                 insertDtors( ordered.begin(), ordered.end(), back_inserter( stmtsToAddBefore ) );
    906857                        } // if
    907858                }
     
    929880                }
    930881
    931                 void addIds( SymTab::Indexer & indexer, const std::list< DeclarationWithType * > & decls ) {
    932                         for ( auto d : decls ) {
    933                                 indexer.addId( d );
    934                         }
    935                 }
    936 
    937                 void addTypes( SymTab::Indexer & indexer, const std::list< TypeDecl * > & tds ) {
    938                         for ( auto td : tds ) {
    939                                 indexer.addType( td );
    940                                 addIds( indexer, td->assertions );
    941                         }
    942                 }
    943 
    944882                void GenStructMemberCalls::previsit( FunctionDecl * funcDecl ) {
    945                         GuardValue( function );
     883                        GuardValue( funcDecl );
    946884                        GuardValue( unhandled );
    947885                        GuardValue( usedUninit );
     
    976914                }
    977915
     916                void addIds( SymTab::Indexer & indexer, const std::list< DeclarationWithType * > & decls ) {
     917                        for ( auto d : decls ) {
     918                                indexer.addId( d );
     919                        }
     920                }
     921
     922                void addTypes( SymTab::Indexer & indexer, const std::list< TypeDecl * > & tds ) {
     923                        for ( auto td : tds ) {
     924                                indexer.addType( td );
     925                                addIds( indexer, td->assertions );
     926                        }
     927                }
     928
    978929                void GenStructMemberCalls::postvisit( FunctionDecl * funcDecl ) {
    979930                        // remove the unhandled objects from usedUninit, because a call is inserted
     
    11281079                }
    11291080
    1130                 DeclarationWithType * MutatingResolver::mutate( ObjectDecl * objectDecl ) {
     1081                DeclarationWithType * MutatingResolver::mutate( ObjectDecl *objectDecl ) {
    11311082                        // add object to the indexer assumes that there will be no name collisions
    11321083                        // in generated code. If this changes, add mutate methods for entities with
     
    11361087                }
    11371088
    1138                 Expression * MutatingResolver::mutate( UntypedExpr * untypedExpr ) {
    1139                         Expression * newExpr = untypedExpr;
    1140                         ResolvExpr::findVoidExpression( newExpr, indexer );
    1141                         return newExpr;
     1089                Expression* MutatingResolver::mutate( UntypedExpr *untypedExpr ) {
     1090                        return strict_dynamic_cast< ApplicationExpr * >( ResolvExpr::findVoidExpression( untypedExpr, indexer ) );
    11421091                }
    11431092
     
    11451094                        static UniqueName tempNamer( "_tmp_ctor_expr" );
    11461095                        // xxx - is the size check necessary?
    1147                         assert( ctorExpr->result && ctorExpr->get_result()->size() == 1 );
     1096                        assert( ctorExpr->has_result() && ctorExpr->get_result()->size() == 1 );
    11481097
    11491098                        // xxx - ideally we would reuse the temporary generated from the copy constructor passes from within firstArg if it exists and not generate a temporary if it's unnecessary.
     
    11641113
    11651114                        // resolve assignment and dispose of new env
    1166                         ResolvExpr::findVoidExpression( assign, indexer );
    1167                         delete assign->env;
    1168                         assign->env = nullptr;
     1115                        Expression * resolvedAssign = ResolvExpr::findVoidExpression( assign, indexer );
     1116                        delete resolvedAssign->env;
     1117                        resolvedAssign->env = nullptr;
     1118                        delete assign;
    11691119
    11701120                        // for constructor expr:
     
    11751125                        //   T & tmp;
    11761126                        //   &tmp = &x, ?{}(tmp), tmp
    1177                         CommaExpr * commaExpr = new CommaExpr( assign, new CommaExpr( callExpr, new VariableExpr( tmp ) ) );
     1127                        CommaExpr * commaExpr = new CommaExpr( resolvedAssign, new CommaExpr( callExpr, new VariableExpr( tmp ) ) );
    11781128                        commaExpr->set_env( env );
    11791129                        return commaExpr;
Note: See TracChangeset for help on using the changeset viewer.