aboutsummaryrefslogtreecommitdiff
path: root/lib/CodeGen
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-22 15:32:49 +0000
committer_ov4 <ov4@ov4.org>2026-10-22 15:32:49 +0000
commit713a313b06256a13ddefa5e257e3b80f8aa808ba (patch)
tree0288e9e9e44b9765cf2285fa3adaf82963612180 /lib/CodeGen
parent5a5a14be5e2683a42906da4b2ad19690e230c871 (diff)
feat(codegen): convert implicitly between CHAR and STRING
A CHAR becomes a one-character STRING in &, comparisons, assignment, parameter passing and RETURN. A STRING becomes a CHAR in assignment, parameter passing and RETURN, with a runtime error when its length is not 1. CHAR with INTEGER or BOOLEAN still does not convert. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Diffstat (limited to 'lib/CodeGen')
-rw-r--r--lib/CodeGen/CallGen.cc24
-rw-r--r--lib/CodeGen/ConstructorGen.cc17
-rw-r--r--lib/CodeGen/ControlFlowGen.cc19
-rw-r--r--lib/CodeGen/OperatorGen.cc27
-rw-r--r--lib/CodeGen/StringValueGen.cc51
-rw-r--r--lib/CodeGen/TypeCheckGen.cc7
6 files changed, 130 insertions, 15 deletions
diff --git a/lib/CodeGen/CallGen.cc b/lib/CodeGen/CallGen.cc
index 4631332..43c0dd4 100644
--- a/lib/CodeGen/CallGen.cc
+++ b/lib/CodeGen/CallGen.cc
@@ -225,6 +225,30 @@ const FuncSig *CodeGen::resolveCall(const std::string &Name,
}
}
if (Ordinary) return Ordinary;
+ // Failing an exact match, CHAR and STRING arguments convert into one
+ // another; the overload must be unique.
+ if (!HasTemplates && !HasExplicit) {
+ auto Scalar = [](const TypeInfo *T) { return T && (T->Kind == TypeKind::Char || T->isString()); };
+ const FuncSig *Converted = nullptr;
+ for (const FuncSig *Sig : Overloads) {
+ if (Sig->Params.size() != Actual.size()) continue;
+ bool Match = true;
+ for (size_t I = 0; I < Actual.size() && Match; ++I) {
+ const ParamSig &P = Sig->Params[I];
+ const TypeInfo *Formal = Types->resolve(P.TypeName);
+ Match = Actual[I].Rank == (P.IsArray ? P.Rank : 0) && Actual[I].Type &&
+ (Actual[I].Type->sameType(Formal) ||
+ (!P.IsByRef && !P.IsArray && Scalar(Actual[I].Type) && Scalar(Formal)));
+ }
+ if (!Match) continue;
+ if (Converted) {
+ reportError("Ambiguous call to %s(%s)", Name.c_str(), Key.c_str());
+ return nullptr;
+ }
+ Converted = Sig;
+ }
+ if (Converted) return Converted;
+ }
if (HasTemplates) {
// A matching non-variadic template wins before any variadic candidate.
for (bool Variadic : {false, true}) {
diff --git a/lib/CodeGen/ConstructorGen.cc b/lib/CodeGen/ConstructorGen.cc
index 286435e..e956f9d 100644
--- a/lib/CodeGen/ConstructorGen.cc
+++ b/lib/CodeGen/ConstructorGen.cc
@@ -22,9 +22,11 @@ Value *CodeGen::emitOwnedValue(ExprAST *Expr, const TypeInfo *Type,
std::vector<size_t> &Transfers) {
if (!Type || (!Type->IsClass && !containsString(Type))) return emitCoercedExpr(Expr, Type);
const TypeInfo *Source = getExprTypeInfo(Expr);
- if (!Source || !Source->sameType(Type)) return emitCoercedExpr(Expr, Type);
+ // A CHAR argument becomes a fresh STRING temporary, owned like any other.
+ bool FromChar = Source && Type->isString() && Source->Kind == TypeKind::Char;
+ if (!Source || (!Source->sameType(Type) && !FromChar)) return emitCoercedExpr(Expr, Type);
Value *Value = nullptr;
- if (isObjectLValue(Expr)) {
+ if (!FromChar && isObjectLValue(Expr)) {
LValueInfo LV;
const auto *Var = dynamic_cast<VariableExprAST *>(Expr);
const auto *Symbol = Var ? getSymbolInfo(Var->getName()) : nullptr;
@@ -172,8 +174,11 @@ void CodeGen::emitAssignment(const LValueInfo &Target, ExprAST *Expr, const Symb
}
return;
}
- if (Info && Info->IsClass) {
- auto Resolution = resolveOperator(tok_assign, Info, getExprTypeInfo(Expr));
+ const TypeInfo *ExprType = getExprTypeInfo(Expr);
+ // A CHAR assigned to a STRING is converted first, not bound to OPERATOR<-.
+ bool CharToString = Info && Info->isString() && ExprType && ExprType->Kind == TypeKind::Char;
+ if (Info && Info->IsClass && !CharToString) {
+ auto Resolution = resolveOperator(tok_assign, Info, ExprType);
if (Resolution.Ambiguous) {
reportError("Ambiguous assignment operator");
return;
@@ -190,7 +195,9 @@ void CodeGen::emitAssignment(const LValueInfo &Target, ExprAST *Expr, const Symb
Value *Val = emitCoercedExpr(Expr, Info);
if (!Val) return;
std::vector<size_t> Transfers;
- bool CopySource = isObjectLValue(Expr);
+ // Only a value of the same type is an existing object to copy; a
+ // converted one is already a fresh temporary.
+ bool CopySource = isObjectLValue(Expr) && ExprType && ExprType->sameType(Info);
if (!CopySource) {
materializeOperatorReceiver(Val, Info);
if (typeNeedsDestruction(Info->Name)) Transfers.push_back(StackObjects.size() - 1);
diff --git a/lib/CodeGen/ControlFlowGen.cc b/lib/CodeGen/ControlFlowGen.cc
index 771669d..a351162 100644
--- a/lib/CodeGen/ControlFlowGen.cc
+++ b/lib/CodeGen/ControlFlowGen.cc
@@ -214,9 +214,22 @@ void CodeGen::emitCaseStmt(CaseStmtAST *Stmt) {
}
size_t Temporaries = StackObjects.size();
Value *LabelValue = emitExpr(Label);
- if (containsString(LabelType) && !isObjectLValue(Label))
+ Value *LeftValue = Selected;
+ const TypeInfo *LeftType = SelectorType;
+ bool LabelConverted = false;
+ // CHAR and STRING compare as STRING.
+ if (SelectorType->isString() && LabelType->Kind == TypeKind::Char) {
+ LabelValue = emitStringFromChar(LabelValue);
+ LabelType = SelectorType;
+ LabelConverted = true;
+ } else if (SelectorType->Kind == TypeKind::Char && LabelType->isString()) {
+ LeftValue = emitStringFromChar(Selected);
+ if (LeftValue) materializeOperatorReceiver(LeftValue, LabelType);
+ LeftType = LabelType;
+ }
+ if (containsString(LabelType) && (LabelConverted || !isObjectLValue(Label)))
materializeOperatorReceiver(LabelValue, LabelType);
- auto Overload = resolveOperator(Op, SelectorType, LabelType);
+ auto Overload = resolveOperator(Op, LeftType, LabelType);
if (Overload.Signature && !Overload.Ambiguous) {
const TypeInfo *Result = resolveType(Overload.Signature->ReturnTypeName);
if (!Result || Result->Kind != TypeKind::Boolean) {
@@ -224,7 +237,7 @@ void CodeGen::emitCaseStmt(CaseStmtAST *Stmt) {
return nullptr;
}
}
- Value *Result = emitBinaryOp(Op, Selected, SelectorType, LabelValue, LabelType, Branch.Line);
+ Value *Result = emitBinaryOp(Op, LeftValue, LeftType, LabelValue, LabelType, Branch.Line);
cleanupTemporaries(Temporaries);
return Result;
};
diff --git a/lib/CodeGen/OperatorGen.cc b/lib/CodeGen/OperatorGen.cc
index 0963e05..a840408 100644
--- a/lib/CodeGen/OperatorGen.cc
+++ b/lib/CodeGen/OperatorGen.cc
@@ -16,13 +16,26 @@ CodeGen::OperatorResolution CodeGen::resolveOperator(
auto Members = FuncGen->getOverloads(Left->Name + "." + operatorName(Op));
Candidates.insert(Candidates.end(), Members.begin(), Members.end());
}
- for (const FuncSig *Sig : Candidates) {
- bool Member = !Sig->Prototype->getOwnerClass().empty();
- if (Sig->Prototype->getOperator() != Op || Sig->Params.size() != (Right ? 2u : 1u) - Member) continue;
- if (!Member && !Left->sameType(resolveType(Sig->Params[0].TypeName))) continue;
- if (Right && !Right->sameType(resolveType(Sig->Params[Member ? 0 : 1].TypeName))) continue;
- if (Result.Signature) Result.Ambiguous = true;
- else Result.Signature = Sig;
+ // An exact match wins; failing that, a CHAR operand may stand in for a
+ // STRING parameter (it is converted when the arguments are marshalled).
+ auto Accepts = [&](const TypeInfo *Actual, const std::string &Param, bool Lenient) {
+ const TypeInfo *Formal = resolveType(Param);
+ if (Actual->sameType(Formal)) return true;
+ return Lenient && Actual->Kind == TypeKind::Char && Formal && Formal->isString();
+ };
+ // Two CHARs compare as CHARs; only '&' always builds a STRING.
+ bool CanConvert = Right && (Op == '&' || Left->isString() || Right->isString());
+ for (bool Lenient : {false, true}) {
+ if (Lenient && !CanConvert) break;
+ for (const FuncSig *Sig : Candidates) {
+ bool Member = !Sig->Prototype->getOwnerClass().empty();
+ if (Sig->Prototype->getOperator() != Op || Sig->Params.size() != (Right ? 2u : 1u) - Member) continue;
+ if (!Member && !Accepts(Left, Sig->Params[0].TypeName, Lenient)) continue;
+ if (Right && !Accepts(Right, Sig->Params[Member ? 0 : 1].TypeName, Lenient)) continue;
+ if (Result.Signature) Result.Ambiguous = true;
+ else Result.Signature = Sig;
+ }
+ if (Result.Signature) break;
}
return Result;
}
diff --git a/lib/CodeGen/StringValueGen.cc b/lib/CodeGen/StringValueGen.cc
index c07f9a9..989bac8 100644
--- a/lib/CodeGen/StringValueGen.cc
+++ b/lib/CodeGen/StringValueGen.cc
@@ -35,6 +35,57 @@ Value *CodeGen::emitStringFromCString(Value *Text) {
return Builder->CreateLoad(Type->LLVMType, Storage, "string");
}
+// CHAR -> STRING: STRING.NEW(1, ch).
+Value *CodeGen::emitStringFromChar(Value *Ch) {
+ const TypeInfo *Type = requireStringType();
+ if (!Type || !Ch) return nullptr;
+ const TypeInfo *CChar = resolveType("C_CHAR");
+ const FuncSig *Constructor = nullptr;
+ for (const FuncSig *Sig : FuncGen->getOverloads(Type->Name + ".NEW"))
+ if (Sig->Params.size() == 2 && !Sig->Params[1].IsArray && !Sig->Params[1].IsByRef &&
+ CChar->sameType(resolveType(Sig->Params[1].TypeName)))
+ Constructor = Sig;
+ if (!Constructor) {
+ reportError("STRING requires a constructor NEW(Count : INTEGER, Ch : C_CHAR)");
+ return nullptr;
+ }
+ if (!checkOperatorAccess(*Constructor)) return nullptr;
+ auto *Storage = CreateEntryBlockAlloca(Builder->GetInsertBlock()->getParent(),
+ Type->LLVMType, "string_value");
+ Builder->CreateStore(Types->getZeroValue(Type->Name), Storage);
+ if (!emitFieldDefaults(Type, Storage, DiagnosticLine)) return nullptr;
+ Value *Count = ConstantInt::get(resolveType("INTEGER")->LLVMType, 1);
+ if (!FuncGen->emitCall(*Constructor, {Storage, Count, Ch})) return nullptr;
+ // The consumer takes ownership, just as with an ordinary class result.
+ return Builder->CreateLoad(Type->LLVMType, Storage, "string");
+}
+
+// STRING -> CHAR: the string must hold exactly one character.
+Value *CodeGen::emitCharFromString(ExprAST *Expr, int Line) {
+ const TypeInfo *Type = requireStringType();
+ if (!Type) return nullptr;
+ Value *Text = emitExpr(Expr);
+ if (!Text) return nullptr;
+ if (!isObjectLValue(Expr)) materializeOperatorReceiver(Text, Type);
+ auto *Receiver = CreateEntryBlockAlloca(Builder->GetInsertBlock()->getParent(),
+ Type->LLVMType, "string_view");
+ Builder->CreateStore(Text, Receiver);
+ const FuncSig *Method = resolveMethod(Type->Name, "_LENGTH", {});
+ if (!Method || !resolveType("INTEGER")->sameType(resolveType(Method->ReturnTypeName))) {
+ reportError("STRING requires a method _LENGTH() RETURNS INTEGER");
+ return nullptr;
+ }
+ Value *Length = emitMethodCall(Type->Name, Receiver, "_LENGTH", {}, Line);
+ if (!Length) return nullptr;
+ Value *Wrong = Builder->CreateICmpNE(Length, ConstantInt::get(Length->getType(), 1), "char_len_bad");
+ Value *Msg = Builder->CreateGlobalString("[Fatal] line %d: STRING of length %lld cannot be converted to CHAR\n",
+ "err_string_to_char", 0, TheModule.get());
+ RuntimeChecker->emitCheck(Wrong, Msg, {RuntimeChecker->lineValue(Line), Length});
+ Value *Ptr = emitMethodCall(Type->Name, Receiver, "C_STR", {}, Line);
+ if (!Ptr) return nullptr;
+ return Builder->CreateLoad(resolveType("CHAR")->LLVMType, Ptr, "char_value");
+}
+
Value *CodeGen::emitStringCString(Value *Value) {
const TypeInfo *Type = requireStringType();
if (!Type || !Value) return nullptr;
diff --git a/lib/CodeGen/TypeCheckGen.cc b/lib/CodeGen/TypeCheckGen.cc
index 7cdf6ea..0f94859 100644
--- a/lib/CodeGen/TypeCheckGen.cc
+++ b/lib/CodeGen/TypeCheckGen.cc
@@ -30,6 +30,13 @@ Value *CodeGen::emitCoercedExpr(ExprAST *E, const TypeInfo *Target) {
return nullptr;
}
+ // CHAR and STRING convert into one another; a STRING that is not exactly
+ // one character long is a runtime error.
+ if (Src && Src->Kind == TypeKind::Char && Target->isString())
+ return emitStringFromChar(emitExpr(E));
+ if (Src && Src->isString() && Target->Kind == TypeKind::Char)
+ return emitCharFromString(E, E->getLine() ? E->getLine() : DiagnosticLine);
+
if (Target->isC() || (Src && Src->isC())) {
if (auto *Addr = dynamic_cast<AddrOfExprAST*>(E)) {
const PointerPayload *PP = Target->asPointer();