#include "builtins/builtins.h"
#include "precedence.h"
#include "helpers.h"
#include "tuple.h"
using namespace goose;
using namespace goose::eir;
using namespace goose::cir;
using namespace goose::parse;
namespace goose::builtins
{
void SetupAssignmentOps( Env& e )
{
auto assOp = GetOrCreateOverloadSet( e, "operator_assign"_sid );
// Generic function to perform the assignation of a builtin runtime type
// to a mutable reference of that same type.
auto BuiltinTypeAssignment = []( auto&& c, auto&& lhs, auto&& rhs ) -> Value
{
G_VAL_ASSERT( lhs, !lhs.isConstant() );
auto refType = *FromValue< ReferenceType >( *ValueFromEIR( lhs.type() ) );
if( !ParseTypePredicates( c, *ValueFromEIR( refType.type() ) ) )
return PoisonValue();
const auto& cb = c.codeBuilder();
if( !cb )
{
DiagnosticsManager::GetInstance().emitSyntaxErrorMessage( 0, "assignments are not allowed here." );
return PoisonValue();
}
if( auto bb = cb->cfg()->currentBB() )
bb->emplace_back( Store( lhs.cir(), refType.type(), rhs, lhs.locationId() ) );
// Return the ref to support chained assignments, like in c++.
return Value( lhs );
};
// Generic function to assign a value to a decl (local variable declaration)
auto DeclAssignment = []( auto&& c, auto&& lhs, auto&& rhs ) -> Value
{
if( !c.codeBuilder() )
{
DiagnosticsManager::GetInstance().emitSyntaxErrorMessage( 0, "variable declarations are not allowed here." );
return PoisonValue();
}
auto decl = *FromValue< Decl >( lhs );
return DeclareLocalVar( c, decl.type(), decl.name(), rhs, lhs.locationId() );
};
// Generic function to assign a value to a template named decl (local variable declaration with local type inference)
auto TNamedDeclAssignment = []( auto&& c, auto&& lhs, auto&& rhs ) -> Value
{
if( !c.codeBuilder() )
{
DiagnosticsManager::GetInstance().emitSyntaxErrorMessage( 0, "variable declarations are not allowed here." );
return PoisonValue();
}
auto tndecl = *FromValue< TNamedDecl >( lhs );
return DeclareLocalVarWithTypeInference( c, tndecl.type(), tndecl.name(), rhs, lhs.locationId() );
};
// Tuple assignment.
//
// We create anonymous variables with type inference, and initialize them with
// the values form the rhs tuple. Then we assign each of them to members of the lhs tuple.
//
// The reason we do all this is so that cases such as
// a,b = b,a work as expected.
//
// We count on llvm to elide the temp vars in case they are not needed, but then again
// we do already count on it to elide all those load and stores and allocas anyway.
//
// The real performance overhead danger here is for complex type whose InitializeVar()
// and Destroy() implementations emit code that can't be elided and that may generate object
// copies.
//
// TODO: Ideally, we should restrict tuple assignation only to types that can be moved.
// For types that can only be copied, it may be preferable to write it the old fashioned
// way with a temp var to make the copy more apparent.
// But complex types and the notion of moving them don't even exist yet.
auto TupleAssignment = [assOp]( auto&& c, auto&& lhs, auto&& rhs ) -> Value
{
auto tupSize = TupleSize( lhs );
if( tupSize != TupleSize( rhs ) )
{
DiagnosticsManager::GetInstance().emitErrorMessage( 0, "incompatible tuple sizes." );
return PoisonValue();
}
// Load the values from the rhs tuple into temporary vars.
vector< Value > tempVars;
tempVars.reserve( tupSize );
bool success = true;
ForEachInTuple( rhs, [&]( auto&& srcVal )
{
// We go at very high level to construct the temporary variables by resolving an invocation
// of the assignment of the rhs value to a TNamedDecl with an empty name.
// The reason we need to work at such high level is so that the initializer value gets
// resolved exactly like if it was a regular assignment. In particular, if the value in question
// is a locvar, we want it to go through the type checking process to be replaced with its content.
// This is the simplest and most robust way to achieve this, which should honor every relevant
// extension point.
auto anonVarDecl = ToValue( TNamedDecl( ValueToEIR( ToValue( TVar( "_"_sid ) ) ), ""_sid ) );
auto tmpVar = InvokeOverloadSet( c,
assOp, MakeTuple( anonVarDecl, srcVal ) );
if( tmpVar.isPoison() )
{
success = false;
return false;
}
tmpVar.setLocationId( srcVal.locationId() );
tempVars.emplace_back( move( tmpVar ) );
return true;
} );
if( !success )
return PoisonValue();
auto result = EmptyTuple();
size_t i = 0;
ForEachInTuple( lhs, [&]( auto&& destVal )
{
auto r = InvokeOverloadSet( c,
assOp, MakeTuple( destVal, tempVars[i++] ) );
if( r.isPoison() )
{
success = false;
return false;
}
result = AppendToTuple( result, move( r ) );
return true;
} );
if( !success )
return PoisonValue();
return result;
};
BuildParseRule( e, "="_sid,
RightAssInfixOp( "operator_assign"_sid, precedence::AssignmentOp,
// Assignment with a decl of type $T to the left and a value of type $T to the right:
// Local variable declaration and initialization.
ForTypes< CustomPattern< Decl, Decl::Pattern >, CustomPattern< Value, ForwarderPattern > >( DeclAssignment ),
// Assignment with a tnameddecl to the left and a value to the right:
// Local variable declaration and initialization with type inference.
ForTypes< CustomPattern< TNamedDecl, TNamedDecl::Pattern >, CustomPattern< Value, ForwarderPattern > >( TNamedDeclAssignment ),
ForType< CustomPattern< Value, TuplePattern > >( TupleAssignment ),
// Mutable ref assignment
ForTypes< CustomPattern< Value, ReferenceType::PatternAnyMutableOfTypeT >,
CustomPattern< Value, ValuePatternT > >( BuiltinTypeAssignment ),
// Explicit overload for assigning an rt_int to an rt_int reference:
// We need this to be able to assign a ct_int to a rt_int: the generic version
// above can't perform the necessary implicit conversion.
ForTypes< CustomPattern< Value, ReferenceType::PatternMutable< IntegerType::Pattern > >,
CustomPattern< Value, IntegerType::Pattern > >(
BuiltinTypeAssignment )
)
);
}
}