From a5b956bc8fe1e088909eaa3644c7e6bc61313a8a Mon Sep 17 00:00:00 2001 From: Marc Auberer Date: Sun, 19 Nov 2023 17:32:23 +0100 Subject: [PATCH] Fix bug with missing 'this.' prefix --- .run/spice.run.xml | 2 +- media/test-project/test.spice | 7 +-- media/test-project/test2.spice | 19 ++++--- src-bootstrap/reader/code-loc.spice | 6 +-- src-bootstrap/reader/reader.spice | 20 ++++---- src/global/GlobalResourceManager.cpp | 5 ++ src/global/GlobalResourceManager.h | 1 + src/typechecker/TypeChecker.cpp | 13 ++++- src/typechecker/TypeCheckerImplicit.cpp | 3 +- std/io/file_linux.spice | 42 ++++++++++++---- std/io/file_windows.spice | 49 ++++++++++++++----- std/runtime/memory_rt.spice | 4 ++ std/runtime/string_rt.spice | 41 ++++++++++------ std/type/result.spice | 14 ++++++ .../exception.out | 8 +++ .../source.spice | 18 +++++++ 16 files changed, 190 insertions(+), 62 deletions(-) create mode 100644 test/test-files/typechecker/methods/error-method-on-field-without-this/exception.out create mode 100644 test/test-files/typechecker/methods/error-method-on-field-without-this/source.spice diff --git a/.run/spice.run.xml b/.run/spice.run.xml index d59378dae..a1d0aef50 100644 --- a/.run/spice.run.xml +++ b/.run/spice.run.xml @@ -1,5 +1,5 @@ - + diff --git a/media/test-project/test.spice b/media/test-project/test.spice index 4a29f2c23..50c34f9b7 100644 --- a/media/test-project/test.spice +++ b/media/test-project/test.spice @@ -1,8 +1,5 @@ -//import "../../src-bootstrap/reader/reader"; -import "std/type/int"; +import "../../src-bootstrap/reader/reader"; f main() { - //Reader reader = Reader("./test.spice"); - String str = toString(123); - printf("%s", str); + Reader reader = Reader("./test.spice"); } \ No newline at end of file diff --git a/media/test-project/test2.spice b/media/test-project/test2.spice index 7d1cd3a0d..253aa716e 100644 --- a/media/test-project/test2.spice +++ b/media/test-project/test2.spice @@ -1,9 +1,16 @@ -public type Driveable interface { - public p drive(int); - public f isDriving(); +import "std/type/result"; +import "std/type/error"; + +type FilePtr alias byte*; +public type File struct { + FilePtr* filePtr } -#[test] -f testDriveable() { - return true; +// Link external functions +ext f fopen(string, string); + +public f> openFile(string path, string mode) { + FilePtr* fp = fopen(path, mode); + File file = File{fp}; + return fp != nil ? ok(file) : err(file, Error("Failed to open file")); } \ No newline at end of file diff --git a/src-bootstrap/reader/code-loc.spice b/src-bootstrap/reader/code-loc.spice index aa895abeb..0645e470f 100644 --- a/src-bootstrap/reader/code-loc.spice +++ b/src-bootstrap/reader/code-loc.spice @@ -29,8 +29,8 @@ public f CodeLoc.toString() { */ public f CodeLoc.toPrettyString() { String codeLocStr = String(toString(this.line)); - if this.sourceFilePath.empty() { - return toString(line) + ":" + toString(this.col); + if len(this.sourceFilePath) == 0 { + return toString(this.line) + ":" + toString(this.col); } return this.sourceFilePath + ":" + toString(this.line) + ":" + toString(this.col); } @@ -52,5 +52,5 @@ public f CodeLoc.toPrettyLineAndColumn() { } public f operator==(const CodeLoc &lhs, const CodeLoc &rhs) { - return /*lhs.sourceFilePath == rhs.sourceFilePath && */lhs.line == rhs.line && lhs.col == rhs.col; + return lhs.sourceFilePath == rhs.sourceFilePath && lhs.line == rhs.line && lhs.col == rhs.col; } \ No newline at end of file diff --git a/src-bootstrap/reader/reader.spice b/src-bootstrap/reader/reader.spice index deb95aedf..db7c40a67 100644 --- a/src-bootstrap/reader/reader.spice +++ b/src-bootstrap/reader/reader.spice @@ -1,14 +1,16 @@ -// Imports +// Std imports import "std/io/filepath"; import "std/io/file"; import "std/type/result"; +import "std/type/error"; + +// Own import import "../reader/code-loc"; public type Reader struct { File file string filename char curChar = '\0' - bool moreToRead = true unsigned long line = 1l unsigned long col = 0l } @@ -16,14 +18,14 @@ public type Reader struct { public p Reader.ctor(const string inputFileName) { this.filename = inputFileName; Result result = openFile(inputFileName, MODE_READ); - this.file = result.unwrap(); - if !file.isOpen() { - + if !result.isOk() { + panic(Error("Source file cannot be opened")); } + this.file = result.unwrap(); } public p Reader.dtor() { - file.close(); + this.file.close(); } /** @@ -49,9 +51,7 @@ public f Reader.getCodeLoc() { */ public p Reader.advance() { assert !this.isEOF(); - if !this.file.get(this.curChar) { - this.moreToRead = false; - } + this.curChar = (char) this.file.readChar(); if this.curChar == '\n' { this.line++; this.col = 0l; @@ -76,5 +76,5 @@ public p Reader.expect(char c) { * @return At the end or not */ public f Reader.isEOF() { - return !this.moreToRead; + return this.file.isEOF(); } \ No newline at end of file diff --git a/src/global/GlobalResourceManager.cpp b/src/global/GlobalResourceManager.cpp index 3a9a29c65..1e3561247 100644 --- a/src/global/GlobalResourceManager.cpp +++ b/src/global/GlobalResourceManager.cpp @@ -57,6 +57,11 @@ GlobalResourceManager::GlobalResourceManager(const CliOptions &cliOptions) ltoModule = std::make_unique(LTO_FILE_NAME, context); } +GlobalResourceManager::~GlobalResourceManager() { + // Cleanup all global LLVM resources + llvm::llvm_shutdown(); +} + SourceFile *GlobalResourceManager::createSourceFile(SourceFile *parent, const std::string &dependencyName, const std::filesystem::path &path, bool isStdFile) { // Check if the source file was already added (e.g. by another source file that imports it) diff --git a/src/global/GlobalResourceManager.h b/src/global/GlobalResourceManager.h index c4ba33c12..375c2f1f8 100644 --- a/src/global/GlobalResourceManager.h +++ b/src/global/GlobalResourceManager.h @@ -36,6 +36,7 @@ class GlobalResourceManager { // Constructors explicit GlobalResourceManager(const CliOptions &cliOptions); GlobalResourceManager(const GlobalResourceManager &) = delete; + ~GlobalResourceManager(); // Public methods SourceFile *createSourceFile(SourceFile *parent, const std::string &dependencyName, const std::filesystem::path &path, diff --git a/src/typechecker/TypeChecker.cpp b/src/typechecker/TypeChecker.cpp index 918276d58..035a1a931 100644 --- a/src/typechecker/TypeChecker.cpp +++ b/src/typechecker/TypeChecker.cpp @@ -1502,8 +1502,14 @@ std::any TypeChecker::visitFctCall(FctCallNode *node) { } // Retrieve entry of the first fragment - SymbolTableEntry *firstFragEntry = currentScope->lookup(node->functionNameFragments.front()); + const std::string &firstFrag = node->functionNameFragments.front(); + SymbolTableEntry *firstFragEntry = currentScope->lookup(firstFrag); if (firstFragEntry) { + // Check if we have seen a 'this.' prefix, because the generator needs that + if (firstFragEntry->scope->type == ScopeType::STRUCT && firstFrag != THIS_VARIABLE_NAME) + SOFT_ERROR_ER(node, REFERENCED_UNDEFINED_VARIABLE, + "The symbol '" + firstFrag + "' could not be found. Missing 'this.' prefix?") + firstFragEntry->used = true; // Decide of which type the function call is const SymbolType &baseType = firstFragEntry->getType().getBaseType(); @@ -2232,6 +2238,11 @@ std::any TypeChecker::visitCustomDataType(CustomDataTypeNode *node) { SymbolType symbolType = *genericType; if (typeMapping.contains(firstFragment)) symbolType = typeMapping.at(firstFragment); + + // Check if the replacement is a String type + if (!isImported && symbolType.isStringObj() && !sourceFile->isStringRT()) + sourceFile->requestRuntimeModule(STRING_RT); + return node->setEvaluatedSymbolType(symbolType, manIdx); } diff --git a/src/typechecker/TypeCheckerImplicit.cpp b/src/typechecker/TypeCheckerImplicit.cpp index a7ea99b27..8bb8501b5 100644 --- a/src/typechecker/TypeCheckerImplicit.cpp +++ b/src/typechecker/TypeCheckerImplicit.cpp @@ -203,7 +203,8 @@ void TypeChecker::createDefaultDtorIfRequired(const Struct &spiceStruct, Scope * createDefaultStructMethod(spiceStruct, DTOR_FUNCTION_NAME, {}); // Request memory runtime if we have fields, that are allocated on the heap - if (hasHeapFields) { + // The string runtime does not use it, but allocates manually to avoid circular dependencies + if (hasHeapFields && !sourceFile->isStringRT()) { SourceFile *memoryRT = sourceFile->requestRuntimeModule(MEMORY_RT); assert(memoryRT != nullptr); Scope *matchScope = memoryRT->globalScope.get(); diff --git a/std/io/file_linux.spice b/std/io/file_linux.spice index 725232fa1..f253c7fe0 100644 --- a/std/io/file_linux.spice +++ b/std/io/file_linux.spice @@ -116,14 +116,28 @@ public f File.getSize() { return size; } +/** + * Checks if the end of the file is reached. + * + * @return EOF reached / not reached + */ +public f File.isEOF() { + return this.readChar() == EOF; +} + /** * Reads a whole file from a given path. * * @return Content in form of a string */ -public f readFile(string path) { +public f> readFile(string path) { // Open the file in read mode - File file = openFile(path, MODE_READ); + Result fileResult = openFile(path, MODE_READ); + // Check for errors + if (fileResult.isErr()) { + return err(String(), fileResult.getErr()); + } + File file = fileResult.unwrap(); // Read from the file char by char int buffer; String output = String(); @@ -132,7 +146,7 @@ public f readFile(string path) { } // Close the file file.close(); - return output; + return ok(output); } /** @@ -140,14 +154,19 @@ public f readFile(string path) { * * @return Result code of the write operation: 0 = successful, -1 = failed */ -public f writeFile(string path, string content) { +public f> writeFile(string path, string content) { // Open the file in write mode - File file = openFile(path, MODE_WRITE); + Result fileResult = openFile(path, MODE_WRITE); + // Check for errors + if (fileResult.isErr()) { + return err(0, fileResult.getErr()); + } + File file = fileResult.unwrap(); // Write the string to the file int resultCode = file.writeString(content); // Close the file file.close(); - return resultCode; + return ok(resultCode); } /** @@ -155,14 +174,19 @@ public f writeFile(string path, string content) { * * @return File size in bytes */ -public f getFileSize(string path) { +public f> getFileSize(string path) { // Open the file in read mode - File file = openFile(path, MODE_READ); + Result fileResult = openFile(path, MODE_READ); + // Check for errors + if (fileResult.isErr()) { + return err(0l, fileResult.getErr()); + } + File file = fileResult.unwrap(); // Get the file size long size = file.getSize(); // Close the file file.close(); - return size; + return ok(size); } /** diff --git a/std/io/file_windows.spice b/std/io/file_windows.spice index 0f6be7830..481a17670 100644 --- a/std/io/file_windows.spice +++ b/std/io/file_windows.spice @@ -23,7 +23,7 @@ const int SEEK_SET = 0; const int SEEK_CUR = 1; const int SEEK_END = 2; -public type FilePtr alias byte*; +type FilePtr alias byte*; public type File struct { FilePtr* filePtr @@ -60,7 +60,8 @@ public f createFile(string path) { */ public f> openFile(string path, string mode) { FilePtr* fp = fopen(path, mode); - return fp != nil ? ok(fp) : err(fp, Error("Failed to open file '" + path + "'")); + File file = File{fp}; + return fp != nil ? ok(file) : err(file, Error("Failed to open file")); } /** @@ -116,14 +117,28 @@ public f File.getSize() { return size; } +/** + * Checks if the end of the file is reached. + * + * @return EOF reached / not reached + */ +public f File.isEOF() { + return this.readChar() == EOF; +} + /** * Reads a whole file from a given path. * * @return Content in form of a string */ -public f readFile(string path) { +public f> readFile(string path) { // Open the file in read mode - File file = openFile(path, MODE_READ); + Result fileResult = openFile(path, MODE_READ); + // Check for errors + if !fileResult.isOk() { + return err(String(), fileResult.getErr()); + } + File file = fileResult.unwrap(); // Read from the file char by char int buffer; String output = String(); @@ -132,7 +147,7 @@ public f readFile(string path) { } // Close the file file.close(); - return output; + return ok(output); } /** @@ -140,14 +155,19 @@ public f readFile(string path) { * * @return Result code of the write operation: 0 = successful, -1 = failed */ -public f writeFile(string path, string content) { +public f> writeFile(string path, string content) { // Open the file in write mode - File file = openFile(path, MODE_WRITE); + Result fileResult = openFile(path, MODE_WRITE); + // Check for errors + if !fileResult.isOk() { + return err(0, fileResult.getErr()); + } + File file = fileResult.unwrap(); // Write the string to the file int resultCode = file.writeString(content); // Close the file file.close(); - return resultCode; + return ok(resultCode); } /** @@ -155,14 +175,19 @@ public f writeFile(string path, string content) { * * @return File size in bytes */ -public f getFileSize(string path) { +public f> getFileSize(string path) { // Open the file in read mode - File file = openFile(path, MODE_READ); - // Get the file size + Result fileResult = openFile(path, MODE_READ); + // Check for errors + if !fileResult.isOk() { + return err(0l, fileResult.getErr()); + } + File file = fileResult.unwrap(); + // Get the file file long size = file.getSize(); // Close the file file.close(); - return size; + return ok(size); } /** diff --git a/std/runtime/memory_rt.spice b/std/runtime/memory_rt.spice index 25e491428..845155845 100644 --- a/std/runtime/memory_rt.spice +++ b/std/runtime/memory_rt.spice @@ -40,6 +40,9 @@ public f> sRealloc(heap byte* ptr, unsigned long size) { * @return A pointer to the copied block, or an error if the copy failed. */ public f> sCopy(heap byte* oldPtr, heap byte* newPtr, unsigned long size) { + if oldPtr == nil | newPtr == nil { + return err(nil, "Cannot copy from or to nil pointer!"); + } memcpy(newPtr, oldPtr, size); return ok(newPtr); } @@ -51,6 +54,7 @@ public f> sCopy(heap byte* oldPtr, heap byte* newPtr, unsigne * @param ptr The pointer to the block to free. */ public p sDealloc(heap byte*& ptr) { + if ptr == nil { return; } free(ptr); ptr = nil; // Zero out to prevent accidental double frees } \ No newline at end of file diff --git a/std/runtime/string_rt.spice b/std/runtime/string_rt.spice index cbc38a677..f2d6d67bc 100644 --- a/std/runtime/string_rt.spice +++ b/std/runtime/string_rt.spice @@ -1,6 +1,13 @@ #![core.compiler.alwaysKeepOnNameCollision = true] -import "std/type/result"; +import "std/type/error"; + +// Link external functions +// We intentionally do not use the memory_rt here to avoid dependency circles +ext f malloc(unsigned long); +ext f realloc(heap char*, unsigned long); +ext p free(heap char*); +ext p memcpy(heap char*, heap char*, unsigned long); // Constants const unsigned long INITIAL_ALLOC_COUNT = 5l; @@ -30,8 +37,8 @@ public p String.ctor(const string value = "") { // Allocate space for the initial number of elements unsafe { unsigned long requiredBytes = this.capacity + 1l; // +1 because of null terminator - Result allocResult = sAlloc(requiredBytes); - this.contents = (heap char*) allocResult.unwrap(); + this.contents = malloc(requiredBytes); + this.checkForOOM(); } // Save initial value @@ -49,8 +56,8 @@ public p String.ctor(const char value) { // Allocate space for the initial number of elements unsafe { unsigned long requiredBytes = this.capacity + 1l; // +1 because of null terminator - Result allocResult = sAlloc(requiredBytes); - this.contents = (heap char*) allocResult.unwrap(); + this.contents = malloc(requiredBytes); + this.checkForOOM(); } // Save initial value @@ -67,13 +74,13 @@ public p String.ctor(const String& value) { // Allocate space unsafe { unsigned long requiredBytes = this.capacity + 1l; // +1 because of null terminator - Result allocResult = sAlloc(requiredBytes); - this.contents = (heap char*) allocResult.unwrap(); + this.contents = malloc(requiredBytes); + this.checkForOOM(); } // Copy the contents from the other string unsafe { - sCopy((heap byte*) value.contents, (heap byte*) this.contents, value.length + 1l); // +1 because of null terminator + memcpy(this.contents, value.contents, value.length + 1l); // +1 because of null terminator } } @@ -84,8 +91,8 @@ public p String.ctor(IntLong initialSize) { // Allocate space for the initial number of elements unsafe { unsigned long requiredBytes = this.capacity + 1l; // +1 because of null terminator - Result allocResult = sAlloc(requiredBytes); - this.contents = (heap char*) allocResult.unwrap(); + this.contents = malloc(requiredBytes); + this.checkForOOM(); } // Save initial value @@ -460,11 +467,11 @@ public f String.replace( if needleLength != replacementLength { const heap char* oldSuffixAddr = startAddr + needleLength; const heap char* newSuffixAddr = startAddr + replacementLength; - sCopy((heap byte*) oldSuffixAddr, (heap byte*) newSuffixAddr, suffixLength + 1); // +1 because of null terminator + memcpy(newSuffixAddr, oldSuffixAddr, suffixLength + 1); // +1 because of null terminator } // Replace needle with replacement - sCopy((heap byte*) replacement, (heap byte*) startAddr, replacementLength); + memcpy(startAddr, (char*) replacement, replacementLength); } // Update length @@ -560,13 +567,19 @@ p String.resize(unsigned long newLength) { unsafe { heap char* oldAddress = this.contents; unsigned long requiredBytes = newLength + 1l; // +1 because of null terminator - Result allocResult = sRealloc((heap byte*) oldAddress, requiredBytes); - this.contents = (heap char*) allocResult.unwrap(); + this.contents = realloc(oldAddress, requiredBytes); + this.checkForOOM(); } // Set new capacity this.capacity = newLength; } +p String.checkForOOM() { + if this.contents == nil { + panic(Error("Could not allocate memory for dynamic string object")); + } +} + // ======================================================= Static functions ====================================================== /** diff --git a/std/type/result.spice b/std/type/result.spice index 23c8ac204..d9dd54778 100644 --- a/std/type/result.spice +++ b/std/type/result.spice @@ -30,6 +30,20 @@ public inline f Result.unwrap() { return this.data; } +/** + * Return the enclosed error object. + */ +public inline f Result.getErr() { + return this.error; +} + +/** + * Checks if the result contains any data. + */ +public inline f Result.isOk() { + return this.error.code == 0; +} + /** * Returns a result object with a value and no error. */ diff --git a/test/test-files/typechecker/methods/error-method-on-field-without-this/exception.out b/test/test-files/typechecker/methods/error-method-on-field-without-this/exception.out new file mode 100644 index 000000000..429508bcc --- /dev/null +++ b/test/test-files/typechecker/methods/error-method-on-field-without-this/exception.out @@ -0,0 +1,8 @@ +[Error|Compiler]: +Unresolved soft errors: There are unresolved errors. Please fix them and recompile. + +[Error|Semantic] ./test-files/typechecker/methods/error-method-on-field-without-this/source.spice:12:5: +Referenced undefined variable: The symbol 'a' could not be found. Missing 'this.' prefix? + +12 a.foo(); + ^^^^^^^ \ No newline at end of file diff --git a/test/test-files/typechecker/methods/error-method-on-field-without-this/source.spice b/test/test-files/typechecker/methods/error-method-on-field-without-this/source.spice new file mode 100644 index 000000000..fe7fa3259 --- /dev/null +++ b/test/test-files/typechecker/methods/error-method-on-field-without-this/source.spice @@ -0,0 +1,18 @@ +type A struct {} + +p A.foo() { + printf("A.foo\n"); +} + +type B struct { + A a +} + +p B.callFooOnA() { + a.foo(); +} + +f main() { + B b; + b.callFooOnA(); +} \ No newline at end of file