TCResult ast::IfStmt::typecheck(sst::TypecheckState* fs, fir::Type* infer) { fs->pushLoc(this); defer(fs->popLoc()); using Case = sst::IfStmt::Case; auto ret = util::pool<sst::IfStmt>(this->loc); fs->pushAnonymousTree(); defer(fs->popTree()); for(auto c : this->cases) { auto inits = util::map(c.inits, [fs](Stmt* s) -> auto { return s->typecheck(fs).stmt(); }); auto cs = Case(c.cond->typecheck(fs).expr(), dcast(sst::Block, c.body->typecheck(fs).stmt()), inits); if(!cs.cond->type->isBoolType() && !cs.cond->type->isPointerType()) error(cs.cond, "non-boolean expression with type '%s' cannot be used as a conditional", cs.cond->type); ret->cases.push_back(cs); iceAssert(ret->cases.back().body); } if(this->elseCase) { ret->elseCase = dcast(sst::Block, this->elseCase->typecheck(fs).stmt()); iceAssert(ret->elseCase); } return TCResult(ret); }
void ConvType::parse() { // read in scope ConvScope scope( &cc->main_scope ); cc->file_stack << ast_class->sourcefile; ast_class->block->parse_in( scope ); base_size = scope.base_size; base_alig = scope.base_alig; for( ConvScope::NamedVar e : scope.variables ) { if ( e.stat ) continue; if ( Ast_Assign *aa = dcast( e.expr.data ) ) { if ( aa->type ) { Past val = aa->val->parse_in( scope ); ConvType *t = val->make_type(); attributes << Attribute{ e.name, t, Past() }; } else { TODO; } // aa->val } else TODO; PRINT( e.name ); PRINT( e.expr ); } cc->file_stack.pop_back(); }
void OverloadError::post() { // first, post the original error. this->top->wordsBeforeContext = "(call site)"; this->top->post(); // sort the candidates by line number // (idk maybe it's a windows thing but it feels like the order changes every so often) auto cds = std::vector<std::pair<Locatable*, ErrorMsg*>>(this->cands.begin(), this->cands.end()); std::sort(cds.begin(), cds.end(), [](auto a, auto b) -> bool { return a.first->loc.line < b.first->loc.line; }); // go through each candidate. int cand_counter = 1; for(auto [ loc, emg ] : cds) { if(emg->kind != ErrKind::Span) { emg->post(); } else { auto spe = dcast(SpanError, emg); iceAssert(spe); spe->top = SimpleError::make(MsgType::Note, loc->loc, "candidate %d was defined here:", cand_counter++); spe->highlightActual = false; spe->post(); } } for(auto sub : this->subs) sub->post(); }
TCResult ast::EnumDefn::typecheck(sst::TypecheckState* fs, fir::Type* infer, const TypeParamMap_t& gmaps) { fs->pushLoc(this); defer(fs->popLoc()); auto tcr = this->generateDeclaration(fs, infer, gmaps); if(tcr.isParametric()) return tcr; else if(tcr.isError()) error(this, "failed to generate declaration for enum '%s'", this->name); auto defn = dcast(sst::EnumDefn, tcr.defn()); iceAssert(defn); auto oldscope = fs->getCurrentScope(); fs->teleportToScope(defn->id.scope); fs->pushTree(defn->id.name); if(this->memberType) defn->memberType = fs->convertParserTypeToFIR(this->memberType); else defn->memberType = fir::Type::getInt64(); auto ety = fir::EnumType::get(defn->id, defn->memberType); size_t index = 0; for(auto cs : this->cases) { sst::Expr* val = 0; if(cs.value) { iceAssert(defn->memberType); val = cs.value->typecheck(fs, defn->memberType).expr(); if(val->type != defn->memberType) error(cs.value, "mismatched type in enum case value; expected type '%s', but found type '%s'", defn->memberType, val->type); } auto ecd = util::pool<sst::EnumCaseDefn>(cs.loc); ecd->id = Identifier(cs.name, IdKind::Name); ecd->id.scope = fs->getCurrentScope(); ecd->type = ety; ecd->parentEnum = defn; ecd->val = val; ecd->index = index++; defn->cases[cs.name] = ecd; fs->stree->addDefinition(cs.name, ecd); } defn->type = ety; fs->popTree(); fs->teleportToScope(oldscope); return TCResult(defn); }
TCResult ast::DecompVarDefn::typecheck(sst::TypecheckState* fs, fir::Type* infer) { fs->pushLoc(this); defer(fs->popLoc()); auto ret = util::pool<sst::DecompDefn>(this->loc); ret->immutable = this->immut; if(auto splat = dcast(ast::SplatOp, this->initialiser)) { if(this->bindings.array) { SpanError::make(SimpleError::make(this->loc, "value splats can only be assigned to tuple decompositions")) ->add(util::ESpan(this->initialiser->loc, "")) ->postAndQuit(); } bool isnest = false; for(const auto& b : this->bindings.inner) { if(b.name.empty()) { isnest = true; break; } } if(isnest) { SpanError::make(SimpleError::make(this->loc, "cannot assign value splats to nested tuple decomposition")) ->add(util::ESpan(this->initialiser->loc, "")) ->postAndQuit(); } // ok, at this point we should be fine. this->initialiser = util::pool<ast::LitTuple>(splat->loc, std::vector<ast::Expr*>(this->bindings.inner.size(), splat->expr)); } ret->init = this->initialiser->typecheck(fs).expr(); ret->bindings = fs->typecheckDecompositions(this->bindings, ret->init->type, this->immut, false); return TCResult(ret); }
static bool checkBlockPathsReturn(sst::TypecheckState* fs, sst::Block* block, fir::Type* retty, std::vector<sst::Block*>* faulty) { // return value is whether or not the block had a return value; // true if all paths explicitly returned, false if not // this return value is used to determine whether we need to insert a // 'return void' thing. bool ret = false; for(size_t i = 0; i < block->statements.size(); i++) { auto& s = block->statements[i]; if(auto retstmt = dcast(sst::ReturnStmt, s)) { // ok... ret = true; auto t = retstmt->expectedType; iceAssert(t); if(fir::getCastDistance(t, retty) < 0) { if(retstmt->expectedType->isVoidType()) { error(retstmt, "expected value after 'return'; function return type is '%s'", retty); } else { std::string msg; if(block->isSingleExpr) msg = "invalid single-expression with type '%s' in function returning '%s'"; else msg = "mismatched type in return statement; function returns '%s', value has type '%s'"; SpanError::make(SimpleError::make(retstmt->loc, msg.c_str(), retty, retstmt->expectedType)) ->add(util::ESpan(retstmt->value->loc, strprintf("type '%s'", retstmt->expectedType))) ->append(SimpleError::make(MsgType::Note, fs->getCurrentFunction()->loc, "function definition is here:")) ->postAndQuit(); } } if(i != block->statements.size() - 1) { SimpleError::make(block->statements[i + 1]->loc, "unreachable code after return statement") ->append(SimpleError::make(MsgType::Note, retstmt->loc, "return statement was here:")) ->postAndQuit();; doTheExit(); } } else /* if(i == block->statements.size() - 1) */ { //* it's our duty to check the internals of these things regardless of their exhaustiveness //* so that we can check for the elision of the merge block. //? eg: if 's' itself does not have an else case, but say one of its branches is exhaustive (all arms return), //? then we can elide the merge block for that branch, even though we can't for 's' itself. //* this isn't strictly necessary (the program is still correct without it), but we generate nicer IR this way. bool exhausted = false; if(auto ifstmt = dcast(sst::IfStmt, s); ifstmt) { bool all = true; for(const auto& c: ifstmt->cases) all = all && checkBlockPathsReturn(fs, c.body, retty, faulty); exhausted = all && ifstmt->elseCase && checkBlockPathsReturn(fs, ifstmt->elseCase, retty, faulty); ifstmt->elideMergeBlock = exhausted; } else if(auto whileloop = dcast(sst::WhileLoop, s); whileloop) { exhausted = checkBlockPathsReturn(fs, whileloop->body, retty, faulty) && whileloop->isDoVariant; whileloop->elideMergeBlock = exhausted; } else if(auto feloop = dcast(sst::ForeachLoop, s); feloop) { // we don't set 'exhausted' here beacuse for loops cannot be guaranteed to be exhaustive. // but we still want to check the block inside for elision. feloop->elideMergeBlock = checkBlockPathsReturn(fs, feloop->body, retty, faulty); } ret = exhausted; }
TCResult resolveConstructorCall(TypecheckState* fs, TypeDefn* typedf, const std::vector<FnCallArgument>& arguments, const PolyArgMapping_t& pams) { //! ACHTUNG: DO NOT REARRANGE ! //* NOTE: ClassDefn inherits from StructDefn * if(auto cls = dcast(ClassDefn, typedf)) { // class initialisers must be called with named arguments only. for(const auto& arg : arguments) { if(arg.name.empty()) { return TCResult(SimpleError::make(arg.loc, "arguments to class initialisers (for class '%s' here) must be named", cls->id.name)); } } auto copy = arguments; //! SELF HANDLING (INSERTION) (CONSTRUCTOR) copy.push_back(FnCallArgument::make(cls->loc, "self", cls->type->getMutablePointerTo())); auto copy1 = copy; auto cand = resolveFunctionCallFromCandidates(fs, util::map(cls->initialisers, [](auto e) -> auto { return dcast(sst::Defn, e); }), ©, pams, true); // TODO: support re-eval of constructor args! // TODO: support re-eval of constructor args! // TODO: support re-eval of constructor args! if(copy1 != copy) error(fs->loc(), "args changed for constructor call -- fixme!!!"); if(cand.isError()) { cand.error()->prepend(SimpleError::make(fs->loc(), "failed to find matching initialiser for class '%s':", cls->id.name)); return TCResult(cand.error()); } return TCResult(cand); } else if(auto str = dcast(StructDefn, typedf)) { std::set<std::string> fieldNames; for(auto f : str->fields) fieldNames.insert(f->id.name); auto err = resolver::verifyStructConstructorArguments(fs->loc(), str->id.name, fieldNames, arguments); if(err.second != nullptr) return TCResult(err.second); // in actual fact we just return the thing here. sigh. return TCResult(str); } else if(auto uvd = dcast(sst::UnionVariantDefn, typedf)) { // TODO: support re-eval of constructor args! // TODO: support re-eval of constructor args! // TODO: support re-eval of constructor args! auto copy = arguments; auto ret = resolver::resolveAndInstantiatePolymorphicUnion(fs, uvd, ©, /* type_infer: */ nullptr, /* isFnCall: */ true); if(copy != arguments) error(fs->loc(), "args changed for constructor call -- fixme!!!"); return ret; } else { return TCResult( SimpleError::make(fs->loc(), "unsupported constructor call on type '%s'", typedf->id.name) ->append(SimpleError::make(typedf->loc, "type was defined here:")) ); } }