Ignore:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/InitTweak/FixInit.cc

    r1bc749f re41306d  
    5454#include "SynTree/Type.h"              // for Type, Type::StorageClasses
    5555#include "SynTree/TypeSubstitution.h"  // for TypeSubstitution, operator<<
    56 #include "SynTree/VarExprReplacer.h"   // for VarExprReplacer
    5756#include "SynTree/Visitor.h"           // for acceptAll, maybeAccept
    5857
     
    9493                        /// true if type does not need to be copy constructed to ensure correctness
    9594                        bool skipCopyConstruct( Type * type );
    96                         void copyConstructArg( Expression *& arg, ImplicitCopyCtorExpr * impCpCtorExpr );
     95                        void copyConstructArg( Expression *& arg, ImplicitCopyCtorExpr * impCpCtorExpr, Type * formal );
    9796                        void destructRet( ObjectDecl * ret, ImplicitCopyCtorExpr * impCpCtorExpr );
    9897
     
    159158                        using Parent::previsit;
    160159
     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 );
    163166                        void previsit( BranchStmt * stmt );
    164167                private:
     
    176179
    177180                        DeclarationWithType * postmutate( ObjectDecl *objDecl );
    178 
    179                         DeclarationWithType * getDtorFunc( ObjectDecl * objDecl, Statement * dtor );
    180181
    181182                        std::list< Declaration * > staticDtorDecls;
     
    213214                        void emit( CodeLocation, const Params &... params );
    214215
    215                         FunctionDecl * function = 0;
     216                        FunctionDecl * function = nullptr;
    216217                        std::set< DeclarationWithType * > unhandled;
    217218                        std::map< DeclarationWithType *, CodeLocation > usedUninit;
    218                         ObjectDecl * thisParam = 0;
     219                        ObjectDecl * thisParam = nullptr;
    219220                        bool isCtor = false; // true if current function is a constructor
    220                         StructDecl * structDecl = 0;
     221                        StructDecl * structDecl = nullptr;
    221222                };
    222223
     
    259260
    260261                GenStructMemberCalls::generate( translationUnit );
     262
    261263                // xxx - ctor expansion currently has to be after FixCopyCtors, because there is currently a
    262264                // hack in the way untyped assignments are generated, where the first argument cannot have
     
    288290                        for ( std::list< Declaration * >::iterator i = translationUnit.begin(); i != translationUnit.end(); ++i ) {
    289291                                try {
    290                                         *i = maybeMutate( *i, fixer );
     292                                        maybeMutate( *i, fixer );
    291293                                        translationUnit.splice( i, fixer.pass.staticDtorDecls );
    292294                                } catch( SemanticError &e ) {
     
    322324
    323325                Expression * InsertImplicitCalls::postmutate( ApplicationExpr * appExpr ) {
    324                         assert( appExpr );
    325 
    326326                        if ( VariableExpr * function = dynamic_cast< VariableExpr * > ( appExpr->get_function() ) ) {
    327                                 if ( LinkageSpec::isBuiltin( function->get_var()->get_linkage() ) ) {
     327                                if ( function->var->linkage.is_builtin ) {
    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->get_parameters().size() == 2 ) {
    334                                                 Type * t1 = getPointerBase( ftype->get_parameters().front()->get_type() );
    335                                                 Type * t2 = ftype->get_parameters().back()->get_type();
     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();
    336336                                                assert( t1 );
    337337
     
    366366                        ImplicitCtorDtorStmt * stmt = genCtorDtor( fname, var, cpArg );
    367367                        ExprStmt * exprStmt = strict_dynamic_cast< ExprStmt * >( stmt->get_callStmt() );
    368                         Expression * untyped = exprStmt->get_expr();
     368                        Expression * resolved = exprStmt->expr;
     369                        exprStmt->expr = nullptr; // take ownership of expr
    369370
    370371                        // resolve copy constructor
    371372                        // should only be one alternative for copy ctor and dtor expressions, since all arguments are fixed
    372373                        // (VariableExpr and already resolved expression)
    373                         CP_CTOR_PRINT( std::cerr << "ResolvingCtorDtor " << untyped << std::endl; )
    374                         Expression * resolved = ResolvExpr::findVoidExpression( untyped, indexer );
     374                        CP_CTOR_PRINT( std::cerr << "ResolvingCtorDtor " << resolved << std::endl; )
     375                        ResolvExpr::findVoidExpression( resolved, indexer );
    375376                        assert( resolved );
    376377                        if ( resolved->get_env() ) {
     
    380381                                resolved->set_env( nullptr );
    381382                        } // if
    382 
    383383                        delete stmt;
    384384                        return resolved;
    385385                }
    386386
    387                 void ResolveCopyCtors::copyConstructArg( Expression *& arg, ImplicitCopyCtorExpr * impCpCtorExpr ) {
     387                void ResolveCopyCtors::copyConstructArg( Expression *& arg, ImplicitCopyCtorExpr * impCpCtorExpr, Type * formal ) {
    388388                        static UniqueName tempNamer("_tmp_cp");
    389389                        assert( env );
    390390                        CP_CTOR_PRINT( std::cerr << "Type Substitution: " << *env << std::endl; )
    391                         assert( arg->has_result() );
    392                         Type * result = arg->get_result();
     391                        assert( arg->result );
     392                        Type * result = arg->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
     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).
    396397                        result = result->clone();
    397                         env->apply( result );
     398                        env->applyFree( result );
    398399                        ObjectDecl * tmp = ObjectDecl::newObject( "__tmp", result, nullptr );
    399400                        tmp->get_type()->set_const( false );
     
    406407                                // if the chosen constructor is intrinsic, the copy is unnecessary, so
    407408                                // don't create the temporary and don't call the copy constructor
    408                                 VariableExpr * function = dynamic_cast< VariableExpr * >( appExpr->get_function() );
    409                                 assert( function );
    410                                 if ( function->get_var()->get_linkage() == LinkageSpec::Intrinsic ) return;
     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                                }
    411415                        }
    412416
     
    416420                        // replace argument to function call with temporary
    417421                        arg = new CommaExpr( cpCtor, new VariableExpr( tmp ) );
    418                         impCpCtorExpr->get_tempDecls().push_back( tmp );
    419                         impCpCtorExpr->get_dtors().push_front( makeCtorDtor( "^?{}", tmp ) );
     422                        impCpCtorExpr->tempDecls.push_back( tmp );
     423                        impCpCtorExpr->dtors.push_front( makeCtorDtor( "^?{}", tmp ) );
    420424                }
    421425
     
    427431                        CP_CTOR_PRINT( std::cerr << "ResolveCopyCtors: " << impCpCtorExpr << std::endl; )
    428432
    429                         ApplicationExpr * appExpr = impCpCtorExpr->get_callExpr();
     433                        ApplicationExpr * appExpr = impCpCtorExpr->callExpr;
    430434
    431435                        // take each argument and attempt to copy construct it.
    432                         for ( Expression * & arg : appExpr->get_args() ) {
    433                                 copyConstructArg( arg, impCpCtorExpr );
     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 );
    434448                        } // for
    435449
     
    437451                        // initialized with the return value and is destructed later
    438452                        // xxx - handle named return values?
    439                         Type * result = appExpr->get_result();
     453                        Type * result = appExpr->result;
    440454                        if ( ! result->isVoid() ) {
    441455                                static UniqueName retNamer("_tmp_cp_ret");
     
    443457                                env->apply( result );
    444458                                ObjectDecl * ret = ObjectDecl::newObject( retNamer.newName(), result, nullptr );
    445                                 ret->get_type()->set_const( false );
    446                                 impCpCtorExpr->get_returnDecls().push_back( ret );
     459                                ret->type->set_const( false );
     460                                impCpCtorExpr->returnDecls.push_back( ret );
    447461                                CP_CTOR_PRINT( std::cerr << "makeCtorDtor for a return" << std::endl; )
    448462                                if ( ! dynamic_cast< ReferenceType * >( result ) ) {
     
    551565                                Expression * retExpr = new CommaExpr( assign, new VariableExpr( returnDecl ) );
    552566                                // move env from callExpr to retExpr
    553                                 retExpr->set_env( callExpr->get_env() );
    554                                 callExpr->set_env( nullptr );
     567                                std::swap( retExpr->env, callExpr->env );
    555568                                return retExpr;
    556569                        } else {
     
    623636                }
    624637
    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 
    654638                DeclarationWithType * FixInit::postmutate( ObjectDecl *objDecl ) {
    655639                        // since this removes the init field from objDecl, it must occur after children are mutated (i.e. postmutate)
     
    750734                                        } else {
    751735                                                ImplicitCtorDtorStmt * implicit = strict_dynamic_cast< ImplicitCtorDtorStmt * > ( ctor );
    752                                                 ExprStmt * ctorStmt = dynamic_cast< ExprStmt * >( implicit->get_callStmt() );
     736                                                ExprStmt * ctorStmt = dynamic_cast< ExprStmt * >( implicit->callStmt );
    753737                                                ApplicationExpr * ctorCall = nullptr;
    754                                                 if ( ctorStmt && (ctorCall = isIntrinsicCallExpr( ctorStmt->get_expr() )) && ctorCall->get_args().size() == 2 ) {
     738                                                if ( ctorStmt && (ctorCall = isIntrinsicCallExpr( ctorStmt->expr )) && ctorCall->get_args().size() == 2 ) {
    755739                                                        // clean up intrinsic copy constructor calls by making them into SingleInits
    756                                                         objDecl->set_init( new SingleInit( ctorCall->get_args().back() ) );
    757                                                         ctorCall->get_args().pop_back();
     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();
    758745                                                } else {
    759746                                                        stmtsToAddAfter.push_back( ctor );
    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
     747                                                        objDecl->init = nullptr;
     748                                                        ctorInit->ctor = nullptr;
    777749                                                }
    778750                                        } // if
    779                                 } else if ( Initializer * init = ctorInit->get_init() ) {
    780                                         objDecl->set_init( init );
    781                                         ctorInit->set_init( nullptr );
     751                                } else if ( Initializer * init = ctorInit->init ) {
     752                                        objDecl->init = init;
     753                                        ctorInit->init = nullptr;
    782754                                } else {
    783755                                        // no constructor and no initializer, which is okay
    784                                         objDecl->set_init( nullptr );
     756                                        objDecl->init = nullptr;
    785757                                } // if
    786758                                delete ctorInit;
     
    818790
    819791
     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
    820824                void InsertDtors::previsit( FunctionDecl * funcDecl ) {
    821825                        // each function needs to have its own set of labels
    822826                        GuardValue( labelVars );
    823827                        labelVars.clear();
     828                        // LabelFinder does not recurse into FunctionDecl, so need to visit
     829                        // its children manually.
    824830                        maybeAccept( funcDecl->type, finder );
    825831                        maybeAccept( funcDecl->statements, finder );
    826832
    827833                        // 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
    828859                }
    829860
     
    855886                        if ( ! diff.empty() ) {
    856887                                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 ) );
    857906                        } // if
    858907                }
     
    880929                }
    881930
     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
    882944                void GenStructMemberCalls::previsit( FunctionDecl * funcDecl ) {
    883                         GuardValue( funcDecl );
     945                        GuardValue( function );
    884946                        GuardValue( unhandled );
    885947                        GuardValue( usedUninit );
     
    914976                }
    915977
    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 
    929978                void GenStructMemberCalls::postvisit( FunctionDecl * funcDecl ) {
    930979                        // remove the unhandled objects from usedUninit, because a call is inserted
     
    10791128                }
    10801129
    1081                 DeclarationWithType * MutatingResolver::mutate( ObjectDecl *objectDecl ) {
     1130                DeclarationWithType * MutatingResolver::mutate( ObjectDecl * objectDecl ) {
    10821131                        // add object to the indexer assumes that there will be no name collisions
    10831132                        // in generated code. If this changes, add mutate methods for entities with
     
    10871136                }
    10881137
    1089                 Expression* MutatingResolver::mutate( UntypedExpr *untypedExpr ) {
    1090                         return strict_dynamic_cast< ApplicationExpr * >( ResolvExpr::findVoidExpression( untypedExpr, indexer ) );
     1138                Expression * MutatingResolver::mutate( UntypedExpr * untypedExpr ) {
     1139                        Expression * newExpr = untypedExpr;
     1140                        ResolvExpr::findVoidExpression( newExpr, indexer );
     1141                        return newExpr;
    10911142                }
    10921143
     
    10941145                        static UniqueName tempNamer( "_tmp_ctor_expr" );
    10951146                        // xxx - is the size check necessary?
    1096                         assert( ctorExpr->has_result() && ctorExpr->get_result()->size() == 1 );
     1147                        assert( ctorExpr->result && ctorExpr->get_result()->size() == 1 );
    10971148
    10981149                        // 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.
     
    11131164
    11141165                        // resolve assignment and dispose of new env
    1115                         Expression * resolvedAssign = ResolvExpr::findVoidExpression( assign, indexer );
    1116                         delete resolvedAssign->env;
    1117                         resolvedAssign->env = nullptr;
    1118                         delete assign;
     1166                        ResolvExpr::findVoidExpression( assign, indexer );
     1167                        delete assign->env;
     1168                        assign->env = nullptr;
    11191169
    11201170                        // for constructor expr:
     
    11251175                        //   T & tmp;
    11261176                        //   &tmp = &x, ?{}(tmp), tmp
    1127                         CommaExpr * commaExpr = new CommaExpr( resolvedAssign, new CommaExpr( callExpr, new VariableExpr( tmp ) ) );
     1177                        CommaExpr * commaExpr = new CommaExpr( assign, new CommaExpr( callExpr, new VariableExpr( tmp ) ) );
    11281178                        commaExpr->set_env( env );
    11291179                        return commaExpr;
Note: See TracChangeset for help on using the changeset viewer.