Skip to content

Commit

Permalink
Move more logic to helper functions in QualType (#770)
Browse files Browse the repository at this point in the history
  • Loading branch information
marcauberer authored Feb 9, 2025
1 parent 50e318b commit 3aa0a3d
Show file tree
Hide file tree
Showing 18 changed files with 161 additions and 122 deletions.
2 changes: 1 addition & 1 deletion .run/spicetest.run.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="spicetest" type="CMakeGoogleTestRunConfigurationType" factoryName="Google Test" PROGRAM_PARAMS="--update-refs=true" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="Spice" TARGET_NAME="spicetest" CONFIG_NAME="Debug" RUN_TARGET_PROJECT_NAME="Spice" RUN_TARGET_NAME="spicetest" TEST_MODE="SUITE_TEST">
<configuration default="false" name="spicetest" type="CMakeGoogleTestRunConfigurationType" factoryName="Google Test" PROGRAM_PARAMS="--update-refs=false" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="Spice" TARGET_NAME="spicetest" CONFIG_NAME="Debug" RUN_TARGET_PROJECT_NAME="Spice" RUN_TARGET_NAME="spicetest" TEST_MODE="SUITE_TEST">
<envs>
<env name="LLVM_ADDITIONAL_FLAGS" value="-lole32 -lws2_32" />
<env name="LLVM_BUILD_INCLUDE_DIR" value="$PROJECT_DIR$/../llvm-project-latest/build/include" />
Expand Down
28 changes: 22 additions & 6 deletions media/test-project/test.spice
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
import "test2" as s1;
import "std/data/red-black-tree";
import "std/data/pair";

f<int> main() {
dyn v = s1::Vector<int>{};
v.setData(12);
printf("Data: %d\n", v.data);
v.setData(1.5);
printf("Data: %d\n", v.data);
RedBlackTree<int, int> tree;
{
foreach Pair<int&, int&> item : tree {
int& first = item.getFirst();
int& second = item.getSecond();
printf("%d: %d\n", first, second);
}
}

tree.insert(1, 2);
tree.insert(2, 3);
tree.insert(3, 4);
tree.insert(4, 5);
tree.insert(5, 6);
foreach Pair<int&, int&> item : tree {
int& first = item.getFirst();
int& second = item.getSecond();
printf("%d: %d\n", first, second);
}
}


/*import "bootstrap/util/block-allocator";
import "bootstrap/util/memory";

Expand Down
7 changes: 3 additions & 4 deletions src/irgenerator/GenControlStructures.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,9 @@ std::any IRGenerator::visitForeachLoop(const ForeachLoopNode *node) {
LLVMExprResult callResult = {.value = iterator, .node = iteratorAssignNode};
iteratorPtr = resolveAddress(callResult);

// Attach address to anonymous symbol to keep track of de-allocation
SymbolTableEntry *returnSymbol = currentScope->symbolTable.lookupAnonymous(iteratorAssignNode->codeLoc);
assert(returnSymbol != nullptr);
returnSymbol->updateAddress(iteratorPtr);
// If an anonymous symbol exists, set its address
if (SymbolTableEntry *returnSymbol = currentScope->symbolTable.lookupAnonymous(iteratorAssignNode->codeLoc))
returnSymbol->updateAddress(iteratorPtr);
} else { // The iteratorAssignExpr is of type Iterator
iteratorPtr = resolveAddress(iteratorAssignNode);
}
Expand Down
89 changes: 81 additions & 8 deletions src/symboltablebuilder/QualType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,30 +87,69 @@ const QualTypeList &QualType::getTemplateTypes() const { return type->getTemplat
* Get the struct instance for a struct type
*
* @param node Accessing AST node
* @param templateTypes Custom set of template types
* @return Struct instance
*/
Struct *QualType::getStruct(const ASTNode *node) const {
Struct *QualType::getStruct(const ASTNode *node, const QualTypeList &templateTypes) const {
assert(is(TY_STRUCT));
Scope *structDefScope = getBodyScope()->parent;
const std::string &structName = getSubType();
const QualTypeList &templateTypes = getTemplateTypes();
return StructManager::match(structDefScope, structName, templateTypes, node);
}

/**
* Get the struct instance for a struct type
*
* @param node Accessing AST node
* @return Struct instance
*/
Struct *QualType::getStruct(const ASTNode *node) const { return getStruct(node, type->getTemplateTypes()); }

/**
* Get the struct instance for a struct type
* Adopt information from the struct to this type.
*
* @param node Accessing AST node
* @param templateTypes Custom set of template types
* @return Struct instance
*/
Struct *QualType::getStructAndAdjustType(const ASTNode *node, const QualTypeList &templateTypes) {
Struct *spiceStruct = getStruct(node, templateTypes);
type = type->getWithBodyScope(spiceStruct->scope)->getWithTemplateTypes(spiceStruct->getTemplateTypes());
return spiceStruct;
}

/**
* Get the struct instance for a struct type
* Adopt information from the struct to this type.
*
* @param node Accessing AST node
* @return Struct instance
*/
Struct *QualType::getStructAndAdjustType(const ASTNode *node) { return getStructAndAdjustType(node, type->getTemplateTypes()); }

/**
* Get the interface instance for an interface type
*
* @param node Accessing AST node
* @param templateTypes Custom set of template types
* @return Interface instance
*/
Interface *QualType::getInterface(const ASTNode *node) const {
Interface *QualType::getInterface(const ASTNode *node, const QualTypeList &templateTypes) const {
assert(is(TY_INTERFACE));
Scope *interfaceDefScope = getBodyScope()->parent;
const std::string structName = getSubType();
const QualTypeList &templateTypes = getTemplateTypes();
return InterfaceManager::match(interfaceDefScope, structName, templateTypes, node);
}

/**
* Get the interface instance for an interface type
*
* @param node Accessing AST node
* @return Interface instance
*/
Interface *QualType::getInterface(const ASTNode *node) const { return getInterface(node, type->getTemplateTypes()); }

/**
* Check if the underlying type is of a certain super type
*
Expand Down Expand Up @@ -273,13 +312,15 @@ bool QualType::isErrorObj() const {
bool QualType::hasAnyGenericParts() const { return type->hasAnyGenericParts(); }

/**
* Check if copying an instance of the current type would require calling other copy ctors.
* Check if copying an instance of the current type would require a call to the copy ctor.
* If this function return true, the type can be copied by calling memcpy.
*
* @param node Accessing ASTNode
* @return Trivially copyable or not
*/
bool QualType::isTriviallyCopyable(const ASTNode *node) const { // NOLINT(*-no-recursion)
assert(!hasAnyGenericParts());

// Heap-allocated values may not be copied via memcpy
if (qualifiers.isHeap)
return false;
Expand All @@ -296,8 +337,6 @@ bool QualType::isTriviallyCopyable(const ASTNode *node) const { // NOLINT(*-no-r
if (is(TY_STRUCT)) {
// If the struct has a copy ctor, it is a non-trivially copyable one
const Struct *spiceStruct = getStruct(node);

// If the struct itself has a copy ctor, it is not trivially copyable
const std::vector args = {Arg(toConstRef(node), false)};
const Function *copyCtor = FunctionManager::lookup(spiceStruct->scope, CTOR_FUNCTION_NAME, *this, args, true);
if (copyCtor != nullptr)
Expand All @@ -311,6 +350,41 @@ bool QualType::isTriviallyCopyable(const ASTNode *node) const { // NOLINT(*-no-r
return true;
}

/**
* Check if destructing an instance of the current type would require calling other dtors.
* If this function return true, the type does not need to be destructed
*
* @param node Accessing ASTNode
* @return Trivially destructible or not
*/
bool QualType::isTriviallyDestructible(const ASTNode *node) const {
assert(!hasAnyGenericParts());

// Heap-allocated values require manual de-allocation, which is done in the default/explicit dtor
if (qualifiers.isHeap)
return false;

// In case of an array, the item type is determining the destructing triviality
if (isArray())
return getBase().isTriviallyDestructible(node);

// In case of a struct, the member types determine the destructing triviality
if (is(TY_STRUCT)) {
// If the struct has a dtor, it is a non-trivially destructible one
const Struct *spiceStruct = getStruct(node);
const Function *dtor = FunctionManager::lookup(spiceStruct->scope, DTOR_FUNCTION_NAME, *this, {}, true);
if (dtor != nullptr)
return false;

// Check if all member types are trivially destructible
const auto pred = [&](const QualType &fieldType) { return fieldType.isTriviallyDestructible(node); }; // NOLINT(*-no-recursion)
return std::ranges::all_of(spiceStruct->fieldTypes, pred);
}

return true;
}


/**
* Check if the current type implements the given interface type
*
Expand Down Expand Up @@ -635,7 +709,6 @@ QualType QualType::autoDeReference() const {
return newType;
}


/**
* Replace the base type with another one
*
Expand Down
7 changes: 6 additions & 1 deletion src/symboltablebuilder/QualType.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,11 @@ class QualType {
[[nodiscard]] const QualTypeList &getFunctionParamAndReturnTypes() const;
[[nodiscard]] bool hasLambdaCaptures() const;
[[nodiscard]] const QualTypeList &getTemplateTypes() const;
Struct *getStruct(const ASTNode *node) const;
[[nodiscard]] Struct *getStruct(const ASTNode *node, const QualTypeList &templateTypes) const;
[[nodiscard]] Struct *getStruct(const ASTNode *node) const;
[[nodiscard]] Struct *getStructAndAdjustType(const ASTNode *node, const QualTypeList &templateTypes);
[[nodiscard]] Struct *getStructAndAdjustType(const ASTNode *node);
[[nodiscard]] Interface *getInterface(const ASTNode *node, const QualTypeList &templateTypes) const;
[[nodiscard]] Interface *getInterface(const ASTNode *node) const;

// Queries on the type
Expand All @@ -82,6 +86,7 @@ class QualType {

// Complex queries on the type
[[nodiscard]] bool isTriviallyCopyable(const ASTNode *node) const;
[[nodiscard]] bool isTriviallyDestructible(const ASTNode *node) const;
[[nodiscard]] bool doesImplement(const QualType &implementedInterfaceType, const ASTNode *node) const;
[[nodiscard]] bool canBind(const QualType &inputType, bool isTemporary) const;
[[nodiscard]] bool matches(const QualType &otherType, bool ignoreArraySize, bool ignoreQualifiers, bool allowConstify) const;
Expand Down
5 changes: 2 additions & 3 deletions src/typechecker/FunctionManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -353,13 +353,12 @@ MatchResult FunctionManager::matchManifestation(Function &candidate, Scope *&mat
// Substantiate return type
substantiateReturnType(candidate, typeMapping, callNode);

// Set the match scope to the scope of the concrete substantiation
const QualType &thisType = candidate.thisType;
if (!thisType.is(TY_DYN)) {
// If we only have the generic struct scope, lookup the concrete manifestation scope
if (matchScope->isGenericScope) {
const std::string &structName = thisType.getSubType();
Scope *scope = thisType.getBodyScope()->parent;
Struct *spiceStruct = StructManager::match(scope, structName, thisType.getTemplateTypes(), candidate.declNode);
const Struct *spiceStruct = thisType.getStruct(candidate.declNode);
assert(spiceStruct != nullptr);
matchScope = spiceStruct->scope;
}
Expand Down
2 changes: 1 addition & 1 deletion src/typechecker/InterfaceManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ bool InterfaceManager::matchTemplateTypes(Interface &candidate, const QualTypeLi
* @param typeMapping Generic type mapping
* @param node Instantiation AST node for printing error messages
*/
void InterfaceManager::substantiateSignatures(Interface &candidate, TypeMapping &typeMapping, const ASTNode *node) {
void InterfaceManager::substantiateSignatures(Interface &candidate, const TypeMapping &typeMapping, const ASTNode *node) {
// Loop over all signatures and substantiate the generic ones
for (Function *method : candidate.methods) {
// Skip methods, that are already fully substantiated
Expand Down
2 changes: 1 addition & 1 deletion src/typechecker/InterfaceManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class InterfaceManager {
[[nodiscard]] static bool matchName(const Interface &candidate, const std::string &reqName);
[[nodiscard]] static bool matchTemplateTypes(Interface &candidate, const QualTypeList &reqTemplateTypes,
TypeMapping &typeMapping, const ASTNode *node);
static void substantiateSignatures(Interface &candidate, TypeMapping &typeMapping, const ASTNode *node);
static void substantiateSignatures(Interface &candidate, const TypeMapping &typeMapping, const ASTNode *node);
[[nodiscard]] static const GenericType *getGenericTypeOfCandidateByName(const Interface &candidate,
const std::string &templateTypeName);
[[nodiscard]] static uint64_t getCacheKey(Scope *scope, const std::string &name, const QualTypeList &templateTypes);
Expand Down
9 changes: 5 additions & 4 deletions src/typechecker/OpRuleManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -726,13 +726,14 @@ ExprResult OpRuleManager::isOperatorOverloadingFctAvailable(ASTNode *node, const
// Procedures always have the return type 'bool'
if (callee->isProcedure())
return ExprResult(QualType(TY_BOOL));
const QualType &returnType = callee->returnType;

// Add anonymous symbol to keep track of de-allocation
// Add anonymous symbol to keep track of dtor call, if non-trivially destructible
SymbolTableEntry *anonymousSymbol = nullptr;
if (callee->returnType.is(TY_STRUCT))
anonymousSymbol = typeChecker->currentScope->symbolTable.insertAnonymous(callee->returnType, node, opIdx);
if (returnType.is(TY_STRUCT) && !returnType.isTriviallyDestructible(node))
anonymousSymbol = typeChecker->currentScope->symbolTable.insertAnonymous(returnType, node, opIdx);

return {typeChecker->mapImportedScopeTypeToLocalType(calleeParentScope, callee->returnType), anonymousSymbol};
return {typeChecker->mapImportedScopeTypeToLocalType(calleeParentScope, returnType), anonymousSymbol};
}

QualType OpRuleManager::validateUnaryOperation(const ASTNode *node, const UnaryOpRule opRules[], size_t opRulesSize,
Expand Down
6 changes: 2 additions & 4 deletions src/typechecker/StructManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ Struct *StructManager::match(Scope *matchScope, const std::string &qt, const Qua

// Instantiate structs
if (baseType.is(TY_STRUCT))
baseType.getStruct(node);
(void)baseType.getStruct(node);
}

// Instantiate implemented interfaces if required
Expand All @@ -170,10 +170,8 @@ Struct *StructManager::match(Scope *matchScope, const std::string &qt, const Qua
TypeMatcher::substantiateTypesWithTypeMapping(templateTypes, typeMapping, node);

// Instantiate interface
Scope *interfaceMatchScope = interfaceType.getBodyScope()->parent;
Interface *spiceInterface = InterfaceManager::match(interfaceMatchScope, interfaceType.getSubType(), templateTypes, node);
Interface *spiceInterface = interfaceType.getInterface(node, templateTypes);
assert(spiceInterface != nullptr);

interfaceType = spiceInterface->entry->getQualType();
}

Expand Down
2 changes: 1 addition & 1 deletion src/typechecker/TypeCheckMeta.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ std::any TypeChecker::visitCustomDataType(CustomDataTypeNode *node) {
// Here, it is allowed to accept, that the struct/interface cannot be found, because there are self-referencing ones
if (entryType.is(TY_STRUCT)) {
const std::string structName = node->typeNameFragments.back();
if (Struct *spiceStruct = StructManager::match(localAccessScope, structName, templateTypes, node))
if (const Struct *spiceStruct = StructManager::match(localAccessScope, structName, templateTypes, node))
entryType = entryType.getWithBodyScope(spiceStruct->scope);
} else {
assert(entryType.is(TY_INTERFACE));
Expand Down
2 changes: 1 addition & 1 deletion src/typechecker/TypeChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ QualType TypeChecker::mapImportedScopeTypeToLocalType(const Scope *sourceScope,
if (!symbolType.isBase(TY_STRUCT))
return symbolType;

// If the source scope is in the current source file, we can return the symbol type as is
// If the given source file is in the current one, we can return the symbol type as is
const SourceFile *sourceSourceFile = sourceScope->sourceFile;
if (sourceSourceFile == sourceFile)
return symbolType;
Expand Down
5 changes: 3 additions & 2 deletions src/typechecker/TypeCheckerControlStructures.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,9 @@ std::any TypeChecker::visitForeachLoop(ForeachLoopNode *node) {
throw SemanticError(iteratorNode, INVALID_ITERATOR, "No getIterator() function found for the given iterable type");

iteratorType = QualType(node->getIteratorFct->returnType);
// Create anonymous entry for the iterator
currentScope->symbolTable.insertAnonymous(iteratorType, iteratorNode);
// Add anonymous symbol to keep track of dtor call, if non-trivially destructible
if (!iteratorType.isTriviallyDestructible(iteratorNode))
currentScope->symbolTable.insertAnonymous(iteratorType, iteratorNode);
}

// Change to foreach body scope
Expand Down
3 changes: 1 addition & 2 deletions src/typechecker/TypeCheckerExpressions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -552,8 +552,7 @@ std::any TypeChecker::visitPostfixUnaryExpr(PostfixUnaryExprNode *node) {

// If we only have the generic struct scope, lookup the concrete manifestation scope
if (structScope->isGenericScope) {
Scope *matchScope = structScope->parent;
Struct *spiceStruct = StructManager::match(matchScope, structName, lhsBaseTy.getTemplateTypes(), node);
const Struct *spiceStruct = lhsBaseTy.getStruct(node);
assert(spiceStruct != nullptr);
structScope = spiceStruct->scope;
}
Expand Down
5 changes: 1 addition & 4 deletions src/typechecker/TypeCheckerTopLevelDefinitionsCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,7 @@ std::any TypeChecker::visitStructDefCheck(StructDefNode *node) {
// Check if the struct implements all methods of all attached interfaces
size_t vtableIndex = 0;
for (const QualType &interfaceType : manifestation->interfaceTypes) {
// Retrieve interface instance
const std::string interfaceName = interfaceType.getSubType();
Scope *matchScope = interfaceType.getBodyScope()->parent;
const Interface *interface = InterfaceManager::match(matchScope, interfaceName, interfaceType.getTemplateTypes(), node);
const Interface *interface = interfaceType.getInterface(node);
assert(interface != nullptr);

// Check for all methods, that it is implemented by the struct
Expand Down
Loading

0 comments on commit 3aa0a3d

Please sign in to comment.