polserver/pol-core/bscript/compiler/astbuilder/GeneratedFunctionBuilder.cpp
Kevin Eady 82cf03ba2a Generate constructors for child classes (#703)
* Make constructor_link nullable; WIP generated function builder changes

* saving work

* saving work, need to resolve double call

* hopefully finish up super inherited constructors

* Error if no base class has ctor

* Fix AddressSanitizer error...?

* Revert unnecessary edit

* Remove duplicate code

* Update pol-core/bscript/compiler/astbuilder/GeneratedFunctionBuilder.cpp
2024-10-10 18:06:04 +02:00

272 lines
No EOL
10 KiB
C++

#include "GeneratedFunctionBuilder.h"
#include <boost/range/adaptor/reversed.hpp>
#include <boost/range/adaptor/sliced.hpp>
#include <list>
#include <set>
#include "bscript/compiler/Report.h"
#include "bscript/compiler/ast/Argument.h"
#include "bscript/compiler/ast/ArrayInitializer.h"
#include "bscript/compiler/ast/ClassDeclaration.h"
#include "bscript/compiler/ast/FunctionBody.h"
#include "bscript/compiler/ast/FunctionCall.h"
#include "bscript/compiler/ast/FunctionParameterDeclaration.h"
#include "bscript/compiler/ast/FunctionParameterList.h"
#include "bscript/compiler/ast/FunctionReference.h"
#include "bscript/compiler/ast/GeneratedFunction.h"
#include "bscript/compiler/ast/Identifier.h"
#include "bscript/compiler/ast/ReturnStatement.h"
#include "bscript/compiler/ast/SpreadElement.h"
#include "bscript/compiler/ast/UserFunction.h"
#include "bscript/compiler/ast/ValueConsumer.h"
#include "bscript/compiler/astbuilder/BuilderWorkspace.h"
#include "bscript/compiler/astbuilder/SimpleValueCloner.h"
#include "bscript/compiler/model/ClassLink.h"
#include "bscript/compiler/model/FunctionLink.h"
#include "bscript/compiler/model/ScopableName.h"
namespace Pol::Bscript::Compiler
{
GeneratedFunctionBuilder::GeneratedFunctionBuilder( const SourceFileIdentifier& loc,
BuilderWorkspace& workspace )
: CompoundStatementBuilder( loc, workspace )
{
}
void GeneratedFunctionBuilder::super_function( std::unique_ptr<GeneratedFunction>& super )
{
std::vector<UserFunction*> base_class_ctors;
std::set<ClassDeclaration*> visited;
std::list<std::shared_ptr<ClassLink>> to_visit;
auto class_declaration = super->class_declaration();
to_visit.insert( to_visit.end(), class_declaration->base_class_links.begin(),
class_declaration->base_class_links.end() );
for ( auto to_link_itr = to_visit.begin(); to_link_itr != to_visit.end();
to_link_itr = to_visit.erase( to_link_itr ) )
{
if ( auto base_cd = ( *to_link_itr )->class_declaration() )
{
if ( visited.find( base_cd ) != visited.end() )
{
continue;
}
visited.insert( base_cd );
if ( auto constructor_link = base_cd->constructor_link )
{
if ( auto base_class_ctor = constructor_link->user_function() )
{
base_class_ctors.push_back( base_class_ctor );
}
}
}
}
build( super, base_class_ctors );
}
void GeneratedFunctionBuilder::constructor_function(
std::unique_ptr<GeneratedFunction>& constructor )
{
std::set<ClassDeclaration*> visited;
std::list<std::shared_ptr<ClassLink>> to_visit;
auto class_declaration = constructor->class_declaration();
UserFunction* base_class_ctor = nullptr;
// If the class declaration _has_ a constructor link, then it registered a
// constructor but it was never resolved. This should never happen.
if ( class_declaration->constructor_link != nullptr )
{
// If the class has a _linked_ constructor, then we should not have
// generated a constructor function.
if ( class_declaration->constructor_link->function() != nullptr )
class_declaration->internal_error(
fmt::format( "class {} has linked ctor", class_declaration->name ) );
class_declaration->internal_error(
fmt::format( "class {} unlinked ctor", class_declaration->name ) );
}
to_visit.insert( to_visit.end(), class_declaration->base_class_links.begin(),
class_declaration->base_class_links.end() );
for ( auto to_link_itr = to_visit.begin(); to_link_itr != to_visit.end();
to_link_itr = to_visit.erase( to_link_itr ) )
{
if ( auto base_cd = ( *to_link_itr )->class_declaration() )
{
if ( visited.find( base_cd ) != visited.end() )
{
continue;
}
visited.insert( base_cd );
if ( auto constructor_link = base_cd->constructor_link )
{
if ( auto ctor = constructor_link->user_function() )
{
base_class_ctor = ctor;
break;
}
}
}
}
if ( base_class_ctor != nullptr )
{
build( constructor, { base_class_ctor } );
}
// Set the generated function as the class' constructor function.
class_declaration->constructor_link =
std::make_unique<FunctionLink>( constructor->source_location, "", true );
class_declaration->constructor_link->link_to( constructor.get() );
}
void GeneratedFunctionBuilder::build( std::unique_ptr<GeneratedFunction>& function,
std::vector<UserFunction*> base_class_ctors )
{
const auto& loc = function->source_location;
// If there are no base ctors, skip updating the function's parameters and
// body, therefore a super function that is "invalid" will have no parameters
// or body.
if ( !base_class_ctors.empty() )
{
auto& function_parameters = function->child<FunctionParameterList>( 0 ).children;
function_parameters.push_back( std::make_unique<FunctionParameterDeclaration>(
loc, ScopableName( ScopeName::None, "this" ), true /* byref */, false /* unused */,
false /* rest */ ) );
// Our super() alias'ed parameter can be a rest parameter only if the not-last
// constructors are not variadic.
bool can_use_rest = true;
for ( const auto* base_class_ctor :
base_class_ctors | boost::adaptors::sliced( 0, base_class_ctors.size() - 1 ) )
{
if ( base_class_ctor->is_variadic() )
{
can_use_rest = false;
break;
}
}
std::set<ScopableName> visited_arg_names;
for ( auto base_class_ctor : base_class_ctors )
{
add_base_constructor( function, base_class_ctor, visited_arg_names, can_use_rest );
}
}
std::string desc;
Node::describe_tree_to_indented( *function, desc, 0 );
workspace.report.debug( loc, "Generated function: {}", desc );
}
void GeneratedFunctionBuilder::add_base_constructor( std::unique_ptr<GeneratedFunction>& super,
UserFunction* base_class_ctor,
std::set<ScopableName>& visited_arg_names,
bool can_use_rest )
{
auto& function_parameters = super->child<FunctionParameterList>( 0 ).children;
auto& body = super->child<FunctionBody>( 1 ).children;
auto params = base_class_ctor->parameters();
const auto& loc = super->source_location;
auto call_arguments = std::vector<std::unique_ptr<Argument>>();
bool first = true;
for ( auto& param_ref : params )
{
auto& param = param_ref.get();
auto param_scope = first ? ScopeName::None
: param.name.scope.empty() ? ScopeName( base_class_ctor->name )
: param.name.scope;
auto param_name = ScopableName( param_scope, param.name.name );
// Skip the first `this` parameter of the function declaration, as we've already added it.
if ( !first && visited_arg_names.find( param_name ) == visited_arg_names.end() )
{
// If the base ctor parameter is a rest param, our super() function parameter
// will _not_ be a rest, but a regular variable with a default [empty]
// array value. This only applies if we _cannot_ use rest parameters.
if ( param.rest && !can_use_rest )
{
function_parameters.push_back( std::make_unique<FunctionParameterDeclaration>(
loc, param_name, param.byref, param.unused, false,
std::make_unique<ArrayInitializer>( param.source_location,
std::vector<std::unique_ptr<Expression>>() ) ) );
}
// If the base ctor parameter has a default value, our super() function
// parameter will have the same default value.
else if ( auto default_value = param.default_value() )
{
SimpleValueCloner cloner( report, default_value->source_location );
// Value must be cloneable
if ( auto final_argument = cloner.clone( *default_value ) )
{
function_parameters.push_back( std::make_unique<FunctionParameterDeclaration>(
loc, param_name, param.byref, param.unused, false, std::move( final_argument ) ) );
}
else
{
report.error( loc,
"In construction of '{}': Unable to create argument from default for "
"parameter '{}'.\n"
" See also: {}",
super->scoped_name(), param.name, param.source_location );
return;
}
}
else
{
// This super() alias'ed parameter is a rest parameter if the base
// ctor's parameter is rest _and_ we can use a rest parameter.
auto is_rest_param = param.rest && can_use_rest;
function_parameters.push_back( std::make_unique<FunctionParameterDeclaration>(
loc, param_name, param.byref, param.unused, is_rest_param ) );
}
}
// By default, the function call argument inside this super()'s function
// declaration will just be the super() alias'ed parameter.
std::unique_ptr<Expression> call_argument =
std::make_unique<Identifier>( param.source_location, param_name );
// If the base ctor parameter is a rest param, the base_ctor() function
// call will spread the super() function parameter -- an array -- into
// the base constructor.
if ( param.rest )
{
// Passing an element that is not spreadable (eg. a string) will result
// in an empty array passed to the base constructor.
call_argument =
std::make_unique<SpreadElement>( param.source_location, std::move( call_argument ) );
}
call_arguments.insert( call_arguments.end(),
std::make_unique<Argument>( param.source_location, param.name,
std::move( call_argument ), param.rest ) );
visited_arg_names.insert( param_name );
first = false;
}
auto fc = std::make_unique<FunctionCall>(
loc, base_class_ctor->name, ScopableName( base_class_ctor->name, base_class_ctor->name ),
std::move( call_arguments ) );
fc->function_link->link_to( base_class_ctor );
auto value_consumer = std::make_unique<ValueConsumer>( fc->source_location, std::move( fc ) );
body.push_back( std::move( value_consumer ) );
}
} // namespace Pol::Bscript::Compiler