diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 041524c..ebe5c88 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -2151,4 +2151,8 @@ add_executable(step362_test tests/step362_test.cpp) target_include_directories(step362_test PRIVATE src) target_link_libraries(step362_test PRIVATE nlohmann_json::nlohmann_json) +add_executable(step363_test tests/step363_test.cpp) +target_include_directories(step363_test PRIVATE src) +target_link_libraries(step363_test PRIVATE nlohmann_json::nlohmann_json) + # Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies) diff --git a/editor/src/AnnotationInference.h b/editor/src/AnnotationInference.h index 79f255b..2c0bf96 100644 --- a/editor/src/AnnotationInference.h +++ b/editor/src/AnnotationInference.h @@ -5,11 +5,15 @@ #include "ast/Variable.h" #include "ast/Statement.h" #include "ast/Expression.h" +#include "ast/Type.h" #include "ast/Annotation.h" +#include "ast/PreprocessorNodes.h" #include "MemoryStrategyInference.h" #include #include #include +#include +#include class AnnotationInference { public: @@ -28,11 +32,15 @@ public: // Delegate memory annotations to existing MemoryStrategyInference if (root->conceptType == "Module") { + auto* mod = static_cast(root); MemoryStrategyInference memInf; auto memSuggestions = memInf.inferAnnotations(root); for (const auto& s : memSuggestions) { out.push_back({s.nodeId, s.annotationType, "strategy", s.strategy, s.reason, s.confidence}); } + if (mod->targetLanguage == "c") { + inferCModule(mod, out); + } } // Infer all other annotation types @@ -377,4 +385,156 @@ private: } return false; } + + void inferCModule(const Module* mod, std::vector& out) const { + if (!mod) return; + inferCHeaderGuard(mod, out); + for (auto* fnNode : mod->getChildren("functions")) { + if (fnNode->conceptType != "Function") continue; + inferCFunction(fnNode, out); + } + } + + void inferCHeaderGuard(const Module* mod, std::vector& out) const { + bool hasIfndef = false; + bool hasEndif = false; + for (auto* stmt : mod->getChildren("statements")) { + if (stmt->conceptType != "PragmaDirective") continue; + auto* pragma = static_cast(stmt); + std::string d = lowerAscii(pragma->directive); + if (startsWith(d, "ifndef") || startsWith(d, "ifdef")) hasIfndef = true; + if (startsWith(d, "endif")) hasEndif = true; + } + if (hasIfndef && hasEndif && + !hasInferred(out, mod->id, "SyntheticAnnotation", "guard")) { + out.push_back({mod->id, "SyntheticAnnotation", "generator", "guard", + "C header guard pattern detected", 0.93}); + } + } + + void inferCFunction(const ASTNode* fn, std::vector& out) const { + std::set existing; + for (const auto* a : fn->getChildren("annotations")) { + existing.insert(a->conceptType); + } + + bool hasGoto = false; + bool hasVoidPtrPattern = false; + bool hasArrayAccess = false; + bool hasBoundsCheck = false; + for (auto* bodyNode : fn->getChildren("body")) { + hasGoto = hasGoto || hasGotoPattern(bodyNode); + hasVoidPtrPattern = hasVoidPtrPattern || hasVoidPtrPatternNode(bodyNode); + hasArrayAccess = hasArrayAccess || hasArrayAccessNode(bodyNode); + hasBoundsCheck = hasBoundsCheck || hasBoundsCheckNode(bodyNode); + } + + if (hasGoto && !existing.count("ComplexityAnnotation")) { + out.push_back({fn->id, "ComplexityAnnotation", "timeComplexity", "high", + "goto usage increases control-flow complexity", 0.90}); + } + if (hasGoto && !existing.count("RiskAnnotation")) { + out.push_back({fn->id, "RiskAnnotation", "level", "medium", + "goto usage is error-prone in C code paths", 0.82}); + } + if (hasVoidPtrPattern && !existing.count("RiskAnnotation")) { + out.push_back({fn->id, "RiskAnnotation", "level", "high", + "void* conversion erases type guarantees", 0.90}); + } + if (hasVoidPtrPattern && !existing.count("AmbiguityAnnotation")) { + out.push_back({fn->id, "AmbiguityAnnotation", "level", "medium", + "void* usage introduces type ambiguity", 0.84}); + } + if (hasArrayAccess && !hasBoundsCheck && !existing.count("BoundsCheckAnnotation")) { + out.push_back({fn->id, "BoundsCheckAnnotation", "mode", "unchecked", + "Array access without guard detected", 0.80}); + } + } + + bool hasGotoPattern(const ASTNode* node) const { + if (!node) return false; + if (node->conceptType == "FunctionCall") { + auto* fc = static_cast(node); + if (lowerAscii(fc->functionName) == "goto") return true; + } + for (auto* child : node->allChildren()) { + if (hasGotoPattern(child)) return true; + } + return false; + } + + bool hasVoidPtrPatternNode(const ASTNode* node) const { + if (!node) return false; + if (node->conceptType == "Variable") { + auto* typeNode = node->getChild("type"); + if (containsVoidPtr(typeNode)) return true; + } + if (node->conceptType == "FunctionCall") { + auto* fc = static_cast(node); + if (lowerAscii(fc->functionName).find("void*") != std::string::npos) return true; + } + for (auto* child : node->allChildren()) { + if (hasVoidPtrPatternNode(child)) return true; + } + return false; + } + + bool hasArrayAccessNode(const ASTNode* node) const { + if (!node) return false; + if (node->conceptType == "IndexAccess") return true; + for (auto* child : node->allChildren()) { + if (hasArrayAccessNode(child)) return true; + } + return false; + } + + bool hasBoundsCheckNode(const ASTNode* node) const { + if (!node) return false; + if (node->conceptType == "BoundsCheckAnnotation") return true; + if (node->conceptType == "FunctionCall") { + auto* fc = static_cast(node); + std::string fn = lowerAscii(fc->functionName); + if (fn == "bounds_check" || fn == "assert" || fn == "check_bounds") return true; + } + for (auto* child : node->allChildren()) { + if (hasBoundsCheckNode(child)) return true; + } + return false; + } + + static bool containsVoidPtr(const ASTNode* typeNode) { + if (!typeNode) return false; + if (typeNode->conceptType == "PrimitiveType") { + auto* p = static_cast(typeNode); + return lowerAscii(p->kind).find("void*") != std::string::npos; + } + if (typeNode->conceptType == "CustomType") { + auto* c = static_cast(typeNode); + return lowerAscii(c->typeName).find("void*") != std::string::npos; + } + return false; + } + + static bool hasInferred(const std::vector& out, + const std::string& nodeId, + const std::string& annotationType, + const std::string& value) { + for (const auto& a : out) { + if (a.nodeId == nodeId && a.annotationType == annotationType && a.value == value) { + return true; + } + } + return false; + } + + static std::string lowerAscii(const std::string& value) { + std::string out = value; + std::transform(out.begin(), out.end(), out.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + return out; + } + + static bool startsWith(const std::string& value, const std::string& prefix) { + return value.rfind(prefix, 0) == 0; + } }; diff --git a/editor/src/CrossLanguageProjector.h b/editor/src/CrossLanguageProjector.h index 12afb86..4479462 100644 --- a/editor/src/CrossLanguageProjector.h +++ b/editor/src/CrossLanguageProjector.h @@ -9,6 +9,7 @@ #include #include #include +#include "ProjectionAdaptation.h" #include "ast/ASTNode.h" #include "ast/Module.h" #include "ast/Function.h" @@ -157,7 +158,9 @@ private: } if (ct == "OwnerAnnotation") { auto* src = static_cast(anno); - auto* a = new OwnerAnnotation(src->id + "_proj", src->strategy); + auto* a = new OwnerAnnotation( + src->id + "_proj", + ProjectionAdaptation::adaptOwnerStrategy(src->strategy, targetLanguage)); a->ownerType = src->ownerType; return a; } @@ -240,6 +243,7 @@ private: auto* typeChild = src->getChild("type"); if (typeChild) { ASTNode* t = cloneNode(typeChild, targetLanguage); + t = ProjectionAdaptation::adaptTypeForTarget(t, targetLanguage); if (t) p->setChild("type", t); } auto* defaultValue = src->getChild("defaultValue"); diff --git a/editor/src/MemoryStrategyInference.h b/editor/src/MemoryStrategyInference.h index 2b175b6..d3ae076 100644 --- a/editor/src/MemoryStrategyInference.h +++ b/editor/src/MemoryStrategyInference.h @@ -10,10 +10,12 @@ #include #include #include +#include #include "ast/ASTNode.h" #include "ast/Module.h" #include "ast/Function.h" #include "ast/Variable.h" +#include "ast/Type.h" #include "ast/Expression.h" #include "ast/Annotation.h" @@ -92,8 +94,7 @@ private: inferCppModule(mod, out); } else if (lang == "c") { - out.push_back({mod->id, "DeallocateAnnotation", "Explicit", - "C requires explicit malloc/free", 0.90}); + inferCModule(mod, out); } else if (lang == "rust") { out.push_back({mod->id, "OwnerAnnotation", "Single", @@ -119,11 +120,26 @@ private: "C++ idiom: RAII with scope-based cleanup", 0.70}); } + void inferCModule(const Module* mod, + std::vector& out) const { + out.push_back({mod->id, "OwnerAnnotation", "Manual", + "C defaults to manual ownership", 0.95}); + out.push_back({mod->id, "LifetimeAnnotation", "Scope", + "C locals default to lexical scope lifetime", 0.92}); + out.push_back({mod->id, "ReclaimAnnotation", "Explicit", + "C uses explicit reclamation (free)", 0.95}); + } + void inferFunction(const Function* fn, const std::string& lang, std::vector& out) const { // Skip if function already has a memory annotation if (hasMemoryAnnotation(fn)) return; + if (lang == "c") { + inferCFunction(fn, out); + return; + } + // Check body for patterns bool hasAlloc = false; bool hasDealloc = false; @@ -162,6 +178,41 @@ private: } } + void inferCFunction(const Function* fn, + std::vector& out) const { + bool hasAlloc = false; + + for (auto* paramNode : fn->getChildren("parameters")) { + if (paramNode->conceptType != "Parameter") continue; + auto* typeNode = paramNode->getChild("type"); + if (isPointerType(typeNode)) { + out.push_back({paramNode->id, "OwnerAnnotation", "Borrowed", + "Pointer parameters in C are borrowed by default", + 0.86}); + } + } + + for (auto* stmt : fn->getChildren("body")) { + hasAlloc = hasAlloc || containsAllocCall(stmt); + inferCLocalLifetime(stmt, out); + } + + auto* retType = fn->getChild("returnType"); + if (isPointerType(retType)) { + out.push_back({fn->id, "OwnerAnnotation", inferCReturnOwner(fn->name), + "C pointer returns are convention-based ownership transfers", + 0.72}); + } + + if (hasAlloc) { + out.push_back({fn->id, "OwnerAnnotation", "Manual", + "malloc/calloc allocation indicates manual ownership", + 0.94}); + out.push_back({fn->id, "ReclaimAnnotation", "Explicit", + "malloc/calloc requires explicit free", 0.94}); + } + } + static bool hasMemoryAnnotation(const ASTNode* node) { for (auto* anno : node->getChildren("annotations")) { const auto& ct = anno->conceptType; @@ -190,6 +241,77 @@ private: } } + static bool containsAllocCall(const ASTNode* node) { + if (!node) return false; + if (node->conceptType == "FunctionCall") { + auto* fc = static_cast(node); + if (fc->functionName == "malloc" || fc->functionName == "calloc") { + return true; + } + } + for (auto* child : node->allChildren()) { + if (containsAllocCall(child)) return true; + } + return false; + } + + static void inferCLocalLifetime(const ASTNode* node, + std::vector& out) { + if (!node) return; + if (node->conceptType == "Variable") { + auto* v = static_cast(node); + auto* typeNode = v->getChild("type"); + std::string typeKind = typeString(typeNode); + std::string lowered = lowerAscii(typeKind); + if (lowered.find("static") != std::string::npos) { + out.push_back({v->id, "LifetimeAnnotation", "Static", + "Static storage duration detected in C declaration", + 0.90}); + } else { + out.push_back({v->id, "LifetimeAnnotation", "Scope", + "Local C variable uses scope-bound lifetime", 0.88}); + } + } + for (auto* child : node->allChildren()) { + inferCLocalLifetime(child, out); + } + } + + static std::string inferCReturnOwner(const std::string& fnName) { + std::string lower = lowerAscii(fnName); + if (startsWith(lower, "get") || startsWith(lower, "borrow") || + startsWith(lower, "peek") || startsWith(lower, "view")) { + return "Borrowed"; + } + return "Transferred"; + } + + static bool isPointerType(const ASTNode* typeNode) { + return typeString(typeNode).find('*') != std::string::npos; + } + + static std::string typeString(const ASTNode* typeNode) { + if (!typeNode) return ""; + if (typeNode->conceptType == "PrimitiveType") { + return static_cast(typeNode)->kind; + } + if (typeNode->conceptType == "CustomType") { + return static_cast(typeNode)->typeName; + } + return ""; + } + + static std::string lowerAscii(const std::string& input) { + std::string out = input; + std::transform(out.begin(), out.end(), out.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + return out; + } + + static bool startsWith(const std::string& value, const std::string& prefix) { + return value.rfind(prefix, 0) == 0; + } + static std::string feedbackKey(const Suggestion& suggestion) { return suggestion.annotationType + ":" + suggestion.strategy; } diff --git a/editor/src/ProjectionAdaptation.h b/editor/src/ProjectionAdaptation.h new file mode 100644 index 0000000..c2c62dd --- /dev/null +++ b/editor/src/ProjectionAdaptation.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include +#include "ast/ASTNode.h" +#include "ast/Type.h" + +namespace ProjectionAdaptation { + +inline std::string adaptOwnerStrategy(const std::string& strategy, + const std::string& targetLanguage) { + if (targetLanguage == "rust") { + if (strategy == "Manual") return "Box"; + if (strategy == "Borrowed") return "Borrowed"; + if (strategy == "Transferred") return "Moved"; + } + return strategy; +} + +inline void trimInPlace(std::string& text) { + while (!text.empty() && std::isspace(static_cast(text.front()))) { + text.erase(text.begin()); + } + while (!text.empty() && std::isspace(static_cast(text.back()))) { + text.pop_back(); + } +} + +inline bool startsWith(const std::string& value, const std::string& prefix) { + return value.rfind(prefix, 0) == 0; +} + +inline std::string stripPointers(const std::string& rawType) { + std::string out = rawType; + out.erase(std::remove(out.begin(), out.end(), '*'), out.end()); + trimInPlace(out); + if (startsWith(out, "const ")) { + out = out.substr(6); + trimInPlace(out); + } + return out; +} + +inline ASTNode* adaptPointerTypeForRust(ASTNode* typeNode) { + if (!typeNode) return nullptr; + if (typeNode->conceptType == "PrimitiveType") { + auto* p = static_cast(typeNode); + if (p->kind.find('*') == std::string::npos) return typeNode; + std::string base = stripPointers(p->kind); + if (!base.empty()) p->kind = "&" + base; + return p; + } + if (typeNode->conceptType == "CustomType") { + auto* c = static_cast(typeNode); + if (c->typeName.find('*') == std::string::npos) return typeNode; + std::string base = stripPointers(c->typeName); + if (!base.empty()) c->typeName = "&" + base; + return c; + } + return typeNode; +} + +inline ASTNode* adaptTypeForTarget(ASTNode* typeNode, + const std::string& targetLanguage) { + if (targetLanguage == "rust") { + return adaptPointerTypeForRust(typeNode); + } + return typeNode; +} + +} // namespace ProjectionAdaptation diff --git a/editor/tests/step363_test.cpp b/editor/tests/step363_test.cpp new file mode 100644 index 0000000..17465df --- /dev/null +++ b/editor/tests/step363_test.cpp @@ -0,0 +1,263 @@ +// Step 363: C Memory Annotation Mapping (12 tests) + +#include +#include +#include +#include +#include "MemoryStrategyInference.h" +#include "AnnotationInference.h" +#include "CrossLanguageProjector.h" +#include "ast/CParser.h" + +static bool hasMemorySuggestion( + const std::vector& suggestions, + const std::string& nodeId, + const std::string& annotationType, + const std::string& strategy) { + for (const auto& s : suggestions) { + if (s.nodeId == nodeId && s.annotationType == annotationType && s.strategy == strategy) { + return true; + } + } + return false; +} + +static bool hasInferred( + const std::vector& inferred, + const std::string& nodeId, + const std::string& annotationType, + const std::string& value) { + for (const auto& a : inferred) { + if (a.nodeId == nodeId && a.annotationType == annotationType && a.value == value) { + return true; + } + } + return false; +} + +int main() { + int passed = 0; + + // Test 1: C defaults applied on module + { + Module mod("m1", "CDefaults", "c"); + MemoryStrategyInference inf; + auto suggestions = inf.inferAnnotations(&mod); + assert(hasMemorySuggestion(suggestions, "m1", "OwnerAnnotation", "Manual")); + assert(hasMemorySuggestion(suggestions, "m1", "LifetimeAnnotation", "Scope")); + assert(hasMemorySuggestion(suggestions, "m1", "ReclaimAnnotation", "Explicit")); + std::cout << "Test 1 PASSED: c defaults owner/lifetime/reclaim\n"; + passed++; + } + + // Test 2: malloc pattern -> Owner(Manual) + Reclaim(Explicit) + { + Module mod("m1", "CMalloc", "c"); + Function fn("f1", "alloc_buf"); + FunctionCall* mallocCall = new FunctionCall(); + mallocCall->id = "c1"; + mallocCall->functionName = "malloc"; + fn.addChild("body", mallocCall); + mod.addChild("functions", &fn); + + MemoryStrategyInference inf; + auto suggestions = inf.inferAnnotations(&mod); + assert(hasMemorySuggestion(suggestions, "f1", "OwnerAnnotation", "Manual")); + assert(hasMemorySuggestion(suggestions, "f1", "ReclaimAnnotation", "Explicit")); + std::cout << "Test 2 PASSED: malloc infers manual+explicit\n"; + passed++; + } + + // Test 3: local variable -> Lifetime(Scope) + { + Module mod("m1", "CLocal", "c"); + Function fn("f1", "work"); + Variable* v = new Variable("v1", "x"); + v->setChild("type", new PrimitiveType("t1", "int")); + fn.addChild("body", v); + mod.addChild("functions", &fn); + + MemoryStrategyInference inf; + auto suggestions = inf.inferAnnotations(&mod); + assert(hasMemorySuggestion(suggestions, "v1", "LifetimeAnnotation", "Scope")); + std::cout << "Test 3 PASSED: local variable infers scope lifetime\n"; + passed++; + } + + // Test 4: pointer parameter -> Owner(Borrowed) + { + Module mod("m1", "CParam", "c"); + Function fn("f1", "consume"); + Parameter* p = new Parameter("p1", "ptr"); + p->setChild("type", new PrimitiveType("t1", "int*")); + fn.addChild("parameters", p); + mod.addChild("functions", &fn); + + MemoryStrategyInference inf; + auto suggestions = inf.inferAnnotations(&mod); + assert(hasMemorySuggestion(suggestions, "p1", "OwnerAnnotation", "Borrowed")); + std::cout << "Test 4 PASSED: pointer parameter infers borrowed owner\n"; + passed++; + } + + // Test 5: return pointer convention -> Borrowed for get* names + { + Module mod("m1", "CReturn", "c"); + Function fn("f1", "get_buffer"); + fn.setChild("returnType", new PrimitiveType("t1", "char*")); + mod.addChild("functions", &fn); + + MemoryStrategyInference inf; + auto suggestions = inf.inferAnnotations(&mod); + assert(hasMemorySuggestion(suggestions, "f1", "OwnerAnnotation", "Borrowed")); + std::cout << "Test 5 PASSED: get* pointer return infers borrowed owner\n"; + passed++; + } + + // Test 6: static variable -> Lifetime(Static) + { + Module mod("m1", "CStatic", "c"); + Function fn("f1", "counter"); + Variable* v = new Variable("v1", "counter"); + v->setChild("type", new PrimitiveType("t1", "static int")); + fn.addChild("body", v); + mod.addChild("functions", &fn); + + MemoryStrategyInference inf; + auto suggestions = inf.inferAnnotations(&mod); + assert(hasMemorySuggestion(suggestions, "v1", "LifetimeAnnotation", "Static")); + std::cout << "Test 6 PASSED: static variable infers static lifetime\n"; + passed++; + } + + // Test 7: goto usage -> Complexity(high) + Risk(medium) + { + Module mod("m1", "CGoto", "c"); + Function fn("f1", "jumping"); + FunctionCall* go = new FunctionCall(); + go->id = "g1"; + go->functionName = "goto"; + fn.addChild("body", go); + mod.addChild("functions", &fn); + + AnnotationInference inf; + auto inferred = inf.inferAll(&mod); + assert(hasInferred(inferred, "f1", "ComplexityAnnotation", "high")); + assert(hasInferred(inferred, "f1", "RiskAnnotation", "medium")); + std::cout << "Test 7 PASSED: goto infers complexity/risk signals\n"; + passed++; + } + + // Test 8: void* cast pattern -> Risk(high) + Ambiguity(medium) + { + Module mod("m1", "CVoid", "c"); + Function fn("f1", "casty"); + Variable* v = new Variable("v1", "raw"); + v->setChild("type", new PrimitiveType("t1", "void*")); + fn.addChild("body", v); + mod.addChild("functions", &fn); + + AnnotationInference inf; + auto inferred = inf.inferAll(&mod); + assert(hasInferred(inferred, "f1", "RiskAnnotation", "high")); + assert(hasInferred(inferred, "f1", "AmbiguityAnnotation", "medium")); + std::cout << "Test 8 PASSED: void* infers risk+ambiguity\n"; + passed++; + } + + // Test 9: array access without checks -> BoundsCheck(unchecked) + { + Module mod("m1", "CArray", "c"); + Function fn("f1", "idx"); + IndexAccess* idx = new IndexAccess(); + idx->id = "i1"; + idx->setChild("target", new VariableReference("vr1", "arr")); + idx->setChild("index", new VariableReference("vr2", "i")); + ExpressionStatement* stmt = new ExpressionStatement(); + stmt->id = "s1"; + stmt->setChild("expression", idx); + fn.addChild("body", stmt); + mod.addChild("functions", &fn); + + AnnotationInference inf; + auto inferred = inf.inferAll(&mod); + assert(hasInferred(inferred, "f1", "BoundsCheckAnnotation", "unchecked")); + std::cout << "Test 9 PASSED: unchecked array access inferred\n"; + passed++; + } + + // Test 10: header guard pattern -> Synthetic(guard) + { + std::string source = "#ifndef SAMPLE_H\n#define SAMPLE_H\nint x;\n#endif\n"; + auto mod = CParser::parseC(source); + AnnotationInference inf; + auto inferred = inf.inferAll(mod.get()); + assert(hasInferred(inferred, mod->id, "SyntheticAnnotation", "guard")); + std::cout << "Test 10 PASSED: header guard infers synthetic marker\n"; + passed++; + } + + // Test 11: inference confidence scores remain reasonable + { + Module mod("m1", "CConfidence", "c"); + Function fn("f1", "alloc"); + FunctionCall* mallocCall = new FunctionCall(); + mallocCall->id = "c1"; + mallocCall->functionName = "malloc"; + fn.addChild("body", mallocCall); + mod.addChild("functions", &fn); + + MemoryStrategyInference inf; + auto suggestions = inf.inferAnnotations(&mod); + bool foundHigh = false; + for (const auto& s : suggestions) { + assert(s.confidence >= 0.0 && s.confidence <= 1.0); + if (s.confidence >= 0.70) foundHigh = true; + } + assert(foundHigh); + std::cout << "Test 11 PASSED: confidence values are bounded and useful\n"; + passed++; + } + + // Test 12: cross-language C ownership adaptation to Rust + { + Module mod("m1", "COwnership", "c"); + Function* fn = new Function("f1", "transfer"); + auto* fnOwner = new OwnerAnnotation("a1", "Manual"); + fn->addChild("annotations", fnOwner); + + Parameter* p = new Parameter("p1", "src"); + p->setChild("type", new PrimitiveType("t1", "int*")); + auto* paramOwner = new OwnerAnnotation("a2", "Borrowed"); + p->addChild("annotations", paramOwner); + fn->addChild("parameters", p); + mod.addChild("functions", fn); + + CrossLanguageProjector projector; + auto rustMod = projector.project(&mod, "rust"); + auto* rustFn = static_cast(rustMod->getChildren("functions")[0]); + + bool hasBoxOwner = false; + for (auto* anno : rustFn->getChildren("annotations")) { + if (anno->conceptType == "OwnerAnnotation") { + auto* owner = static_cast(anno); + if (owner->strategy == "Box") hasBoxOwner = true; + } + } + assert(hasBoxOwner); + + auto* rustParam = static_cast(rustFn->getChildren("parameters")[0]); + auto* rustType = rustParam->getChild("type"); + assert(rustType != nullptr); + assert(rustType->conceptType == "PrimitiveType"); + auto* rustPrim = static_cast(rustType); + assert(!rustPrim->kind.empty() && rustPrim->kind[0] == '&'); + + std::cout << "Test 12 PASSED: C owner/manual->Box and borrowed pointer->&T style\n"; + passed++; + } + + std::cout << "\nResults: " << passed << "/12\n"; + assert(passed == 12); + return 0; +} diff --git a/progress.md b/progress.md index 388acff..6238405 100644 --- a/progress.md +++ b/progress.md @@ -2123,6 +2123,51 @@ includes, and method-name lowering for class-style methods. - `step361_test` — PASS (12/12) regression coverage - `step360_test` — PASS (8/8) regression coverage +### Step 363: C Memory Annotation Mapping +**Status:** PASS (12/12 tests) + +Implemented C-specific memory and semantic annotation inference so C code now +gets meaningful defaults and risk signals aligned with explicit-memory patterns. + +**Files created:** +- `editor/src/ProjectionAdaptation.h` — extracted projection adaptation helpers: + - C owner strategy adaptation for Rust targets (`Manual -> Box`, + `Transferred -> Moved`) + - pointer-type adaptation for Rust references (`T* -> &T`) + +**Files modified:** +- `editor/src/MemoryStrategyInference.h` — C-specific memory inference: + - module defaults: `Owner(Manual)`, `Lifetime(Scope)`, `Reclaim(Explicit)` + - malloc/calloc detection: function-level owner/reclaim suggestions + - pointer parameter inference: `Owner(Borrowed)` + - pointer return inference by naming convention: + - `get*/borrow*/peek*/view*` -> `Owner(Borrowed)` + - otherwise -> `Owner(Transferred)` + - local/static variable lifetime inference: + - locals -> `Lifetime(Scope)` + - static storage -> `Lifetime(Static)` +- `editor/src/AnnotationInference.h` — C pattern inference: + - `goto` pattern -> `Complexity(high)` + `Risk(medium)` + - unchecked array indexing -> `BoundsCheck(unchecked)` + - `void*` usage -> `Risk(high)` + `Ambiguity(medium)` + - header guard detection -> `Synthetic(guard)` +- `editor/src/CrossLanguageProjector.h` — uses `ProjectionAdaptation` helpers + for ownership and pointer-type adaptation without exceeding header size limits +- `editor/tests/step363_test.cpp` — 12 tests covering defaults, C-specific rules, + confidence bounds, header-guard synthesis, and C->Rust ownership adaptation +- `editor/CMakeLists.txt` — `step363_test` target + +**Verification run:** +- `step363_test` — PASS (12/12) new step coverage +- `step362_test` — PASS (12/12) regression coverage +- `step361_test` — PASS (12/12) regression coverage +- `step360_test` — PASS (8/8) regression coverage + +**Architecture gate check:** +- `editor/src/CrossLanguageProjector.h` remained under the 600-line cap after + refactor by extracting target adaptation logic into + `editor/src/ProjectionAdaptation.h` + # Roadmap Planning — Sprints 12-25+ ## Status: Planning Complete (Sprints 12-19 detailed, 20-25 in roadmap.md)