From 713a313b06256a13ddefa5e257e3b80f8aa808ba Mon Sep 17 00:00:00 2001 From: _ov4 Date: Thu, 22 Oct 2026 15:32:49 +0000 Subject: 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 --- lib/CodeGen/CallGen.cc | 24 ++++++++++++++++++++ lib/CodeGen/ConstructorGen.cc | 17 ++++++++++----- lib/CodeGen/ControlFlowGen.cc | 19 +++++++++++++--- lib/CodeGen/OperatorGen.cc | 27 +++++++++++++++++------ lib/CodeGen/StringValueGen.cc | 51 +++++++++++++++++++++++++++++++++++++++++++ lib/CodeGen/TypeCheckGen.cc | 7 ++++++ 6 files changed, 130 insertions(+), 15 deletions(-) (limited to 'lib/CodeGen') 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 &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(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 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(E)) { const PointerPayload *PP = Target->asPointer(); -- cgit v1.3.1-4-g156e