实现 12.7 类型检查:表达式类型、语句规则、FUNCTION 禁写全局。

- Typecheck.h/cpp:三型(BOOL/INT/TIME)求值,AND/OR/NOT 只吃 BOOL、算术只吃 INT(TIME 无算术)、比较同型、IF/WHILE 条件必须 BOOL
- FUNCTION 禁写全局(含经 VAR_EXTERNAL)——用例 14 点亮;函数名赋值=返回类型
- FB 命名实参匹配字段类型;FB 实例不能当值
- typecheck_test:23 断言(用例 14 + 8 类负例 + 13 个正例),ctest 8/8
This commit is contained in:
2026-08-21 11:26:55 +08:00
parent 93a4662d6a
commit 9cd4997a3a
7 changed files with 826 additions and 1 deletions
+11
View File
@@ -56,3 +56,14 @@ target_compile_definitions(linker_test PRIVATE
add_test(NAME linker_links
COMMAND linker_test)
# 类型检查测试:用例 14 + 负例 + 正例(REPO_ROOT 注入源目录绝对路径)
add_executable(typecheck_test
./src/typecheck_test.cpp)
target_link_libraries(typecheck_test PRIVATE compiler)
target_compile_definitions(typecheck_test PRIVATE
REPO_ROOT="${CMAKE_SOURCE_DIR}")
add_test(NAME typecheck_types
COMMAND typecheck_test)
+213
View File
@@ -0,0 +1,213 @@
/**
* @file typecheck_test.cpp
* @brief 类型检查测试:用例 14 + 负例 + 正例
* @author
* @date 2026-08-21
*/
#include <cstdio>
#include <filesystem>
#include <string>
#include <vector>
#include "compiler/Linker.h"
#include "compiler/Project.h"
#include "compiler/Typecheck.h"
#ifndef REPO_ROOT
#define REPO_ROOT "."
#endif
static int g_checks = 0;
#define CHECK(cond) \
do { \
if (!(cond)) { \
std::printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \
return false; \
} \
++g_checks; \
} while (0)
// 从用例目录加载、链接、类型检查
static bool check_case(const char* dir, compiler::LinkResult* r, std::string* err) {
using namespace compiler;
const std::string toml = std::string(REPO_ROOT) + "/tests/cases/" + dir + "/project.toml";
Project p;
if (!parse_project(toml, &p, err)) {
return false;
}
std::vector<SourceUnit> units;
for (const std::string& f : compile_files(p)) {
SourceUnit u;
if (!load_unit(p.base_dir + "/" + f, &u, err)) {
return false;
}
units.push_back(std::move(u));
}
if (!link_project(p, units, r, err)) {
return false;
}
return check_project(p, units, *r, err);
}
// 从内联源码构造工程(写临时目录),加载、链接、类型检查
static bool check_src(const char* src_name, const char* st_content,
std::string* err) {
using namespace compiler;
const std::string dir = std::string(REPO_ROOT) + "/build/tc_tmp_" + src_name;
std::filesystem::create_directories(dir);
const std::string toml_path = dir + "/project.toml";
const std::string st_path = dir + "/main.st";
std::FILE* f = std::fopen(toml_path.c_str(), "w");
std::fprintf(f, "[project]\nname = \"tc\"\nentry = \"program MAIN\"\n"
"cycle_limit = 1000\ndt_ms = 10\n"
"[files]\nst = [\"main.st\"]\n");
std::fclose(f);
f = std::fopen(st_path.c_str(), "w");
std::fputs(st_content, f);
std::fclose(f);
Project p;
if (!parse_project(toml_path, &p, err)) {
return false;
}
std::vector<SourceUnit> units;
for (const std::string& file : compile_files(p)) {
SourceUnit u;
if (!load_unit(p.base_dir + "/" + file, &u, err)) {
return false;
}
units.push_back(std::move(u));
}
LinkResult r;
if (!link_project(p, units, &r, err)) {
return false;
}
return check_project(p, units, r, err);
}
static bool expect_type_err(const char* src_name, const char* st_content,
const char* keyword) {
std::string err;
if (check_src(src_name, st_content, &err)) {
std::printf("FAIL %s: typecheck passed\n", src_name);
return false;
}
if (err.find("type error") != 0) {
std::printf("FAIL %s: want 'type error', got '%s'\n", src_name, err.c_str());
return false;
}
if (err.find(keyword) == std::string::npos) {
std::printf("FAIL %s: want '%s', got '%s'\n", src_name, keyword, err.c_str());
return false;
}
++g_checks;
return true;
}
// ---- 1. 用例 14FUNCTION 写全局(经 VAR_EXTERNAL----
static bool test_case14() {
std::string err;
compiler::LinkResult r;
if (check_case("14_function_write_global", &r, &err)) {
std::printf("FAIL case14: typecheck passed\n");
return false;
}
CHECK(err.find("type error") == 0);
CHECK(err.find("function cannot write global") != std::string::npos);
return true;
}
// ---- 2. 负例 ----
static bool test_negative() {
// AND 吃 INT
if (!expect_type_err("and_int",
"PROGRAM MAIN\nVAR\n a, b : INT;\n x : BOOL;\nEND_VAR\n"
" x := a AND b;\nEND_PROGRAM\n",
"AND operands must be BOOL")) {
return false;
}
// 赋值类型不匹配(INT 赋给 BOOL)
if (!expect_type_err("assign_mismatch",
"PROGRAM MAIN\nVAR\n b : BOOL;\nEND_VAR\n"
" b := 5;\nEND_PROGRAM\n",
"type mismatch in assignment to 'b'")) {
return false;
}
// INT 与 TIME 混用算术
if (!expect_type_err("int_time",
"PROGRAM MAIN\nVAR\n x : INT;\n t : TIME;\n y : INT;\nEND_VAR\n"
" y := x + t;\nEND_PROGRAM\n",
"TIME has no arithmetic")) {
return false;
}
// NOT 吃 INT
if (!expect_type_err("not_int",
"PROGRAM MAIN\nVAR\n a : INT;\n b : BOOL;\nEND_VAR\n"
" b := NOT a;\nEND_PROGRAM\n",
"NOT operand must be BOOL")) {
return false;
}
// 比较异型(INT = TIME
if (!expect_type_err("cmp_mismatch",
"PROGRAM MAIN\nVAR\n x : INT;\n t : TIME;\n b : BOOL;\nEND_VAR\n"
" b := x = t;\nEND_PROGRAM\n",
"comparison of mismatched types")) {
return false;
}
// FB 输入类型不匹配(TON pt 期望 TIME,给 TRUE
if (!expect_type_err("fb_arg",
"PROGRAM MAIN\nVAR\n t : TON;\n q : BOOL;\nEND_VAR\n"
" t(in := TRUE, pt := TRUE);\n"
" q := t.Q;\nEND_PROGRAM\n",
"FB input 'pt' expects TIME")) {
return false;
}
// IF 条件非 BOOL
if (!expect_type_err("if_cond",
"PROGRAM MAIN\nVAR\n n : INT;\n x : INT;\nEND_VAR\n"
" IF n THEN\n x := 1;\n END_IF\nEND_PROGRAM\n",
"condition must be BOOL")) {
return false;
}
// FB 实例当值用
if (!expect_type_err("fb_value",
"PROGRAM MAIN\nVAR\n t : TON;\n b : BOOL;\nEND_VAR\n"
" b := t;\nEND_PROGRAM\n",
"cannot be used as a value")) {
return false;
}
return true;
}
// ---- 3. 正例 ----
static bool test_positive() {
std::string err;
compiler::LinkResult r;
CHECK(check_case("01_empty_main", &r, &err));
CHECK(check_case("02_bool_assign", &r, &err));
CHECK(check_case("03_short_circuit", &r, &err));
CHECK(check_case("04_if_elsif_else", &r, &err));
CHECK(check_case("05_while_normal", &r, &err));
CHECK(check_case("07_int_arith", &r, &err));
CHECK(check_case("08_time_literal", &r, &err));
CHECK(check_case("09_gvl_external", &r, &err));
CHECK(check_case("13_function_call", &r, &err));
CHECK(check_case("15_fb_instance", &r, &err));
CHECK(check_case("17_ton", &r, &err));
CHECK(check_case("18_tof_ctu", &r, &err));
CHECK(check_case("20_line1", &r, &err));
return true;
}
int main() {
if (!test_case14()) return 1;
if (!test_negative()) return 1;
if (!test_positive()) return 1;
std::printf("typecheck_test: %d checks passed\n", g_checks);
return 0;
}