Skip to content

Commit

Permalink
SP004: implement initialize list translation to ctor
Browse files Browse the repository at this point in the history
This is the first change to implement part of SP004, what this
change does:

1. We synthesize a member-wise constructor for each struct follow
   the rules described in SP004.

2. Add logic to translate the initialize list to constructor invoke

TODO: what needs to be done next

1. We didn't fill the body of the synthesized constructor
  - This is relative easy task.

2. The fall back to legacy initialize list logic is not done
  - I found out that it's straight forward to put this logic after
    ResolveInvoke() call, because after ResolveInvoke() we will need to
    do a final 'coerce()' check to determine whether the candidate is
    applicable to use. If that fails, we can do the C-Style struct check
    and fall back to legacy logic.

3. Should we enable this for stdlib as well?
  - Our stdlib has some syntax that external users cannot use, so whether this
    change could break anything is unknown for now. It needs some extra
    effort to investigate and debugging the potential compile issue.
  • Loading branch information
kaizhangNV committed Oct 2, 2024
1 parent 68c2834 commit c759f4d
Show file tree
Hide file tree
Showing 5 changed files with 371 additions and 54 deletions.
248 changes: 205 additions & 43 deletions source/slang/slang-check-conversion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ namespace Slang
Type* toType,
Expr** outToExpr,
InitializerListExpr* fromInitializerListExpr,
UInt &ioInitArgIndex)
UInt &ioInitArgIndex,
bool useLegacyMode)
{
// First, we will check if we have run out of arguments
// on the initializer list.
Expand Down Expand Up @@ -160,7 +161,8 @@ namespace Slang
toType,
outToExpr,
fromInitializerListExpr,
ioInitArgIndex);
ioInitArgIndex,
useLegacyMode);
}

DeclRefType* findBaseStructType(ASTBuilder* astBuilder, DeclRef<StructDecl> structTypeDeclRef)
Expand Down Expand Up @@ -201,11 +203,148 @@ namespace Slang
return baseStructDeclRef;
}

bool SemanticsVisitor::_hasExplicitConstructor(StructDecl* structDecl)
{
bool result = false;
for (auto ctor : getMembersOfType<ConstructorDecl>(getASTBuilder(), structDecl, MemberFilterStyle::All))
{
// constructor that is not synthesized must be user defined.
if (ctor.getDecl()->findModifier<SynthesizedModifier>() == nullptr)
{
result = true;
break;
}
}
return result;
}

// TODO: We might need to find a good way to get the synthesized constructor instead of traversing all of constructors.
ConstructorDecl* SemanticsVisitor::_getSynthesizedConstructor(StructDecl* structDecl)
{
for (auto ctor : getMembersOfType<ConstructorDecl>(getASTBuilder(), structDecl, MemberFilterStyle::All))
{
if (ctor.getDecl()->findModifier<SynthesizedModifier>() != nullptr)
{
return ctor.getDecl();
}
}

return nullptr;
}

Expr* SemanticsVisitor::_prepareCtorInvokeExpr(Type* toType, const SourceLoc& loc, const List<Expr*>& coercedArgs)
{
auto* varExpr = getASTBuilder()->create<VarExpr>();
varExpr->type = (QualType)getASTBuilder()->getTypeType(toType);
varExpr->declRef = isDeclRefTypeOf<Decl>(toType);

auto* constructorExpr = getASTBuilder()->create<InvokeExpr>();
constructorExpr->functionExpr = varExpr;
constructorExpr->arguments.addRange(coercedArgs);
constructorExpr->loc = loc;
auto resolvedConstructorExpr = CheckExpr(constructorExpr);

return resolvedConstructorExpr;
}

// translation from initializer list to constructor invocation if the struct has constructor.
bool SemanticsVisitor::_invokeExprForExplicitCtor(Type* toType, InitializerListExpr* fromInitializerListExpr, Expr** outExpr)
{
if(auto toDeclRefType = as<DeclRefType>(toType))
{
auto toTypeDeclRef = toDeclRefType->getDeclRef();
if(auto toStructDeclRef = toTypeDeclRef.as<StructDecl>())
{
if (isFromStdLib(toStructDeclRef.getDecl()))
{
// If the struct is from stdlib, we will not try to create a constructor.
return false;
}

if (_hasExplicitConstructor(toStructDeclRef.getDecl()))
{
auto ctorInvokeExpr = _prepareCtorInvokeExpr(toType, fromInitializerListExpr->loc, fromInitializerListExpr->args);
if (outExpr && ctorInvokeExpr)
{
*outExpr = ctorInvokeExpr;
return true;
}
}
}
}
return false;
}

