polserver/pol-core/bscript/compiler/astbuilder/FunctionResolver.cpp
Kevin Eady e94bc86bda Support linking functions in super scope; explicit super:: scope calls (#695)
* Add support for checking base-class scopes in unscoped function calls

* Add support for `super::` call scope

* Address CI annotations; Discord discussions
- Shadowing
- Change itr post-inc to pre-inc
- Rename `to_link` to `to_check` to better describe its use.

* Address review comments

- Remove unnecessary to_check_itr assignment
2024-10-10 18:06:04 +02:00

650 lines
22 KiB
C++

#include "FunctionResolver.h"
#include <list>
#include "bscript/compiler/Profile.h"
#include "bscript/compiler/Report.h"
#include "bscript/compiler/ast/ClassDeclaration.h"
#include "bscript/compiler/ast/Function.h"
#include "bscript/compiler/ast/ModuleFunctionDeclaration.h"
#include "bscript/compiler/ast/UserFunction.h"
#include "bscript/compiler/astbuilder/AvailableParseTree.h"
#include "bscript/compiler/file/SourceFileIdentifier.h"
#include "bscript/compiler/file/SourceLocation.h"
#include "bscript/compiler/model/ClassLink.h"
#include "bscript/compiler/model/FunctionLink.h"
#include "clib/strutil.h"
namespace Pol::Bscript::Compiler
{
FunctionResolver::FunctionResolver( Report& report ) : report( report ) {}
FunctionResolver::~FunctionResolver() = default;
void FunctionResolver::force_reference( const ScopableName& name, const SourceLocation& loc )
{
register_function_link( name, std::make_shared<FunctionLink>( loc, "" /* calling scope */ ) );
}
void FunctionResolver::force_reference( const ScopeName& scope_name, const SourceLocation& loc )
{
register_class_link( scope_name, std::make_shared<ClassLink>( loc, scope_name.string() ) );
}
void FunctionResolver::register_available_generated_function( const SourceLocation& loc,
const ScopableName& name,
Node* context )
{
auto apt = AvailableParseTree{ loc, nullptr, name.string(), context };
register_available_function_parse_tree( loc, name, std::move( apt ) );
}
void FunctionResolver::register_available_user_function(
const SourceLocation& source_location,
EscriptGrammar::EscriptParser::FunctionDeclarationContext* ctx, bool force_reference )
{
ScopableName name( ScopeName::Global, ctx->IDENTIFIER()->getText() );
// Exported functions are always forced to be referenced so the code generator
// will emit the function's instructions, even though there is no FunctionCall
// to it.
register_available_user_function_parse_tree( source_location, ctx, std::move( name ),
force_reference || ctx->EXPORTED() );
}
void FunctionResolver::register_available_scoped_function(
const SourceLocation& source_location, const ScopeName& class_name,
EscriptGrammar::EscriptParser::FunctionDeclarationContext* ctx )
{
ScopableName name( class_name, ctx->IDENTIFIER()->getText() );
register_available_user_function_parse_tree( source_location, ctx, std::move( name ),
ctx->EXPORTED() );
}
void FunctionResolver::register_available_class_declaration(
const SourceLocation& loc, const ScopeName& class_name,
EscriptGrammar::EscriptParser::ClassDeclarationContext* ctx,
Node* top_level_statements_child_node )
{
register_available_class_decl_parse_tree( loc, ctx, class_name, top_level_statements_child_node );
}
void FunctionResolver::register_class_link( const ScopeName& name,
std::shared_ptr<ClassLink> class_link )
{
if ( resolve_if_existing( name, class_link ) )
return;
unresolved_classes[name].push_back( std::move( class_link ) );
}
void FunctionResolver::register_function_link( const ScopableName& name,
std::shared_ptr<FunctionLink> function_link )
{
const auto& calling_scope = function_link->calling_scope;
const auto& unscoped_name = name.name;
// If a call scope was given, _only_ check that one.
if ( !name.scope.global() )
{
if ( resolve_if_existing( name, function_link ) )
return;
}
else
{
// If calling scope present, check, eg. inside Animal class, `foo()` checks
// `Animal::foo()`.
if ( !calling_scope.empty() &&
resolve_if_existing( { calling_scope, unscoped_name }, function_link ) )
return;
// Check global scope, only if calling scope is empty. If we are inside eg.
// `class Animal` and call `foo()`, we do not want to link to an
// _already-registered_ global function `foo()`. Adding this unresolved
// function link with a empty scope name will eventually resolve it to the
// correctly scoped function.
if ( calling_scope.empty() &&
resolve_if_existing( ScopableName( ScopeName::Global, unscoped_name ), function_link ) )
return;
}
report.debug( function_link->source_location, "registering funct link {}", name.string() );
unresolved_function_links[name].push_back( std::move( function_link ) );
}
std::string FunctionResolver::register_function_expression(
const SourceLocation& source_location,
EscriptGrammar::EscriptParser::FunctionExpressionContext* ctx )
{
auto name = function_expression_name( source_location );
auto apt = AvailableParseTree{ source_location, ctx, "", nullptr };
available_user_function_parse_trees.insert( { name, apt } );
return name;
}
void FunctionResolver::register_module_function( ModuleFunctionDeclaration* mf )
{
const auto& name = mf->name;
auto itr = available_user_function_parse_trees.find( name );
if ( itr != available_user_function_parse_trees.end() )
{
const auto& previous = ( *itr ).second;
report.error( previous.source_location,
"User Function '{}' conflicts with Module Function of the same name.\n"
" Module Function declaration: {}",
name, mf->source_location );
}
resolved_functions[{ ScopeName::Global, name }] = mf;
resolved_functions[{ mf->scope, name }] = mf;
}
void FunctionResolver::register_user_function( const std::string& scope, UserFunction* uf )
{
ScopableName scoped_name( scope, uf->name );
auto itr = resolved_functions.find( scoped_name );
if ( itr != resolved_functions.end() )
{
// Throw an exception, this should _never_ happen. If this does, then an
// AvailableParseTree was visited twice.
auto msg = fmt::format( "duplicate user function definition for {}: {} vs {}", uf->name,
uf->source_location, ( *itr ).second->source_location );
uf->internal_error( msg );
}
resolved_functions[scoped_name] = uf;
report.debug( uf->source_location, "registering uf {}", scoped_name );
// Constructors are registered in global scope
if ( uf->type == UserFunctionType::Constructor )
{
ScopableName global_name( ScopeName::Global, uf->name );
resolved_functions[global_name] = uf;
report.debug( uf->source_location, "registering uf {}", global_name );
}
}
void FunctionResolver::register_class_declaration( ClassDeclaration* cd )
{
resolved_classes[cd->name] = cd;
}
bool FunctionResolver::resolve( std::vector<AvailableParseTree>& to_build_ast )
{
// Attempt to link all unresolved function links
for ( auto unresolved_itr = unresolved_function_links.begin();
unresolved_itr != unresolved_function_links.end(); )
{
for ( auto function_link_itr = ( *unresolved_itr ).second.begin();
function_link_itr != ( *unresolved_itr ).second.end(); )
{
auto& function_link = *function_link_itr;
const auto& calling_scope = function_link->calling_scope;
const auto& name = ( *unresolved_itr ).first;
const auto& unscoped_name = name.name;
const auto& call_scope = name.scope;
// Link the function if possible, otherwise try to build it.
auto link_handled = [&]( const ScopableName& key )
{
// A super:: call cannot be handled by the current call scope.
if ( call_scope.super() && Clib::caseInsensitiveEqual( key.scope.string(), calling_scope ) )
{
return false;
}
if ( resolve_if_existing( key, function_link ) )
{
// Remove this function link from the list of links.
function_link_itr = ( *unresolved_itr ).second.erase( function_link_itr );
return true;
};
if ( build_if_available( to_build_ast, calling_scope, key ) )
{
// Keep the link in the list, as it may be resolved later.
++function_link_itr;
return true;
}
return false;
};
report.debug( function_link->source_location, "resolving funct link {}", name );
// If a call scope was given, _only_ check that one (except super::).
if ( !call_scope.empty() && !call_scope.super() )
{
if ( link_handled( { call_scope, unscoped_name } ) )
continue;
}
else
{
if ( !calling_scope.empty() )
{
std::set<std::string> visited;
std::list<std::string> to_check( { calling_scope } );
bool handled = false;
for ( auto to_check_itr = to_check.begin(); !handled && to_check_itr != to_check.end();
++to_check_itr )
{
auto cd_itr = resolved_classes.find( *to_check_itr );
if ( cd_itr == resolved_classes.end() )
continue;
auto cd = cd_itr->second;
if ( visited.find( cd->name ) != visited.end() )
continue;
visited.insert( cd->name );
if ( link_handled( { cd->name, unscoped_name } ) )
{
handled = true;
break;
}
for ( const auto& base_cd_link : cd->base_class_links )
{
if ( auto base_cd = base_cd_link->class_declaration() )
{
to_check.push_back( base_cd->name );
}
}
}
if ( handled )
continue;
}
// Check global scope (if not explicitly calling super:: scope)
if ( !call_scope.super() && link_handled( { ScopeName::Global, unscoped_name } ) )
continue;
}
// Link was not handled, move to the next one
++function_link_itr;
}
// If all links were resolved, remove the entry.
if ( ( *unresolved_itr ).second.empty() )
{
unresolved_itr = unresolved_function_links.erase( unresolved_itr );
}
else
{
// Do not complain here, as the identifier in `[scope::]name` may be a variable.
// Error handled in SemanticAnalyzer.
++unresolved_itr;
}
}
for ( auto unresolved_itr = unresolved_classes.begin();
unresolved_itr != unresolved_classes.end(); )
{
auto scope = unresolved_itr->first;
for ( auto class_link_itr = ( *unresolved_itr ).second.begin();
class_link_itr != ( *unresolved_itr ).second.end(); )
{
auto& class_link = *class_link_itr;
report.debug( class_link->source_location, "resolving class link {}", scope.string() );
// Link the function if possible, otherwise try to build it.
if ( resolve_if_existing( scope, class_link ) )
{
// Remove this function link from the list of links.
class_link_itr = ( *unresolved_itr ).second.erase( class_link_itr );
}
else if ( build_if_available( to_build_ast, scope ) )
{
// Keep the link in the list, as it may be resolved later.
class_link_itr = ( *unresolved_itr ).second.erase( class_link_itr );
}
else
{
++class_link_itr;
}
}
// If all links were resolved, remove the entry.
if ( ( *unresolved_itr ).second.empty() )
{
unresolved_itr = unresolved_classes.erase( unresolved_itr );
}
else
{
++unresolved_itr;
}
}
return !to_build_ast.empty();
}
std::string FunctionResolver::function_expression_name( const SourceLocation& source_location )
{
return fmt::format( "funcexpr@{}:{}:{}", source_location.source_file_identifier->index,
source_location.range.start.line_number,
source_location.range.start.character_column );
}
void FunctionResolver::register_available_function_parse_tree(
const SourceLocation& source_location, const ScopableName& name, const AvailableParseTree& apt )
{
const auto& unscoped_name = name.name;
// Eg. "foo" or "Animal::foo"
auto scoped_name = name.string();
// Eg. "", "Animal"
auto scope = name.scope.string();
report.debug( source_location, "registering funct apt {}", scoped_name );
auto itr = available_user_function_parse_trees.find( scoped_name );
if ( itr != available_user_function_parse_trees.end() )
{
AvailableParseTree& previous = ( *itr ).second;
report.error( source_location,
"Function '{}' defined more than once.\n"
" Previous declaration: {}",
scoped_name, previous.source_location );
}
auto itr2 = resolved_functions.find( { scope, unscoped_name } );
if ( itr2 != resolved_functions.end() )
{
auto* previous = ( *itr2 ).second;
std::string what = dynamic_cast<ModuleFunctionDeclaration*>( previous ) == nullptr
? "User Function"
: "Module Function";
report.error( source_location,
"User Function '{}' conflicts with {} of the same name.\n"
" {} declaration: {}",
scoped_name, what, what, previous->source_location );
}
auto itr3 = available_class_decl_parse_trees.find( scope );
if ( itr3 != available_class_decl_parse_trees.end() )
{
auto previous = ( *itr3 ).second;
report.error( source_location,
"User Function '{}' conflicts with Class of the same name.\n"
" Class declaration: {}",
scoped_name, previous.source_location );
}
available_user_function_parse_trees.insert( { scoped_name, apt } );
}
void FunctionResolver::register_available_user_function_parse_tree(
const SourceLocation& source_location, antlr4::ParserRuleContext* ctx, const ScopableName& name,
bool force_reference )
{
register_available_function_parse_tree( source_location, name,
{ source_location, ctx, name.scope.string(), nullptr } );
if ( force_reference )
{
// just make sure there is an entry, so that we build an AST for it
register_function_link( name, std::make_shared<FunctionLink>(
source_location, name.scope.string() /* calling scope */ ) );
}
}
void FunctionResolver::register_available_class_decl_parse_tree(
const SourceLocation& source_location, antlr4::ParserRuleContext* ctx,
const ScopeName& scope_name, Node* top_level_statements_child_node )
{
const auto name = scope_name.string();
report.debug( source_location, "registering class apt ({}).", name );
auto itr = available_user_function_parse_trees.find( name );
if ( itr != available_user_function_parse_trees.end() )
{
AvailableParseTree& previous = ( *itr ).second;
report.error( source_location,
"Class '{}' conflicts with User Function of the same name.\n"
" Previous declaration: {}",
name, previous.source_location );
}
auto itr2 = available_class_decl_parse_trees.find( name );
if ( itr2 != available_class_decl_parse_trees.end() )
{
AvailableParseTree& previous = ( *itr2 ).second;
report.error( source_location,
"Class '{}' defined more than once.\n"
" Previous declaration: {}",
name, previous.source_location );
}
auto itr3 = resolved_functions.find( { ScopeName::Global, name } );
if ( itr3 != resolved_functions.end() )
{
auto* previous = ( *itr3 ).second;
std::string what = dynamic_cast<ModuleFunctionDeclaration*>( previous ) == nullptr
? "User Function"
: "Module Function";
report.error( source_location,
"Class '{}' conflicts with {} of the same name.\n"
" {} declaration: {}",
name, what, what, previous->source_location );
}
auto apt = AvailableParseTree{ source_location, ctx, name, top_level_statements_child_node };
available_class_decl_parse_trees.insert( { name, apt } );
}
Function* FunctionResolver::check_existing( const ScopableName& key,
bool requires_constructor ) const
{
auto itr = resolved_functions.find( key );
if ( itr != resolved_functions.end() )
{
if ( requires_constructor )
{
auto uf = dynamic_cast<UserFunction*>( ( *itr ).second );
if ( !uf || uf->type != UserFunctionType::Constructor )
{
return nullptr;
}
}
return ( *itr ).second;
}
return nullptr;
}
ClassDeclaration* FunctionResolver::check_existing( const ScopeName& key ) const
{
auto itr = resolved_classes.find( key );
if ( itr != resolved_classes.end() )
{
return ( *itr ).second;
}
return nullptr;
}
bool FunctionResolver::build_if_available( std::vector<AvailableParseTree>& to_build_ast,
const std::string& calling_scope,
const ScopableName& call )
{
AvailableParseTreeMap::iterator itr;
// If a call scope was given, _only_ check that one (except if super:: provided)
// eg. `Animal::foo()` will only search for `Animal::foo()`, disregarding a possible parent
// scoped `::foo()`.
if ( !call.global() && !call.scope.super() )
{
itr = available_user_function_parse_trees.find( call.string() );
if ( itr != available_user_function_parse_trees.end() )
{
to_build_ast.push_back( ( *itr ).second );
report.debug( ( *itr ).second.source_location, "adding to build funct [call] {}: {}",
to_build_ast.back(), call.string() );
available_user_function_parse_trees.erase( itr );
return true;
}
// Check if there is a class available with that scope, as it could provide this function
// later.
if ( build_if_available( to_build_ast, call.scope ) )
{
return true;
}
// Nothing found. Can't build a scoped function call.
return false;
}
// Inside a scope, eg. `Animal`...
if ( !calling_scope.empty() )
{
// Check if exists in given `scope`, eg. `foo()` checks `Animal::foo()`
auto handled_by_scope = [&]( const std::string& scope )
{
// Skip checking current scope if doing `super::` call.
if ( call.scope.super() && Clib::caseInsensitiveEqual( scope, calling_scope ) )
{
return false;
}
auto scoped_call_name = fmt::format( "{}::{}", scope, call.name );
itr = available_user_function_parse_trees.find( scoped_call_name );
if ( itr != available_user_function_parse_trees.end() )
{
to_build_ast.push_back( ( *itr ).second );
report.debug( ( *itr ).second.source_location, "adding to build funct [scoped] {}: {}",
to_build_ast.back(), scoped_call_name );
available_user_function_parse_trees.erase( itr );
return true;
}
return false;
};
std::set<std::string> visited;
std::list<std::string> to_check( { calling_scope } );
for ( auto to_check_itr = to_check.begin(); to_check_itr != to_check.end(); ++to_check_itr )
{
auto cd_itr = resolved_classes.find( *to_check_itr );
if ( cd_itr == resolved_classes.end() )
continue;
auto cd = cd_itr->second;
if ( visited.find( cd->name ) != visited.end() )
continue;
visited.insert( cd->name );
if ( handled_by_scope( cd->name ) )
{
break;
}
for ( const auto& base_cd_link : cd->base_class_links )
{
if ( auto base_cd = base_cd_link->class_declaration() )
{
to_check.push_back( base_cd->name );
}
}
}
}
// Check if there is a class available with the function name.
if ( build_if_available( to_build_ast, call.name ) )
{
return true;
}
// If call scope is empty, check if function is a constructor, eg. Foo() -> Foo::Foo
// The register_user_function will only register the function in global scope if it _is_ a
// constructor.
if ( call.scope.global() )
{
auto scoped_call_name = fmt::format( "{}::{}", call.name, call.name );
itr = available_user_function_parse_trees.find( scoped_call_name );
if ( itr != available_user_function_parse_trees.end() )
{
to_build_ast.push_back( ( *itr ).second );
report.debug( ( *itr ).second.source_location, "adding to build funct [ctor?] {}: {}",
to_build_ast.back(), scoped_call_name );
available_user_function_parse_trees.erase( itr );
return true;
}
}
// Check if exists in global scope.
itr = available_user_function_parse_trees.find( call.name );
if ( itr != available_user_function_parse_trees.end() )
{
to_build_ast.push_back( ( *itr ).second );
report.debug( ( *itr ).second.source_location, "adding to build funct [global] {}: {}",
to_build_ast.back(), call.name );
available_user_function_parse_trees.erase( itr );
return true;
}
return false;
}
bool FunctionResolver::build_if_available( std::vector<AvailableParseTree>& to_build_ast,
const ScopeName& scope )
{
auto itr = available_class_decl_parse_trees.find( scope.string() );
if ( itr != available_class_decl_parse_trees.end() )
{
to_build_ast.push_back( ( *itr ).second );
report.debug( ( *itr ).second.source_location, "adding to build class [call.scope] {}: {}",
to_build_ast.back(), scope.string() );
available_class_decl_parse_trees.erase( itr );
return true;
}
return false;
}
bool FunctionResolver::resolve_if_existing( const ScopableName& key,
std::shared_ptr<FunctionLink>& function_link )
{
auto resolved_function = check_existing( key, function_link->require_ctor );
if ( resolved_function )
{
function_link->link_to( resolved_function );
report.debug( function_link->source_location, "linking {} to {}::{} @ {}", key,
resolved_function->scope, resolved_function->name, (void*)resolved_function );
return true;
}
return false;
}
bool FunctionResolver::resolve_if_existing( const ScopeName& scope,
std::shared_ptr<ClassLink>& class_link )
{
auto resolved_class = check_existing( scope );
if ( resolved_class )
{
class_link->link_to( resolved_class );
report.debug( class_link->source_location, "linking class {} to {} @ {}", scope.string(),
resolved_class->name, (void*)resolved_class );
return true;
}
return false;
};
} // namespace Pol::Bscript::Compiler