From 0cf54b4f77ccf8376341950b02ceb7c1d1268ed7 Mon Sep 17 00:00:00 2001 From: _ov4 Date: Fri, 30 Oct 2026 14:31:07 +0000 Subject: refactor(strings): construct string values implicitly from raw literals Keep double-quoted literals and text constants as C_STRING values. Use marked STRING constructors for text and CHAR conversion, preserving string operations and I/O through the existing class machinery. Co-Authored-By: Claude Opus 5.5 --- lib/CodeGen/CallGen.cc | 26 +++++++++++++------------- lib/CodeGen/ConstantGen.cc | 23 ----------------------- lib/CodeGen/ConstructorGen.cc | 8 ++------ lib/CodeGen/ControlFlowGen.cc | 13 +------------ lib/CodeGen/ExpressionGen.cc | 3 +-- lib/CodeGen/OperatorGen.cc | 2 +- lib/CodeGen/OutputGen.cc | 13 +++++++++++++ lib/CodeGen/StringValueGen.cc | 43 +++++++++++-------------------------------- lib/CodeGen/TypeCheckGen.cc | 25 ++++++++++++++++--------- lib/CodeGen/ValueTypeGen.cc | 3 +-- 10 files changed, 59 insertions(+), 100 deletions(-) (limited to 'lib') diff --git a/lib/CodeGen/CallGen.cc b/lib/CodeGen/CallGen.cc index f5f1eda..6c6a1a9 100644 --- a/lib/CodeGen/CallGen.cc +++ b/lib/CodeGen/CallGen.cc @@ -214,7 +214,7 @@ const FuncSig *CodeGen::resolveCall(const std::string &Name, int Count = 0; // One layer of parameter matching. Lenient admits implicit constructors; // FilterReturn keeps only candidates whose result is the wanted type. - auto Scan = [&](bool Lenient, bool FilterReturn) -> const FuncSig * { + auto Scan = [&](int Layer, bool FilterReturn) -> const FuncSig * { const FuncSig *First = nullptr; std::vector Matches; Count = 0; @@ -228,8 +228,8 @@ const FuncSig *CodeGen::resolveCall(const std::string &Name, bool SameRank = Actual[I].Rank == (P.IsArray ? P.Rank : 0); bool Same = SameRank && Actual[I].Type && Actual[I].Type->sameType(Formal); Match = Same || - (Lenient && SameRank && Actual[I].Type && !P.IsByRef && !P.IsArray && - canImplicitlyConvert(Actual[I].Type, Formal)); + (Layer && SameRank && Actual[I].Type && !P.IsByRef && !P.IsArray && + implicitConversionRank(Actual[I].Type, Formal) <= Layer); } if (!Match) continue; ParamMatched = true; @@ -254,9 +254,9 @@ const FuncSig *CodeGen::resolveCall(const std::string &Name, Name.c_str(), Key.c_str()); }; bool Ambiguous = false; - bool AllowConversions = false; + int ConversionLayer = 0; auto Try = [&](bool FilterReturn, const FuncSig *&Selected) { - Selected = Scan(AllowConversions, FilterReturn); + Selected = Scan(ConversionLayer, FilterReturn); if (Count > 1) { ReportAmbiguous(FilterReturn); Ambiguous = true; @@ -264,8 +264,8 @@ const FuncSig *CodeGen::resolveCall(const std::string &Name, }; // Exhaust exact ordinary/template candidates before considering conversions. - for (bool ConversionLayer : {false, true}) { - AllowConversions = ConversionLayer; + for (int Layer : {0, 1, 2}) { + ConversionLayer = Layer; const FuncSig *Selected = nullptr; if (PreferResult) { Try(true, Selected); @@ -312,8 +312,8 @@ const FuncSig *CodeGen::resolveCall(const std::string &Name, if (Binding != Bindings.end()) { if (Binding->second.empty()) Binding->second.push_back(Type->Name); else if (!Type->sameType(resolveType(Binding->second.front())) && - !(AllowConversions && P.Mode == PassMode::ByVal && !P.IsArray && - canImplicitlyConvert(Type, resolveType(Binding->second.front())))) { + !(ConversionLayer && P.Mode == PassMode::ByVal && !P.IsArray && + implicitConversionRank(Type, resolveType(Binding->second.front())) <= ConversionLayer)) { Match = false; break; } } @@ -328,8 +328,8 @@ const FuncSig *CodeGen::resolveCall(const std::string &Name, if (!Type || Type->isVoid() || Actual[I].Rank) { Match = false; break; } if (Index < Pack.size()) { if (!Type->sameType(resolveType(Pack[Index])) && - !(AllowConversions && Params.back().Mode == PassMode::ByVal && - canImplicitlyConvert(Type, resolveType(Pack[Index])))) Match = false; + !(ConversionLayer && Params.back().Mode == PassMode::ByVal && + implicitConversionRank(Type, resolveType(Pack[Index])) <= ConversionLayer)) Match = false; } else Pack.push_back(Type->Name); } } @@ -345,8 +345,8 @@ const FuncSig *CodeGen::resolveCall(const std::string &Name, std::string Expected = Binding != Bindings.end() ? Binding->second.front() : TypeName.find('<') != std::string::npos ? Substitute.type(TypeName) : TypeName; if (!Match || (!Actual[I].Type->sameType(resolveType(Expected)) && - !(AllowConversions && Params[I].Mode == PassMode::ByVal && !Params[I].IsArray && - canImplicitlyConvert(Actual[I].Type, resolveType(Expected))))) { + !(ConversionLayer && Params[I].Mode == PassMode::ByVal && !Params[I].IsArray && + implicitConversionRank(Actual[I].Type, resolveType(Expected)) <= ConversionLayer))) { Match = false; break; } } diff --git a/lib/CodeGen/ConstantGen.cc b/lib/CodeGen/ConstantGen.cc index a512490..66919d2 100644 --- a/lib/CodeGen/ConstantGen.cc +++ b/lib/CodeGen/ConstantGen.cc @@ -64,31 +64,8 @@ void CodeGen::emitConstantStmt(ConstantStmtAST *Stmt) { return; } if (!prepareSymbolDeclaration(Name, true)) return; - auto Existing = StringConstants.find(Stmt); - if (Existing != StringConstants.end()) { - Symbols[Name] = Existing->second; - return; - } const TypeInfo *Type = getExprTypeInfo(Stmt->getValue()); Value *Val = emitExpr(Stmt->getValue()); - if (Type && Type->isString() && Val) { - Value *Storage; - bool TopLevel = ConstantScopes.back().ID == 0; - if (TopLevel) { - Storage = new GlobalVariable(*TheModule, Type->LLVMType, false, - GlobalValue::InternalLinkage, Types->getZeroValue(Type->Name), "__cps_constant." + Name); - } else { - Storage = CreateEntryBlockAlloca(Builder->GetInsertBlock()->getParent(), Type->LLVMType, Name); - } - size_t Lifetime = registerStackObject(Storage, Type->Name, DiagnosticLine); - Builder->CreateStore(Val, Storage); - activateStackObject(Lifetime); - SymbolInfo Info{Storage, Type->Name, false, nullptr, ConstantScopes.back().ID}; - Info.ReadOnly = true; - Symbols[Name] = Info; - if (TopLevel) StringConstants.emplace(Stmt, Info); - return; - } auto *Literal = dyn_cast_or_null(Val); if (!Type || !Literal) { reportError("CONSTANT initializer must be a literal"); diff --git a/lib/CodeGen/ConstructorGen.cc b/lib/CodeGen/ConstructorGen.cc index 81c448f..585249c 100644 --- a/lib/CodeGen/ConstructorGen.cc +++ b/lib/CodeGen/ConstructorGen.cc @@ -22,11 +22,9 @@ Value *CodeGen::emitOwnedValue(ExprAST *Expr, const TypeInfo *Type, expectResult(Expr, Type); if (!Type || (!Type->IsClass && !containsString(Type))) return emitCoercedExpr(Expr, Type); const TypeInfo *Source = getExprTypeInfo(Expr); - // A CHAR argument becomes a fresh STRING temporary, owned like any other. - bool FromChar = Source && Type->isString() && Source->Kind == TypeKind::Char; if (!Source) return emitCoercedExpr(Expr, Type); Value *Value = nullptr; - if (!FromChar && Source->sameType(Type) && isObjectLValue(Expr)) { + if (Source->sameType(Type) && isObjectLValue(Expr)) { LValueInfo LV; const auto *Var = dynamic_cast(Expr); const auto *Symbol = Var ? getSymbolInfo(Var->getName()) : nullptr; @@ -177,9 +175,7 @@ void CodeGen::emitAssignment(const LValueInfo &Target, ExprAST *Expr, const Symb return; } 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) { + if (Info && Info->IsClass) { auto Resolution = resolveOperator(tok_assign, Info, ExprType); if (Resolution.Ambiguous) { reportError("Ambiguous assignment operator"); diff --git a/lib/CodeGen/ControlFlowGen.cc b/lib/CodeGen/ControlFlowGen.cc index fe9a954..3d16098 100644 --- a/lib/CodeGen/ControlFlowGen.cc +++ b/lib/CodeGen/ControlFlowGen.cc @@ -216,18 +216,7 @@ void CodeGen::emitCaseStmt(CaseStmtAST *Stmt) { Value *LabelValue = emitExpr(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))) + if (containsString(LabelType) && !isObjectLValue(Label)) materializeOperatorReceiver(LabelValue, LabelType); ResultContext BoolContext{resolveType("BOOLEAN"), true}; auto Overload = resolveOperator(Op, LeftType, LabelType, &BoolContext); diff --git a/lib/CodeGen/ExpressionGen.cc b/lib/CodeGen/ExpressionGen.cc index 463dbed..061bcd2 100644 --- a/lib/CodeGen/ExpressionGen.cc +++ b/lib/CodeGen/ExpressionGen.cc @@ -33,8 +33,7 @@ Value *CodeGen::emitExpr(ExprAST *Expr) { } if (auto *Str = dynamic_cast(Expr)) { - Value *Text = Builder->CreateGlobalString(Str->getVal()); - return Str->isCString() ? Text : emitStringFromCString(Text); + return Builder->CreateGlobalString(Str->getVal()); } if (auto *Var = dynamic_cast(Expr)) { diff --git a/lib/CodeGen/OperatorGen.cc b/lib/CodeGen/OperatorGen.cc index 15667ea..2e0ee3a 100644 --- a/lib/CodeGen/OperatorGen.cc +++ b/lib/CodeGen/OperatorGen.cc @@ -22,7 +22,7 @@ CodeGen::OperatorResolution CodeGen::resolveOperator( // Keep builtin CHAR comparisons ahead of STRING construction. if (Right && Left->Kind == TypeKind::Char && Right->Kind == TypeKind::Char && Op != '&' && Formal && Formal->isString()) return false; - return Lenient && !Param.IsByRef && !Param.IsArray && canImplicitlyConvert(Actual, Formal); + return Lenient && !Param.IsByRef && !Param.IsArray && implicitConversionRank(Actual, Formal) == 1; }; // Builtin operations on exact operand types precede converted overloads. // Raw C text is the exception: visible STRING operators supply text semantics. diff --git a/lib/CodeGen/OutputGen.cc b/lib/CodeGen/OutputGen.cc index faf21df..cecf0b7 100644 --- a/lib/CodeGen/OutputGen.cc +++ b/lib/CodeGen/OutputGen.cc @@ -105,6 +105,19 @@ Value *CodeGen::emitStringNullGuard(Value *StrVal) { void CodeGen::emitOutputValue(Value *Val, const TypeInfo *Info, bool AppendNewline) { if (!Val || !Info) return; + if (Info->CIdentity == "char*") { + const TypeInfo *String = requireStringType(); + const FuncSig *Constructor = findImplicitConstructor(Info, String); + if (!Constructor) { + if (String) reportError("STRING requires an IMPLICIT constructor NEW(Text : C_STRING)"); + return; + } + Value *Converted = emitImplicitConstruct(*Constructor, String, Val); + if (!Converted) return; + materializeOperatorReceiver(Converted, String); + emitOutputValue(Converted, String, AppendNewline); + return; + } if (Info->isString()) { Value *Text = emitStringCString(Val); if (Text) Builder->CreateCall(PrintfFunc, { diff --git a/lib/CodeGen/StringValueGen.cc b/lib/CodeGen/StringValueGen.cc index 989bac8..7a42e49 100644 --- a/lib/CodeGen/StringValueGen.cc +++ b/lib/CodeGen/StringValueGen.cc @@ -35,38 +35,15 @@ 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); + const TypeInfo *Source = getExprTypeInfo(Expr); + Value *Text = emitCoercedExpr(Expr, Type); if (!Text) return nullptr; - if (!isObjectLValue(Expr)) materializeOperatorReceiver(Text, Type); + if (!isObjectLValue(Expr) || !Source || !Source->sameType(Type)) + materializeOperatorReceiver(Text, Type); auto *Receiver = CreateEntryBlockAlloca(Builder->GetInsertBlock()->getParent(), Type->LLVMType, "string_view"); Builder->CreateStore(Text, Receiver); @@ -101,14 +78,16 @@ Value *CodeGen::emitStringCString(Value *Value) { } Value *CodeGen::emitCStringExpr(ExprAST *Expr) { - const TypeInfo *Type = getExprTypeInfo(Expr); - if (!Type || !Type->isString() || isWholeArrayVar(Expr)) { - reportError("Expected a STRING value"); + const TypeInfo *Source = getExprTypeInfo(Expr); + const TypeInfo *Type = requireStringType(); + if (!Type || !Source || isWholeArrayVar(Expr) || + (!Source->sameType(Type) && !findImplicitConstructor(Source, Type))) { + reportError("Expected a STRING value or an IMPLICIT conversion to STRING"); return nullptr; } - Value *Value = emitExpr(Expr); + Value *Value = emitCoercedExpr(Expr, Type); if (!Value) return nullptr; - if (!isObjectLValue(Expr)) materializeOperatorReceiver(Value, Type); + if (!isObjectLValue(Expr) || !Source->sameType(Type)) materializeOperatorReceiver(Value, Type); return emitStringCString(Value); } diff --git a/lib/CodeGen/TypeCheckGen.cc b/lib/CodeGen/TypeCheckGen.cc index b730e93..f253e55 100644 --- a/lib/CodeGen/TypeCheckGen.cc +++ b/lib/CodeGen/TypeCheckGen.cc @@ -16,12 +16,19 @@ const FuncSig *CodeGen::findImplicitConstructor(const TypeInfo *Source, const Ty return nullptr; } -bool CodeGen::canImplicitlyConvert(const TypeInfo *Source, const TypeInfo *Target) { - if (!Source || !Target) return false; - if (Source->sameType(Target)) return true; - if ((Source->Kind == TypeKind::Char && Target->isString()) || - (Source->isString() && Target->Kind == TypeKind::Char)) return true; - return findImplicitConstructor(Source, Target) != nullptr; +// 0: exact; 1: one conversion; 2: legacy raw text -> STRING -> CHAR; +// 3: unavailable. The compatibility bridge must not compete with STRING +// parameters in LCASE, UCASE, ASC and STR_TO_NUM. +int CodeGen::implicitConversionRank(const TypeInfo *Source, const TypeInfo *Target) { + if (!Source || !Target) return 3; + if (Source->sameType(Target)) return 0; + if (findImplicitConstructor(Source, Target)) return 1; + if (Target->Kind == TypeKind::Char) { + if (Source->isString()) return 1; + if (Source->CIdentity == "char*" && + findImplicitConstructor(Source, resolveType("STRING"))) return 2; + } + return 3; } Value *CodeGen::emitImplicitConstruct(const FuncSig &Constructor, const TypeInfo *Target, @@ -63,9 +70,9 @@ Value *CodeGen::emitCoercedExpr(ExprAST *E, const TypeInfo *Target) { // 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) + if (Src && Target->Kind == TypeKind::Char && + (Src->isString() || (Src->CIdentity == "char*" && + findImplicitConstructor(Src, resolveType("STRING"))))) return emitCharFromString(E, E->getLine() ? E->getLine() : DiagnosticLine); if (const FuncSig *Constructor = findImplicitConstructor(Src, Target)) { diff --git a/lib/CodeGen/ValueTypeGen.cc b/lib/CodeGen/ValueTypeGen.cc index 6026821..85dcf13 100644 --- a/lib/CodeGen/ValueTypeGen.cc +++ b/lib/CodeGen/ValueTypeGen.cc @@ -28,8 +28,7 @@ const TypeInfo *CodeGen::getExprTypeInfo(ExprAST *Expr) { if (dynamic_cast(Expr)) return resolveType("REAL"); if (dynamic_cast(Expr)) return resolveType("BOOLEAN"); if (dynamic_cast(Expr)) return resolveType("CHAR"); - if (auto *String = dynamic_cast(Expr)) - return String->isCString() ? resolveType("C_STRING") : requireStringType(); + if (dynamic_cast(Expr)) return resolveType("C_STRING"); if (auto *Cast = dynamic_cast(Expr)) return resolveType(Cast->getTypeName()); -- cgit v1.3.1-4-g156e