bool SemanticsVisitor::_invokeExprForSynthesizedCtor(
Type* toType,
StructDecl* structDecl,
InitializerListExpr* fromInitializerListExpr,
UInt &ioArgIndex,
Expr** outExpr)
{
auto synthesizedConstructor = _getSynthesizedConstructor(structDecl);
SLANG_ASSERT(synthesizedConstructor);

List<Expr*> coercedArgs;

for(auto param : getParameters(getASTBuilder(), synthesizedConstructor))
{
Expr* coercedArg = nullptr;
bool argResult = _readValueFromInitializerList(
getType(m_astBuilder, param),
&coercedArg,
fromInitializerListExpr,
ioArgIndex);

// No point in trying further if any argument fails
if(!argResult)
return false;

if( coercedArg )
{
coercedArgs.add(coercedArg);
}
}

auto ctorInvokeExpr = _prepareCtorInvokeExpr(toType, fromInitializerListExpr->loc, coercedArgs);
if (ctorInvokeExpr)
{
// The reason we need to check the coercion again is that the ResolveInvoke() could still find us
// the wrong constructor when there is inheritance. e.g.:
// struct A { int a; };// __init(int a) will be synthesized.
// struct B : A { int b; };// __init(int a, int b) will be synthesized.
// B b = {1}; // This should report an error, but the ResolveInvoke() will find the A.__init(int a).
//
// This will not happen when both A and B have explicit constructors, because 'coerce()' will be called after 'ResolveInvoke()'.
// However, in this initialize list to synthesized constructor translation path, 'coerce()' will not be called.
// But that is the only case, therefore, we will do a quick check here.
//
// TODO: should we improve the ResolveInvoke() to handle this case? Because A.__init(int a) should not be found at first place,
// Base class cannot construct a Derived class.
if (!canCoerce(toType, ctorInvokeExpr->type, ctorInvokeExpr, nullptr))
{
// TODO:
// 1. Create a more explainable error message.
// 2. We should not diagnose here, because we will need a fall back mechanism.
getSink()->diagnose(fromInitializerListExpr->loc, Diagnostics::typeMismatch, toType, ctorInvokeExpr->type);
return false;
}
else
{
if (outExpr)
*outExpr = ctorInvokeExpr;
return true;
}
}
return false;
}

bool SemanticsVisitor::_readAggregateValueFromInitializerList(
Type* inToType,
Expr** outToExpr,
InitializerListExpr* fromInitializerListExpr,
UInt &ioArgIndex)
UInt &ioArgIndex,
bool useLegacyMode)
{
auto toType = inToType;
UInt argCount = fromInitializerListExpr->args.getCount();
Expand Down Expand Up @@ -426,50 +565,62 @@ namespace Slang
auto toTypeDeclRef = toDeclRefType->getDeclRef();
if(auto toStructDeclRef = toTypeDeclRef.as<StructDecl>())
{
// Trying to initialize a `struct` type given an initializer list.
//
// Before we iterate over the fields, we want to check if this struct
// inherits from another `struct` type. If so, we want to read
// an initializer for that base type first.
//
if (auto baseStructType = findBaseStructType(m_astBuilder, toStructDeclRef))
// TODO: create a function '_createCtorInvokeExpr()'
// We must have a synthesized constructor here there are only two cases that we don't synthesized constructor:
// 1. It is a stdlib struct.
// 2. It is a struct that has user defined constructor. (In this case, we should have already handled it in _coerceInitializerList)
if (!isFromStdLib(toStructDeclRef.getDecl()) && !useLegacyMode)
{
Expr* coercedArg = nullptr;
bool argResult = _readValueFromInitializerList(
baseStructType,
outToExpr ? &coercedArg : nullptr,
fromInitializerListExpr,
ioArgIndex);

// No point in trying further if any argument fails
if (!argResult)
return false;

if (coercedArg)
{
coercedArgs.add(coercedArg);
}
return _invokeExprForSynthesizedCtor(inToType, toStructDeclRef.getDecl(), fromInitializerListExpr, ioArgIndex, outToExpr);
}

// We will go through the fields in order and try to match them
// up with initializer arguments.
//
for(auto fieldDeclRef : getMembersOfType<VarDecl>(m_astBuilder, toStructDeclRef, MemberFilterStyle::Instance))
else
{
Expr* coercedArg = nullptr;
bool argResult = _readValueFromInitializerList(
getType(m_astBuilder, fieldDeclRef),
outToExpr ? &coercedArg : nullptr,
fromInitializerListExpr,
ioArgIndex);

// No point in trying further if any argument fails
if(!argResult)
return false;
// Trying to initialize a `struct` type given an initializer list.
//
// Before we iterate over the fields, we want to check if this struct
// inherits from another `struct` type. If so, we want to read
// an initializer for that base type first.
//
if (auto baseStructType = findBaseStructType(m_astBuilder, toStructDeclRef))
{
Expr* coercedArg = nullptr;
bool argResult = _readValueFromInitializerList(
baseStructType,
outToExpr ? &coercedArg : nullptr,
fromInitializerListExpr,
ioArgIndex);

// No point in trying further if any argument fails
if (!argResult)
return false;

if (coercedArg)
{
coercedArgs.add(coercedArg);
}
}

if( coercedArg )
// We will go through the fields in order and try to match them
// up with initializer arguments.
//
for(auto fieldDeclRef : getMembersOfType<VarDecl>(m_astBuilder, toStructDeclRef, MemberFilterStyle::Instance))
{
coercedArgs.add(coercedArg);
Expr* coercedArg = nullptr;
bool argResult = _readValueFromInitializerList(
getType(m_astBuilder, fieldDeclRef),
outToExpr ? &coercedArg : nullptr,
fromInitializerListExpr,
ioArgIndex,
true);

// No point in trying further if any argument fails
if(!argResult)
return false;

if( coercedArg )
{
coercedArgs.add(coercedArg);
}
}
}
}
Expand Down Expand Up @@ -547,8 +698,19 @@ namespace Slang
!canCoerce(toType, fromInitializerListExpr->type, nullptr))
return _failedCoercion(toType, outToExpr, fromInitializerListExpr);

// try to invoke the user-defined constructor if it exists
if(_invokeExprForExplicitCtor(toType, fromInitializerListExpr, outToExpr))
{
return true;
}

// We will fall back to the legacy logic of initialize list.
if(!_readAggregateValueFromInitializerList(toType, outToExpr, fromInitializerListExpr, argIndex))
return false;
{
bool useLegacyMode = true;
if(!_readAggregateValueFromInitializerList(toType, outToExpr, fromInitializerListExpr, argIndex, useLegacyMode))
return false;
}

if(argIndex != argCount)
{
Expand Down
Loading

0 comments on commit c759f4d

Please sign in to comment.