diff --git a/README.md b/README.md index 47748069..184d0f37 100644 --- a/README.md +++ b/README.md @@ -95,11 +95,16 @@ do { ### Building the Flax compiler +#### Dependencies #### +- LLVM 7, mostly due to their obsession with changing the IR interface every damn version (6 does not work, 8 does not work) +- GMP/MPIR +- MPFR + #### macOS / Linux - Flax uses a makefile; most likely some form of GNU-compatible `make` will work. -- LLVM needs to be installed. On macOS, `brew install llvm@6` should work. (note: llvm 7 and up seems to have changed some JIT-related things) +- LLVM needs to be installed. On macOS, `brew install llvm@7` should work. (note: llvm 8 and up seems to have changed some JIT-related things) - For macOS people, simply call `make`. - Linux people, call `make linux`. - A *C++17*-compatible compiler should be used. diff --git a/build/supertiny.flx b/build/supertiny.flx index 623a1170..c7578c58 100644 --- a/build/supertiny.flx +++ b/build/supertiny.flx @@ -4,8 +4,7 @@ export supertiny -// import libc as _ -import libc +import libc as _ // import std::io // import std::map @@ -13,10 +12,21 @@ import std::opt @raw union ipv4 { + _: struct { + _: @raw union { + bytes2: [u8: 4] + raw3: u32 + } + } + _: struct { bytes: [u8: 4] } + _: struct { + raw2: u32 + } + raw: u32 } @@ -29,19 +39,12 @@ struct foo @entry fn main() { - libc::printf("hello world %d\n", 1) - // do { - // var addr: ipv4 - // // addr.raw = -16668480; - // addr.raw = 0xff01a8c0; - - // printf("%d.%d.%d.%d\n", addr._.bytes[0], addr._.bytes[1], addr._.bytes[2], addr._.bytes[3]); - - // // addr._.bytes[0] = 192 - // // addr._.bytes[1] = 168 - // // addr._.bytes[2] = 1 - // // addr._.bytes[3] = 1 - // } + do { + var addr: ipv4 + addr.raw3 = 0xff01a8c0; + + printf("%d.%d.%d.%d\n", addr.bytes[0], addr.bytes[1], addr.bytes[2], addr.bytes[3]); + } /* do { diff --git a/source/frontend/parser/type.cpp b/source/frontend/parser/type.cpp index ad4d0fce..780a8b76 100644 --- a/source/frontend/parser/type.cpp +++ b/source/frontend/parser/type.cpp @@ -129,7 +129,7 @@ namespace parser StructDefn* defn = util::pool(st.loc()); if(nameless) { - defn->name = strprintf("__anon_struct_%zu", anon_counter); + defn->name = strprintf("__anon_struct_%zu", anon_counter++); } else { @@ -174,13 +174,18 @@ namespace parser auto loc = st.loc(); std::string name = st.eat().str(); - if(auto it = std::find_if(defn->fields.begin(), defn->fields.end(), [&name](const auto& p) -> bool { - return std::get<0>(p) == name; - }); it != defn->fields.end()) + // we can't check for duplicates when it's transparent, duh + // we'll collapse and collect and check during typechecking. + if(name != "_") { - SimpleError::make(loc, "duplicate field '%s' in struct definition", name) - ->append(SimpleError::make(MsgType::Note, std::get<1>(*it), "field '%s' previously defined here:", name)) - ->postAndQuit(); + if(auto it = std::find_if(defn->fields.begin(), defn->fields.end(), [&name](const auto& p) -> bool { + return std::get<0>(p) == name; + }); it != defn->fields.end()) + { + SimpleError::make(loc, "duplicate field '%s' in struct definition", name) + ->append(SimpleError::make(MsgType::Note, std::get<1>(*it), "field '%s' previously defined here:", name)) + ->postAndQuit(); + } } if(st.eat() != TT::Colon) @@ -188,6 +193,9 @@ namespace parser pts::Type* type = parseType(st); defn->fields.push_back(std::make_tuple(name, loc, type)); + + if(st.front() == TT::Equal) + error(st.loc(), "struct fields cannot have initialisers"); } else if(st.front() == TT::Func) { @@ -235,7 +243,7 @@ namespace parser UnionDefn* defn = util::pool(st.loc()); if(nameless) { - defn->name = strprintf("__anon_union_%zu", anon_counter); + defn->name = strprintf("__anon_union_%zu", anon_counter++); } else { @@ -289,13 +297,7 @@ namespace parser pts::Type* type = 0; std::string name = st.eat().str(); - if(auto it = defn->cases.find(name); it != defn->cases.end()) - { - SimpleError::make(loc, "duplicate variant '%s' in union definition", name) - ->append(SimpleError::make(MsgType::Note, std::get<1>(it->second), "variant '%s' previously defined here:", name)) - ->postAndQuit(); - } - + // to improve code flow, handle the type first. if(st.front() == TT::Colon) { st.eat(); @@ -310,8 +312,26 @@ namespace parser error(st.loc(), "expected newline after union variant"); } - if(type == nullptr) type = pts::NamedType::create(loc, VOID_TYPE_STRING); - defn->cases[name] = { index, loc, type }; + if(name == "_") + { + if(!israw) + error(loc, "transparent fields can only be present in raw unions"); + + iceAssert(type); + defn->transparentFields.push_back({ loc, type }); + } + else + { + if(auto it = defn->cases.find(name); it != defn->cases.end()) + { + SimpleError::make(loc, "duplicate variant '%s' in union definition", name) + ->append(SimpleError::make(MsgType::Note, std::get<1>(it->second), "variant '%s' previously defined here:", name)) + ->postAndQuit(); + } + + if(type == nullptr) type = pts::NamedType::create(loc, VOID_TYPE_STRING); + defn->cases[name] = { index, loc, type }; + } // do some things if(st.front() == TT::NewLine || st.front() == TT::Semicolon) diff --git a/source/include/ast.h b/source/include/ast.h index b30635b4..4c897cbe 100644 --- a/source/include/ast.h +++ b/source/include/ast.h @@ -448,6 +448,8 @@ namespace ast bool israw = false; util::hash_map> cases; + + std::vector> transparentFields; }; struct TypeExpr : Expr diff --git a/source/include/sst.h b/source/include/sst.h index ca8717b9..fc0998b9 100644 --- a/source/include/sst.h +++ b/source/include/sst.h @@ -765,6 +765,7 @@ namespace sst virtual CGResult _codegen(cgn::CodegenState* cs, fir::Type* inferred = 0) override; util::hash_map fields; + std::vector transparentFields; }; diff --git a/source/typecheck/dotop.cpp b/source/typecheck/dotop.cpp index 36af0815..5664e4fd 100644 --- a/source/typecheck/dotop.cpp +++ b/source/typecheck/dotop.cpp @@ -62,24 +62,34 @@ static ErrorMsg* wrongDotOpError(ErrorMsg* e, sst::StructDefn* str, const Locati +struct search_result_t +{ + search_result_t() { } + search_result_t(fir::Type* t, size_t i, bool tr) : type(t), fieldIdx(i), isTransparent(tr) { } + fir::Type* type = 0; + size_t fieldIdx = 0; + bool isTransparent = false; +}; - -static sst::FieldDotOp* resolveFieldNameDotOp(sst::TypecheckState* fs, sst::TypeDefn* defn, sst::Expr* lhs, +static std::vector searchTransparentFields(sst::TypecheckState* fs, std::vector stack, const std::vector& fields, const Location& loc, const std::string& name) { - size_t idx = 0; + // search for them by name first, instead of doing a super-depth-first-search. for(auto df : fields) { if(df->id.name == name) { - auto ret = util::pool(loc, df->type); - ret->lhs = lhs; - ret->rhsIdent = name; - - return ret; + stack.push_back(search_result_t(df->type, 0, false)); + return stack; } - else if(df->isTransparentField) + } + + + size_t idx = 0; + for(auto df : fields) + { + if(df->isTransparentField) { auto ty = df->type; assert(ty->isRawUnionType() || ty->isStructType()); @@ -92,47 +102,85 @@ static sst::FieldDotOp* resolveFieldNameDotOp(sst::TypecheckState* fs, sst::Type flds = str->fields; else if(auto unn = dcast(sst::RawUnionDefn, defn); unn) - flds = util::map(util::pairs(unn->fields), [](const auto& x) -> auto { return x.second; }); + flds = util::map(util::pairs(unn->fields), [](const auto& x) -> auto { return x.second; }) + unn->transparentFields; else error(loc, "what kind of type is this? '%s'", ty); + stack.push_back(search_result_t(ty, idx, true)); + auto ret = searchTransparentFields(fs, stack, flds, loc, name); - // hmm. - auto hmm = sst::FieldDotOp(loc, df->type); + if(!ret.empty()) return ret; + else stack.pop_back(); + } - hmm.lhs = lhs; - hmm.rhsIdent = ""; - hmm.isTransparentField = true; - hmm.indexOfTransparentField = idx; + idx += 1; + } - auto hmm2 = resolveFieldNameDotOp(fs, defn, &hmm, flds, loc, name); - if(hmm2) - { - // make 'hmm' into a pool node. - iceAssert(hmm2->lhs == &hmm); + // if we've reached the end of the line, return nothing. + return { }; +} - auto real = util::pool(hmm.loc, hmm.type); - real->lhs = lhs; - real->rhsIdent = ""; - real->isTransparentField = true; - real->indexOfTransparentField = idx; - hmm2->lhs = real; +static sst::FieldDotOp* resolveFieldNameDotOp(sst::TypecheckState* fs, sst::Expr* lhs, const std::vector& fields, + const Location& loc, const std::string& name) +{ + for(auto df : fields) + { + if(df->id.name == name) + { + auto ret = util::pool(loc, df->type); + ret->lhs = lhs; + ret->rhsIdent = name; - return hmm2; - } + return ret; } + } - idx += 1; + // sad. search for the field, recursively, in transparent members. + auto ops = searchTransparentFields(fs, { }, fields, loc, name); + if(ops.empty()) + return nullptr; + + // ok, now we just need to make a link of fielddotops... + sst::Expr* cur = lhs; + for(const auto& x : ops) + { + auto op = util::pool(loc, x.type); + + op->lhs = cur; + op->isTransparentField = x.isTransparent; + op->indexOfTransparentField = x.fieldIdx; + + // don't set a name if we're transparent. + op->rhsIdent = (x.isTransparent ? "" : name); + + cur = op; } - return nullptr; + auto ret = dcast(sst::FieldDotOp, cur); + assert(ret); + + return ret; } + + + + + + + + + + + + + + static sst::Expr* doExpressionDotOp(sst::TypecheckState* fs, ast::DotOperator* dotop, fir::Type* infer) { auto lhs = dotop->left->typecheck(fs).expr(); @@ -532,7 +580,7 @@ static sst::Expr* doExpressionDotOp(sst::TypecheckState* fs, ast::DotOperator* d while(copy) { - auto hmm = resolveFieldNameDotOp(fs, copy, lhs, copy->fields, dotop->loc, name); + auto hmm = resolveFieldNameDotOp(fs, lhs, copy->fields, dotop->loc, name); if(hmm) return hmm; // ok, we didn't find it. @@ -622,8 +670,8 @@ static sst::Expr* doExpressionDotOp(sst::TypecheckState* fs, ast::DotOperator* d { if(auto fld = dcast(ast::Ident, dotop->right)) { - auto flds = util::map(util::pairs(rnn->fields), [](const auto& x) -> auto { return x.second; }); - auto hmm = resolveFieldNameDotOp(fs, rnn, lhs, flds, dotop->loc, fld->name); + auto flds = util::map(util::pairs(rnn->fields), [](const auto& x) -> auto { return x.second; }) + rnn->transparentFields; + auto hmm = resolveFieldNameDotOp(fs, lhs, flds, dotop->loc, fld->name); if(hmm) { return hmm; diff --git a/source/typecheck/structs.cpp b/source/typecheck/structs.cpp index bb3830cd..df462b8f 100644 --- a/source/typecheck/structs.cpp +++ b/source/typecheck/structs.cpp @@ -37,16 +37,78 @@ static void _checkFieldRecursion(sst::TypecheckState* fs, fir::Type* strty, fir: for(auto f : field->toStructType()->getElements()) _checkFieldRecursion(fs, field, f, floc, seeing); } + else if(field->isRawUnionType()) + { + for(auto f : field->toRawUnionType()->getVariants()) + _checkFieldRecursion(fs, field, f.second, floc, seeing); + } // ok, we should be fine...? } +// used in typecheck/unions.cpp and typecheck/classes.cpp void checkFieldRecursion(sst::TypecheckState* fs, fir::Type* strty, fir::Type* field, const Location& floc) { std::set seeing; _checkFieldRecursion(fs, strty, field, floc, seeing); } +static void _checkTransparentFieldRedefinition(sst::TypecheckState* fs, sst::TypeDefn* defn, const std::vector& fields, + util::hash_map& seen) +{ + for(auto fld : fields) + { + if(fld->isTransparentField) + { + auto ty = fld->type; + if(!ty->isRawUnionType() && !ty->isStructType()) + { + // you can't have a transparentl field if it's not an aggregate type, lmao + error(fld, "transparent fields must have either a struct or raw-union type."); + } + + auto defn = fs->typeDefnMap[ty]; + iceAssert(defn); + + std::vector flds; + if(auto str = dcast(sst::StructDefn, defn); str) + flds = str->fields; + + else if(auto unn = dcast(sst::RawUnionDefn, defn); unn) + flds = util::map(util::pairs(unn->fields), [](const auto& x) -> auto { return x.second; }) + unn->transparentFields; + + else + error(fs->loc(), "what kind of type is this? '%s'", ty); + + _checkTransparentFieldRedefinition(fs, defn, flds, seen); + } + else + { + if(auto it = seen.find(fld->id.name); it != seen.end()) + { + SimpleError::make(fld->loc, "redefinition of transparently accessible field '%s'", fld->id.name) + ->append(SimpleError::make(MsgType::Note, it->second, "previous definition was here:")) + ->postAndQuit(); + } + else + { + seen[fld->id.name] = fld->loc; + } + } + } +} + +void checkTransparentFieldRedefinition(sst::TypecheckState* fs, sst::TypeDefn* defn, const std::vector& fields) +{ + util::hash_map seen; + _checkTransparentFieldRedefinition(fs, defn, fields, seen); +} + + + + + + @@ -122,9 +184,6 @@ TCResult ast::StructDefn::typecheck(sst::TypecheckState* fs, fir::Type* infer, c std::vector> tys; - //* this is a slight misnomer, since we only 'enter' the struct body when generating methods. - //* for all intents and purposes, static methods (aka functions) don't really need any special - //* treatment anyway, apart from living in a special namespace -- so this should really be fine. fs->enterStructBody(defn); { for(auto f : this->fields) @@ -138,7 +197,8 @@ TCResult ast::StructDefn::typecheck(sst::TypecheckState* fs, fir::Type* infer, c auto v = dcast(sst::StructFieldDefn, vdef->typecheck(fs).defn()); iceAssert(v); - if(v->init) error(v, "struct fields cannot have inline initialisers"); + if(v->id.name == "_") + v->isTransparentField = true; defn->fields.push_back(v); tys.push_back({ v->id.name, v->type }); @@ -162,6 +222,10 @@ TCResult ast::StructDefn::typecheck(sst::TypecheckState* fs, fir::Type* infer, c for(auto m : this->methods) m->typecheck(fs, str, { }); } + + checkTransparentFieldRedefinition(fs, defn, defn->fields); + + fs->leaveStructBody(); diff --git a/source/typecheck/typecheckstate.cpp b/source/typecheck/typecheckstate.cpp index 875215b7..889e6b09 100644 --- a/source/typecheck/typecheckstate.cpp +++ b/source/typecheck/typecheckstate.cpp @@ -380,6 +380,8 @@ namespace sst + + std::vector TypecheckState::getDefinitionsWithName(const std::string& name, StateTree* tree) { if(tree == 0) diff --git a/source/typecheck/unions.cpp b/source/typecheck/unions.cpp index 99db16a8..f0485f5d 100644 --- a/source/typecheck/unions.cpp +++ b/source/typecheck/unions.cpp @@ -9,6 +9,13 @@ #include "ir/type.h" #include "mpool.h" +// defined in typecheck/structs.cpp +void checkFieldRecursion(sst::TypecheckState* fs, fir::Type* strty, fir::Type* field, const Location& floc); +void checkTransparentFieldRedefinition(sst::TypecheckState* fs, sst::TypeDefn* defn, const std::vector& fields); + + + + TCResult ast::UnionDefn::generateDeclaration(sst::TypecheckState* fs, fir::Type* infer, const TypeParamMap_t& gmaps) { fs->pushLoc(this); @@ -52,8 +59,6 @@ TCResult ast::UnionDefn::typecheck(sst::TypecheckState* fs, fir::Type* infer, co if(tcr.isParametric()) return tcr; else if(tcr.isError()) error(this, "failed to generate declaration for union '%s'", this->name); - //auto defn = dcast(sst::UnionDefn, tcr.defn()); - //iceAssert(defn); if(this->finishedTypechecking.find(tcr.defn()) != this->finishedTypechecking.end()) return TCResult(tcr.defn()); @@ -74,16 +79,20 @@ TCResult ast::UnionDefn::typecheck(sst::TypecheckState* fs, fir::Type* infer, co //* to enter the struct body. fs->enterStructBody(defn); + auto unionTy = defn->type->toRawUnionType(); util::hash_map types; util::hash_map fields; - for(auto variant : this->cases) - { - auto vdef = util::pool(std::get<1>(variant.second)); + + + + auto make_field = [fs, unionTy](const std::string& name, const Location& loc, pts::Type* ty) -> sst::StructFieldDefn* { + + auto vdef = util::pool(loc); vdef->immut = false; - vdef->name = variant.first; + vdef->name = name; vdef->initialiser = nullptr; - vdef->type = std::get<2>(variant.second); + vdef->type = ty; auto sfd = dcast(sst::StructFieldDefn, vdef->typecheck(fs).defn()); iceAssert(sfd); @@ -91,13 +100,51 @@ TCResult ast::UnionDefn::typecheck(sst::TypecheckState* fs, fir::Type* infer, co if(fir::isRefCountedType(sfd->type)) error(sfd, "reference-counted type '%s' cannot be a member of a raw union", sfd->type); - fields[variant.first] = sfd; - types[variant.first] = sfd->type; + checkFieldRecursion(fs, unionTy, sfd->type, sfd->loc); + return sfd; + }; + + std::vector tfields; + std::vector allFields; + + for(auto variant : this->cases) + { + auto sfd = make_field(variant.first, std::get<1>(variant.second), std::get<2>(variant.second)); + iceAssert(sfd); + + fields[sfd->id.name] = sfd; + types[sfd->id.name] = sfd->type; + + allFields.push_back(sfd); + } + + + // do the transparent fields + { + size_t tfn = 0; + for(auto [ loc, pty ] : this->transparentFields) + { + auto sfd = make_field(strprintf("__transparent_field_%zu", tfn++), loc, pty); + iceAssert(sfd); + + sfd->isTransparentField = true; + + // still add to the types, cos we need to compute sizes and stuff + types[sfd->id.name] = sfd->type; + tfields.push_back(sfd); + allFields.push_back(sfd); + } } + checkTransparentFieldRedefinition(fs, defn, allFields); + + defn->fields = fields; + defn->transparentFields = tfields; + + + - auto unionTy = defn->type->toRawUnionType(); unionTy->setBody(types); fs->leaveStructBody(); @@ -110,6 +157,9 @@ TCResult ast::UnionDefn::typecheck(sst::TypecheckState* fs, fir::Type* infer, co util::hash_map> vars; std::vector> vdefs; + + iceAssert(this->transparentFields.empty()); + for(auto variant : this->cases) { vars[variant.first] = { std::get<0>(variant.second), (std::get<2>(variant.second) diff --git a/source/typecheck/variable.cpp b/source/typecheck/variable.cpp index bbb27fff..ab0b1a24 100644 --- a/source/typecheck/variable.cpp +++ b/source/typecheck/variable.cpp @@ -357,7 +357,8 @@ TCResult ast::VarDefn::typecheck(sst::TypecheckState* fs, fir::Type* infer) } } - fs->stree->addDefinition(this->name, defn); + if(this->name != "_") + fs->stree->addDefinition(this->name, defn); // store the place where we were defined. if(fs->isInFunctionBody())