diff options
| author | _ov4 <ov4@ov4.org> | 2026-10-30 14:23:32 +0000 |
|---|---|---|
| committer | _ov4 <ov4@ov4.org> | 2026-10-30 14:23:32 +0000 |
| commit | 4cb0d9b196008bc55dbfcdac1f9211dbbc879895 (patch) | |
| tree | 0d95a2682ef26455483c4bb9222e1dff0d5c8e69 | |
| parent | 63109537383fa58c6a1db95f275d6b5de63b0015 (diff) | |
feat(types): support implicit single-argument constructors
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
24 files changed, 440 insertions, 118 deletions
diff --git a/include/cps/CodeGen.h b/include/cps/CodeGen.h index c3a7b19..c0ae997 100644 --- a/include/cps/CodeGen.h +++ b/include/cps/CodeGen.h @@ -148,6 +148,11 @@ class CodeGen { llvm::Value *emitCStringExpr(ExprAST *Expr); llvm::Value *emitFileNameOperand(ExprAST *E, const char *Keyword); bool containsString(const TypeInfo *Type) const; + const FuncSig *findImplicitConstructor(const TypeInfo *Source, const TypeInfo *Target); + bool canImplicitlyConvert(const TypeInfo *Source, const TypeInfo *Target); + llvm::Value *emitImplicitConstruct(const FuncSig &Constructor, const TypeInfo *Target, + llvm::Value *Argument, + const std::vector<size_t> &Transfers = {}); std::unique_ptr<TypeSystem> Types; std::unique_ptr<ArrayHandler> Arrays; @@ -230,7 +235,7 @@ class CodeGen { llvm::Value *materializeOperatorReceiver(llvm::Value *Value, const TypeInfo *Type); llvm::Value *emitOperatorCall(const FuncSig &Signature, ExprAST *Left, ExprAST *Right = nullptr); llvm::Value *emitOperatorValues(const FuncSig &Signature, llvm::Value *Left, - const TypeInfo *LeftType, llvm::Value *Right); + const TypeInfo *LeftType, llvm::Value *Right, const TypeInfo *RightType); llvm::Value *emitVariadicArgument(ExprAST *Expr, const std::string &Callee, size_t Position, const TypeInfo *&Promoted); // Variadic, if given, receives the promoted type of each variadic argument. diff --git a/include/cps/FunctionAST.h b/include/cps/FunctionAST.h index 5280d58..d7fca1a 100644 --- a/include/cps/FunctionAST.h +++ b/include/cps/FunctionAST.h @@ -34,6 +34,7 @@ class PrototypeAST { bool IsOverload; std::string OwnerClass; bool IsPublic; + bool IsImplicit = false; bool IsVariadic = false; bool IsDefaultReturn = false; int Operator = 0; @@ -50,6 +51,8 @@ public: const std::vector<ParamDecl> &getParams() const { return Params; } const std::string &getReturnType() const { return ReturnType; } bool isOverload() const { return IsOverload; } + bool isImplicit() const { return IsImplicit; } + void setImplicit(bool V) { IsImplicit = V; } bool isDefaultReturn() const { return IsDefaultReturn; } void setDefaultReturn(bool V) { IsDefaultReturn = V; } int getOperator() const { return Operator; } diff --git a/include/cps/FunctionGen.h b/include/cps/FunctionGen.h index 5e895fe..741d08b 100644 --- a/include/cps/FunctionGen.h +++ b/include/cps/FunctionGen.h @@ -42,6 +42,7 @@ struct FuncSig { std::string ParamKey; std::string ReturnKey; bool IsOverload = false; + bool IsImplicit = false; bool IsDefaultReturn = false; const PrototypeAST *Prototype = nullptr; std::string ReturnTypeName; diff --git a/include/cps/Lexer.h b/include/cps/Lexer.h index 1268582..9faa09f 100644 --- a/include/cps/Lexer.h +++ b/include/cps/Lexer.h @@ -80,6 +80,7 @@ enum Token { tok_byref = -407, tok_byval = -408, tok_default = -409, + tok_implicit = -410, tok_openfile = -500, tok_readfile = -501, diff --git a/include/cps/Parser.h b/include/cps/Parser.h index e710150..aadf9a4 100644 --- a/include/cps/Parser.h +++ b/include/cps/Parser.h @@ -96,7 +96,7 @@ class Parser { bool ParseRecordField(std::vector<RecordFieldDecl> &Out, bool IsPublic); std::unique_ptr<FunctionDefAST> ParseFunction(const std::string &OwnerClass = {}, bool IsPublic = true, bool IsOverload = false); - std::unique_ptr<FunctionDefAST> ParseProcedure(const std::string &OwnerClass = {}, bool IsPublic = true, bool IsOverload = false); + std::unique_ptr<FunctionDefAST> ParseProcedure(const std::string &OwnerClass = {}, bool IsPublic = true, bool IsOverload = false, bool IsImplicit = false); std::unique_ptr<FunctionDefAST> ParseFunctionBody( std::unique_ptr<PrototypeAST> Proto, int EndToken, const char *EndMessage, int Line); std::unique_ptr<StmtAST> ParseExternDecl(); diff --git a/lib/CodeGen/CallGen.cc b/lib/CodeGen/CallGen.cc index 0741763..f5f1eda 100644 --- a/lib/CodeGen/CallGen.cc +++ b/lib/CodeGen/CallGen.cc @@ -210,10 +210,9 @@ const FuncSig *CodeGen::resolveCall(const std::string &Name, const TypeInfo *Ret = resolveType(Sig->ReturnTypeName); return Ret && Ret->sameType(Context->Type); }; - auto Scalar = [](const TypeInfo *T) { return T && (T->Kind == TypeKind::Char || T->isString()); }; bool ParamMatched = false; int Count = 0; - // One layer of parameter matching. Lenient adds CHAR/STRING conversions; + // 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 * { const FuncSig *First = nullptr; @@ -230,7 +229,7 @@ const FuncSig *CodeGen::resolveCall(const std::string &Name, bool Same = SameRank && Actual[I].Type && Actual[I].Type->sameType(Formal); Match = Same || (Lenient && SameRank && Actual[I].Type && !P.IsByRef && !P.IsArray && - Scalar(Actual[I].Type) && Scalar(Formal)); + canImplicitlyConvert(Actual[I].Type, Formal)); } if (!Match) continue; ParamMatched = true; @@ -254,106 +253,113 @@ const FuncSig *CodeGen::resolveCall(const std::string &Name, reportError("Ambiguous call to %s(%s); the return type cannot be determined from context", Name.c_str(), Key.c_str()); }; - // Exact parameters first, then CHAR/STRING conversions. bool Ambiguous = false; + bool AllowConversions = false; auto Try = [&](bool FilterReturn, const FuncSig *&Selected) { - Selected = Scan(false, FilterReturn); - if (Count > 1) { - ReportAmbiguous(FilterReturn); - Ambiguous = true; - return; - } - if (Selected || HasTemplates || HasExplicit) return; - Selected = Scan(true, FilterReturn); + Selected = Scan(AllowConversions, FilterReturn); if (Count > 1) { ReportAmbiguous(FilterReturn); Ambiguous = true; } }; - const FuncSig *Selected = nullptr; - if (PreferResult) { - Try(true, Selected); - if (Ambiguous) return nullptr; - if (Selected) return Selected; - } - if (!Context || !Context->Exact) { - Try(false, Selected); - if (Ambiguous) return nullptr; - if (Selected) return Selected; - } - if (HasTemplates) { - // A matching non-variadic template wins before any variadic candidate. - for (bool Variadic : {false, true}) { - const TemplateDefinition *Selected = nullptr; - detail::TemplateBindings SelectedBindings; - for (const auto &Definition : Templates.at(Name)) { - auto *Function = Definition.Decl->getFunction(); - if (!Function || Definition.Decl->isVariadic() != Variadic || !isVisible(Definition.OwnerUnit)) continue; - const auto &Params = Function->getProto()->getParams(); - const auto &TemplateParams = Definition.Decl->getParams(); - bool HasPackParam = !Params.empty() && Params.back().IsPack; - size_t FixedCount = Params.size() - HasPackParam; - if (HasPackParam ? Actual.size() < FixedCount : Actual.size() != FixedCount) continue; - detail::TemplateBindings Bindings; - for (const auto &Param : TemplateParams) Bindings.emplace(Param.Name, std::vector<std::string>{}); - SaveAndRestore<bool> Speculation(SpeculativeTemplateResolution, true); - bool Match = true; - if (HasExplicit) { - size_t FixedTypes = TemplateParams.size() - Variadic; - if (!Variadic && Explicit->size() > FixedTypes) continue; - for (size_t I = 0; I < Explicit->size(); ++I) { - auto Type = canonicalTemplateArgument((*Explicit)[I]); - if (!Type) { Match = false; break; } - Bindings.at(TemplateParams[std::min(I, FixedTypes)].Name).push_back(*Type); - } - } - if (!Match) continue; - for (size_t I = 0; I < FixedCount; ++I) { - const auto &P = Params[I]; - const TypeInfo *Type = Actual[I].Type; - if (!Type || Type->isVoid() || Actual[I].Rank != (P.IsArray ? P.Rank : 0)) { Match = false; break; } - auto Binding = Bindings.find(P.TypeName); - if (Binding != Bindings.end()) { - if (Binding->second.empty()) Binding->second.push_back(Type->Name); - else if (!Type->sameType(resolveType(Binding->second.front()))) { Match = false; break; } + // Exhaust exact ordinary/template candidates before considering conversions. + for (bool ConversionLayer : {false, true}) { + AllowConversions = ConversionLayer; + const FuncSig *Selected = nullptr; + if (PreferResult) { + Try(true, Selected); + if (Ambiguous) return nullptr; + if (Selected) return Selected; + } + if (!Context || !Context->Exact) { + Try(false, Selected); + if (Ambiguous) return nullptr; + if (Selected) return Selected; + } + if (HasTemplates) { + // A matching non-variadic template wins before any variadic candidate. + for (bool Variadic : {false, true}) { + const TemplateDefinition *Selected = nullptr; + detail::TemplateBindings SelectedBindings; + for (const auto &Definition : Templates.at(Name)) { + auto *Function = Definition.Decl->getFunction(); + if (!Function || Definition.Decl->isVariadic() != Variadic || !isVisible(Definition.OwnerUnit)) continue; + const auto &Params = Function->getProto()->getParams(); + const auto &TemplateParams = Definition.Decl->getParams(); + bool HasPackParam = !Params.empty() && Params.back().IsPack; + size_t FixedCount = Params.size() - HasPackParam; + if (HasPackParam ? Actual.size() < FixedCount : Actual.size() != FixedCount) continue; + detail::TemplateBindings Bindings; + for (const auto &Param : TemplateParams) Bindings.emplace(Param.Name, std::vector<std::string>{}); + SaveAndRestore<bool> Speculation(SpeculativeTemplateResolution, true); + bool Match = true; + if (HasExplicit) { + size_t FixedTypes = TemplateParams.size() - Variadic; + if (!Variadic && Explicit->size() > FixedTypes) continue; + for (size_t I = 0; I < Explicit->size(); ++I) { + auto Type = canonicalTemplateArgument((*Explicit)[I]); + if (!Type) { Match = false; break; } + Bindings.at(TemplateParams[std::min(I, FixedTypes)].Name).push_back(*Type); + } } - } - if (HasPackParam) { - auto &Pack = Bindings.at(Params.back().TypeName); - // Explicit pack types constrain a prefix; deduction fills the rest. - if (Pack.size() > Actual.size() - FixedCount) Match = false; - for (size_t I = FixedCount; Match && I < Actual.size(); ++I) { + if (!Match) continue; + for (size_t I = 0; I < FixedCount; ++I) { + const auto &P = Params[I]; const TypeInfo *Type = Actual[I].Type; - size_t Index = I - FixedCount; - if (!Type || Type->isVoid() || Actual[I].Rank) { Match = false; break; } - if (Index < Pack.size()) { - if (!Type->sameType(resolveType(Pack[Index]))) Match = false; - } else Pack.push_back(Type->Name); + if (!Type || Type->isVoid() || Actual[I].Rank != (P.IsArray ? P.Rank : 0)) { Match = false; break; } + auto Binding = Bindings.find(P.TypeName); + 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())))) { + Match = false; break; + } + } } + if (HasPackParam) { + auto &Pack = Bindings.at(Params.back().TypeName); + // Explicit pack types constrain a prefix; deduction fills the rest. + if (Pack.size() > Actual.size() - FixedCount) Match = false; + for (size_t I = FixedCount; Match && I < Actual.size(); ++I) { + const TypeInfo *Type = Actual[I].Type; + size_t Index = I - FixedCount; + 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; + } else Pack.push_back(Type->Name); + } + } + for (const auto &Param : TemplateParams) + if (!Param.IsPack && Bindings.at(Param.Name).empty()) Match = false; + if (!Match) continue; + // Composite parameter types can use bindings deduced at later positions, + // but do not themselves contribute deductions. + detail::TemplateClone Substitute(Bindings, [&](int, const std::string &) { Match = false; }); + for (size_t I = 0; I < FixedCount; ++I) { + const auto &TypeName = Params[I].TypeName; + auto Binding = Bindings.find(TypeName); + 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))))) { + Match = false; break; + } + } + if (!Match) continue; + if (Selected) { + reportError("Ambiguous call to %s(%s)", Name.c_str(), Key.c_str()); + return nullptr; + } + Selected = &Definition; + SelectedBindings = std::move(Bindings); } - for (const auto &Param : TemplateParams) - if (!Param.IsPack && Bindings.at(Param.Name).empty()) Match = false; - if (!Match) continue; - // Composite parameter types can use bindings deduced at later positions, - // but do not themselves contribute deductions. - detail::TemplateClone Substitute(Bindings, [&](int, const std::string &) { Match = false; }); - for (size_t I = 0; I < FixedCount; ++I) { - const auto &TypeName = Params[I].TypeName; - auto Binding = Bindings.find(TypeName); - 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))) { Match = false; break; } - } - if (!Match) continue; - if (Selected) { - reportError("Ambiguous call to %s(%s)", Name.c_str(), Key.c_str()); - return nullptr; - } - Selected = &Definition; - SelectedBindings = std::move(Bindings); + if (Selected) return instantiateFunction(*Selected, SelectedBindings); } - if (Selected) return instantiateFunction(*Selected, SelectedBindings); } } if (Context && Context->Exact && ParamMatched) { diff --git a/lib/CodeGen/ConstructorGen.cc b/lib/CodeGen/ConstructorGen.cc index da89f59..81c448f 100644 --- a/lib/CodeGen/ConstructorGen.cc +++ b/lib/CodeGen/ConstructorGen.cc @@ -24,9 +24,9 @@ Value *CodeGen::emitOwnedValue(ExprAST *Expr, const TypeInfo *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 || (!Source->sameType(Type) && !FromChar)) return emitCoercedExpr(Expr, Type); + if (!Source) return emitCoercedExpr(Expr, Type); Value *Value = nullptr; - if (!FromChar && isObjectLValue(Expr)) { + if (!FromChar && Source->sameType(Type) && isObjectLValue(Expr)) { LValueInfo LV; const auto *Var = dynamic_cast<VariableExprAST *>(Expr); const auto *Symbol = Var ? getSymbolInfo(Var->getName()) : nullptr; @@ -241,7 +241,14 @@ void CodeGen::emitAssignment(const LValueInfo &Target, ExprAST *Expr, const Symb return; } Value *Val = emitCoercedExpr(Expr, Info); - if (Val) Builder->CreateStore(Val, Target.Addr); + if (Val && Info && Info->IsClass && ExprType && !ExprType->sameType(Info)) { + materializeOperatorReceiver(Val, Info); + std::vector<size_t> Transfers; + if (typeNeedsDestruction(Info->Name)) Transfers.push_back(StackObjects.size() - 1); + if (!emitDestroy(Info->Name, Target.Addr, DiagnosticLine)) return; + Builder->CreateStore(Val, Target.Addr); + transferObjects(Transfers); + } else if (Val) Builder->CreateStore(Val, Target.Addr); } bool CodeGen::typeNeedsDestruction(const std::string &TypeName) const { diff --git a/lib/CodeGen/ExpressionGen.cc b/lib/CodeGen/ExpressionGen.cc index 9f68a82..463dbed 100644 --- a/lib/CodeGen/ExpressionGen.cc +++ b/lib/CodeGen/ExpressionGen.cc @@ -249,7 +249,7 @@ Value *CodeGen::emitBinaryOp(int Op, Value *L, const TypeInfo *LT, LT->Name.c_str(), RT->Name.c_str(), Context->Type->Name.c_str()); return nullptr; } - if (Overload.Signature) return emitOperatorValues(*Overload.Signature, L, LT, R); + if (Overload.Signature) return emitOperatorValues(*Overload.Signature, L, LT, R, RT); if ((LT && LT->isC()) || (RT && RT->isC())) return emitCBinaryOp(Op, L, LT, R, RT, Line); if ((LT && LT->isUserKind()) || (RT && RT->isUserKind())) diff --git a/lib/CodeGen/FunctionGen.cc b/lib/CodeGen/FunctionGen.cc index a0c520d..7aa5d46 100644 --- a/lib/CodeGen/FunctionGen.cc +++ b/lib/CodeGen/FunctionGen.cc @@ -130,6 +130,15 @@ const FuncSig *FunctionGen::selectDefaultReturn(const std::vector<const FuncSig // Copy-constructor, operator, MAIN and name-collision rules for a // FUNCTION/PROCEDURE prototype; reports the first violation. bool FunctionGen::validatePrototype(PrototypeAST *Proto) { + if (Proto->isImplicit()) { + const auto &P = Proto->getParams(); + if (Proto->getOwnerClass().empty() || !sameIdentifier(Proto->getName(), "NEW") || + !sameIdentifier(Proto->getReturnType(), "VOID") || P.size() != 1 || + P[0].Mode != PassMode::ByVal || P[0].IsArray || P[0].IsPack) { + reportError("IMPLICIT requires a CLASS constructor NEW with one BYVAL non-array parameter"); + return false; + } + } if (Proto->isDefaultReturn()) { const TypeInfo *Return = Types.resolve(Proto->getReturnType()); if (!Proto->isOverload() || (Return && Return->isVoid())) { @@ -213,6 +222,7 @@ Function *FunctionGen::emitPrototype(PrototypeAST *Proto) { Sig.OwnerUnit = CurrentUnit; Sig.Name = Proto->getLinkName(); Sig.IsOverload = Proto->isOverload(); + Sig.IsImplicit = Proto->isImplicit(); Sig.IsDefaultReturn = Proto->isDefaultReturn(); Sig.Prototype = Proto; Sig.ReturnTypeName = Proto->getReturnType(); diff --git a/lib/CodeGen/OperatorGen.cc b/lib/CodeGen/OperatorGen.cc index 7cfac00..15667ea 100644 --- a/lib/CodeGen/OperatorGen.cc +++ b/lib/CodeGen/OperatorGen.cc @@ -16,15 +16,39 @@ CodeGen::OperatorResolution CodeGen::resolveOperator( auto Members = FuncGen->getOverloads(Left->Name + "." + operatorName(Op)); Candidates.insert(Candidates.end(), Members.begin(), Members.end()); } - // 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); + auto Accepts = [&](const TypeInfo *Actual, const ParamSig &Param, bool Lenient) { + const TypeInfo *Formal = resolveType(Param.TypeName); if (Actual->sameType(Formal)) return true; - return Lenient && Actual->Kind == TypeKind::Char && Formal && Formal->isString(); + // 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); }; - // Two CHARs compare as CHARs; only '&' always builds a STRING. - bool CanConvert = Right && (Op == '&' || Left->isString() || Right->isString()); + // Builtin operations on exact operand types precede converted overloads. + // Raw C text is the exception: visible STRING operators supply text semantics. + auto Numeric = [](const TypeInfo *T) { + return T && (T->Kind == TypeKind::Integer || T->Kind == TypeKind::Real); + }; + bool Comparison = Op == tok_eq || Op == tok_ne || Op == '<' || Op == '>' || + Op == tok_le || Op == tok_ge; + bool Builtin = false; + if (!Right) { + Builtin = (Op == '-' && Numeric(Left)) || + (Op == tok_not && Left->Kind == TypeKind::Boolean) || + Left->Kind == TypeKind::CScalar; + } else { + Builtin = (Numeric(Left) && Numeric(Right) && + (Comparison || Op == '+' || Op == '-' || Op == '*' || Op == '/' || + Op == tok_div || Op == tok_mod)) || + (Left->sameType(Right) && + ((Comparison && (Left->Kind == TypeKind::Char || + Left->Kind == TypeKind::Boolean || Left->isEnum())) || + ((Op == tok_and || Op == tok_or) && Left->Kind == TypeKind::Boolean) || + ((Op == tok_eq || Op == tok_ne) && Left->isPointer() && + Left->CIdentity != "char*"))) || + (Left->Kind == TypeKind::CScalar && Right->Kind == TypeKind::CScalar) || + (Left->isEnum() && Right->Kind == TypeKind::Integer && (Op == '+' || Op == '-')); + } const bool PreferResult = Context && Context->Type; auto ReturnsWanted = [&](const FuncSig *Sig) { const TypeInfo *Ret = resolveType(Sig->ReturnTypeName); @@ -35,7 +59,7 @@ CodeGen::OperatorResolution CodeGen::resolveOperator( Result.Signature = nullptr; Result.Ambiguous = false; for (bool Lenient : {false, true}) { - if (Lenient && !CanConvert) break; + if (Lenient && Builtin) break; const FuncSig *Selected = nullptr; int Count = 0; std::vector<const FuncSig *> Matches; @@ -43,8 +67,8 @@ CodeGen::OperatorResolution CodeGen::resolveOperator( 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 (!Member && !Accepts(Left, Sig->Params[0], Lenient)) continue; + if (Right && !Accepts(Right, Sig->Params[Member ? 0 : 1], Lenient)) continue; ParamMatched = true; if (FilterReturn && !ReturnsWanted(Sig)) continue; ++Count; @@ -125,7 +149,7 @@ Value *CodeGen::emitOperatorCall(const FuncSig &Signature, ExprAST *Left, ExprAS // CASE has already evaluated and captured its selector and label values. Value *CodeGen::emitOperatorValues(const FuncSig &Signature, Value *Left, - const TypeInfo *LeftType, Value *Right) { + const TypeInfo *LeftType, Value *Right, const TypeInfo *ActualRightType) { if (!checkOperatorAccess(Signature)) return nullptr; bool Member = !Signature.Prototype->getOwnerClass().empty(); std::vector<Value *> Args; @@ -136,16 +160,31 @@ Value *CodeGen::emitOperatorValues(const FuncSig &Signature, Value *Left, Builder->CreateStore(Val, Slot); return Slot; }; + auto Prepare = [&](Value *Val, const TypeInfo *Actual, const TypeInfo *Formal) -> Value * { + if (!Val || !Actual || !Formal) return nullptr; + if (!Actual->sameType(Formal)) { + const FuncSig *Constructor = findImplicitConstructor(Actual, Formal); + if (!Constructor) return nullptr; + std::vector<size_t> SourceTransfers; + if (Actual->IsClass || containsString(Actual)) + Val = copyClassValue(Actual, Capture(Val, Actual), SourceTransfers); + Val = emitImplicitConstruct(*Constructor, Formal, Val, SourceTransfers); + if (!Val) return nullptr; + materializeOperatorReceiver(Val, Formal); + if (typeNeedsDestruction(Formal->Name)) Transfers.push_back(StackObjects.size() - 1); + } else if (Formal->IsClass || containsString(Formal)) { + Val = copyClassValue(Formal, Capture(Val, Formal), Transfers); + } + return Val; + }; if (Member) Args.push_back(Capture(Left, LeftType)); else { - if (LeftType->IsClass || containsString(LeftType)) - Left = copyClassValue(LeftType, Capture(Left, LeftType), Transfers); + Left = Prepare(Left, LeftType, resolveType(Signature.Params[0].TypeName)); if (!Left) return nullptr; Args.push_back(Left); } const TypeInfo *RightType = resolveType(Signature.Params[Member ? 0 : 1].TypeName); - if (RightType->IsClass || containsString(RightType)) - Right = copyClassValue(RightType, Capture(Right, RightType), Transfers); + Right = Prepare(Right, ActualRightType, RightType); if (!Right) return nullptr; Args.push_back(Right); transferObjects(Transfers); diff --git a/lib/CodeGen/TemplateClone.cc b/lib/CodeGen/TemplateClone.cc index b0aaf0c..2d8b952 100644 --- a/lib/CodeGen/TemplateClone.cc +++ b/lib/CodeGen/TemplateClone.cc @@ -221,6 +221,7 @@ std::unique_ptr<FunctionDefAST> TemplateClone::function(const FunctionDefAST *So Proto->isExternal(), OwnerClass, Proto->isPublic(), Proto->isOverload()); CopyProto->setOperator(Proto->getOperator()); CopyProto->setDefaultReturn(Proto->isDefaultReturn()); + CopyProto->setImplicit(Proto->isImplicit()); CopyProto->setVariadic(Proto->isVariadic()); auto Copy = located(std::make_unique<FunctionDefAST>(std::move(CopyProto), body(Source->getBody())), Source); PackNames = std::move(SavedNames); diff --git a/lib/CodeGen/TypeCheckGen.cc b/lib/CodeGen/TypeCheckGen.cc index 6e9cac2..b730e93 100644 --- a/lib/CodeGen/TypeCheckGen.cc +++ b/lib/CodeGen/TypeCheckGen.cc @@ -7,6 +7,36 @@ using namespace llvm; using namespace cps; +// Candidate queries neither evaluate the source nor follow conversion chains. +const FuncSig *CodeGen::findImplicitConstructor(const TypeInfo *Source, const TypeInfo *Target) { + if (!Source || !Target || !Target->IsClass || Source->sameType(Target)) return nullptr; + for (const FuncSig *Sig : FuncGen->getOverloads(Target->Name + ".NEW")) + if (Sig->IsImplicit && Sig->Params.size() == 1 && + Source->sameType(resolveType(Sig->Params[0].TypeName))) return Sig; + 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; +} + +Value *CodeGen::emitImplicitConstruct(const FuncSig &Constructor, const TypeInfo *Target, + Value *Argument, const std::vector<size_t> &Transfers) { + if (!Argument || !checkOperatorAccess(Constructor)) return nullptr; + auto *Storage = CreateEntryBlockAlloca(Builder->GetInsertBlock()->getParent(), + Target->LLVMType, "implicit_object"); + Builder->CreateStore(Types->getZeroValue(Target->Name), Storage); + if (!emitFieldDefaults(Target, Storage, DiagnosticLine)) return nullptr; + transferObjects(Transfers); + if (!FuncGen->emitCall(Constructor, {Storage, Argument})) return nullptr; + // Like a function result, the value is owned by its consumer. + return Builder->CreateLoad(Target->LLVMType, Storage, "implicit_value"); +} + // The single name-level admission gate. Targets of a user kind demand exact // type-name equality (their representations are indistinguishable at the // LLVM level); builtin targets additionally reject user-kind sources, then @@ -38,6 +68,14 @@ Value *CodeGen::emitCoercedExpr(ExprAST *E, const TypeInfo *Target) { if (Src && Src->isString() && Target->Kind == TypeKind::Char) return emitCharFromString(E, E->getLine() ? E->getLine() : DiagnosticLine); + if (const FuncSig *Constructor = findImplicitConstructor(Src, Target)) { + if (!checkOperatorAccess(*Constructor)) return nullptr; + std::vector<size_t> Transfers; + Value *Argument = emitOwnedValue(E, Src, Transfers); + if (!Argument) return nullptr; + return emitImplicitConstruct(*Constructor, Target, Argument, Transfers); + } + if (Target->isC() || (Src && Src->isC())) { if (auto *Addr = dynamic_cast<AddrOfExprAST*>(E)) { const PointerPayload *PP = Target->asPointer(); diff --git a/lib/Lexer/Lexer.cc b/lib/Lexer/Lexer.cc index e75cbba..bc9b4f3 100644 --- a/lib/Lexer/Lexer.cc +++ b/lib/Lexer/Lexer.cc @@ -70,6 +70,7 @@ const std::unordered_map<std::string, int> Keywords = { {"NOT", tok_not}, {"OPERATOR", tok_operator}, {"OVERLOAD", tok_overload}, + {"IMPLICIT", tok_implicit}, {"TEMPLATE", tok_template}, {"FUNCTION", tok_function}, {"ENDFUNCTION", tok_endfunction}, diff --git a/lib/Parser/Parser.cc b/lib/Parser/Parser.cc index ad2e408..227e309 100644 --- a/lib/Parser/Parser.cc +++ b/lib/Parser/Parser.cc @@ -50,7 +50,7 @@ void Parser::syncToStatementStart() { case tok_delete: case tok_constant: case tok_declare: case tok_identifier: case tok_operator: case tok_input: case tok_output: case tok_case: case tok_endcase: case tok_otherwise: case tok_if: case tok_while: case tok_repeat: case tok_for: - case tok_template: case tok_overload: case tok_function: case tok_procedure: case tok_call: case tok_return: + case tok_template: case tok_implicit: case tok_overload: case tok_function: case tok_procedure: case tok_call: case tok_return: case tok_else: case tok_endif: case tok_endwhile: case tok_until: case tok_next: case tok_endfunction: case tok_endprocedure: case tok_openfile: case tok_readfile: case tok_writefile: case tok_closefile: @@ -157,9 +157,16 @@ std::unique_ptr<StmtAST> Parser::ParseStatementImpl() { else if (CurTok == tok_for) { return ParseForStmt(); } - else if (CurTok == tok_overload) { + else if (CurTok == tok_implicit || CurTok == tok_overload) { int Line = Lex.getLine(); + bool Overload = CurTok == tok_overload; getNextToken(); + bool Implicit = !Overload || CurTok == tok_implicit; + if (Overload && Implicit) getNextToken(); + if (Implicit) { + cps::sourceError(Line, "IMPLICIT is only allowed on a CLASS constructor NEW"); + return nullptr; + } if (CurTok == tok_function) return ParseFunction({}, true, true); if (CurTok == tok_procedure) return ParseProcedure({}, true, true); cps::sourceError(Line, "OVERLOAD must be followed by FUNCTION or PROCEDURE"); diff --git a/lib/Parser/ParserFunction.cc b/lib/Parser/ParserFunction.cc index 2f80c42..27be388 100644 --- a/lib/Parser/ParserFunction.cc +++ b/lib/Parser/ParserFunction.cc @@ -233,7 +233,7 @@ std::unique_ptr<FunctionDefAST> Parser::ParseFunction(const std::string &OwnerCl return Def; } -std::unique_ptr<FunctionDefAST> Parser::ParseProcedure(const std::string &OwnerClass, bool IsPublic, bool IsOverload) { +std::unique_ptr<FunctionDefAST> Parser::ParseProcedure(const std::string &OwnerClass, bool IsPublic, bool IsOverload, bool IsImplicit) { int Line = Lex.getLine(); getNextToken(); if (CurTok != tok_identifier && CurTok != tok_new && CurTok != tok_delete && CurTok != tok_operator) { @@ -265,12 +265,19 @@ std::unique_ptr<FunctionDefAST> Parser::ParseProcedure(const std::string &OwnerC auto Args = ParsePrototypeArgs(/*AllowByRef=*/true); if (!Args) return nullptr; + if (IsImplicit) { + for (const auto &P : *Args) if (P.IsPack) { + cps::sourceError(Line, "IMPLICIT constructor parameters cannot be packs"); + return nullptr; + } + } if (sameIdentifier(Name, "DELETE") && !Args->empty()) { cps::sourceError(Line, "destructor DELETE must have no parameters"); return nullptr; } auto Proto = std::make_unique<PrototypeAST>(Name, std::move(*Args), "VOID", false, OwnerClass, IsPublic, IsOverload); Proto->setOperator(Operator); + Proto->setImplicit(IsImplicit); return ParseFunctionBody(std::move(Proto), tok_endprocedure, "expected ENDPROCEDURE", Line); } diff --git a/lib/Parser/ParserType.cc b/lib/Parser/ParserType.cc index 976d165..df1fc8f 100644 --- a/lib/Parser/ParserType.cc +++ b/lib/Parser/ParserType.cc @@ -116,7 +116,7 @@ std::optional<std::vector<RecordFieldDecl>> Parser::ParseRecordBody( const char *EndKeyword = IsClass ? "ENDCLASS" : "ENDTYPE"; auto AtField = [&] { return IsClass ? (CurTok == tok_public || CurTok == tok_private || CurTok == tok_identifier || - CurTok == tok_overload || CurTok == tok_function || CurTok == tok_procedure) + CurTok == tok_implicit || CurTok == tok_overload || CurTok == tok_function || CurTok == tok_procedure) : CurTok == tok_declare; }; std::vector<RecordFieldDecl> Fields; @@ -135,18 +135,25 @@ std::optional<std::vector<RecordFieldDecl>> Parser::ParseRecordBody( // Class fields default to public, like record fields. bool IsPublic = CurTok != tok_private; if (IsClass && (CurTok == tok_public || CurTok == tok_private) && - (Lex.peekToken() == tok_overload || Lex.peekToken() == tok_function || Lex.peekToken() == tok_procedure)) + (Lex.peekToken() == tok_implicit || Lex.peekToken() == tok_overload || Lex.peekToken() == tok_function || Lex.peekToken() == tok_procedure)) getNextToken(); - if (IsClass && (CurTok == tok_overload || CurTok == tok_function || CurTok == tok_procedure)) { + if (IsClass && (CurTok == tok_implicit || CurTok == tok_overload || CurTok == tok_function || CurTok == tok_procedure)) { bool IsOverload = CurTok == tok_overload; if (IsOverload) getNextToken(); + bool IsImplicit = CurTok == tok_implicit; + if (IsImplicit) getNextToken(); + if (IsImplicit && CurTok != tok_procedure) { + cps::sourceError(Lex.getLine(), "IMPLICIT requires PROCEDURE NEW"); + HadFieldError = true; + continue; + } if (CurTok != tok_function && CurTok != tok_procedure) { cps::sourceError(Lex.getLine(), "OVERLOAD must be followed by FUNCTION or PROCEDURE"); HadFieldError = true; continue; } auto Method = CurTok == tok_function ? ParseFunction(Name, IsPublic, IsOverload) - : ParseProcedure(Name, IsPublic, IsOverload); + : ParseProcedure(Name, IsPublic, IsOverload, IsImplicit); if (Method && Methods) { Methods->push_back(std::move(Method)); continue; diff --git a/tests/i/implicit-constructors/case.json b/tests/i/implicit-constructors/case.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/tests/i/implicit-constructors/case.json @@ -0,0 +1 @@ +{} diff --git a/tests/i/implicit-constructors/expected.txt b/tests/i/implicit-constructors/expected.txt new file mode 100644 index 0000000..d360ce8 --- /dev/null +++ b/tests/i/implicit-constructors/expected.txt @@ -0,0 +1,5 @@ +7:8:201:6:9 +9 +TRUE +0 +0 diff --git a/tests/i/implicit-constructors/notes.md b/tests/i/implicit-constructors/notes.md new file mode 100644 index 0000000..cdcab87 --- /dev/null +++ b/tests/i/implicit-constructors/notes.md @@ -0,0 +1,3 @@ +# Implicit constructors + +Pending human review. Box counts constructions, copies and destruction. Assignments, BYVAL arguments, RETURN, operators and CASE use its INTEGER conversion. Pick selects its exact INTEGER overload. Exercise releases all objects; a later failing argument releases the pending converted Box before CATCH. diff --git a/tests/i/implicit-constructors/program.cps b/tests/i/implicit-constructors/program.cps new file mode 100644 index 0000000..d13236e --- /dev/null +++ b/tests/i/implicit-constructors/program.cps @@ -0,0 +1,63 @@ +GLOBAL + DECLARE Alive : INTEGER +ENDGLOBAL +CLASS Box + Value : INTEGER + OVERLOAD PROCEDURE NEW() + Alive <- Alive + 1 + ENDPROCEDURE + OVERLOAD IMPLICIT PROCEDURE NEW(Number : INTEGER) + Value <- Number + Alive <- Alive + 1 + ENDPROCEDURE + OVERLOAD PROCEDURE NEW(BYREF Other : Box) + Value <- Other.Value + Alive <- Alive + 1 + ENDPROCEDURE + PROCEDURE DELETE() + Alive <- Alive - 1 + ENDPROCEDURE +ENDCLASS +FUNCTION Read(Value : Box) RETURNS INTEGER + RETURN Value.Value +ENDFUNCTION +OVERLOAD FUNCTION Pick(Value : Box) RETURNS INTEGER + RETURN 100 + Value.Value +ENDFUNCTION +OVERLOAD FUNCTION Pick(Value : INTEGER) RETURNS INTEGER + RETURN 200 + Value +ENDFUNCTION +OVERLOAD FUNCTION Only(Value : Box) RETURNS INTEGER + RETURN Value.Value +ENDFUNCTION +OVERLOAD FUNCTION OPERATOR+(A : Box, B : Box) RETURNS INTEGER + RETURN A.Value + B.Value +ENDFUNCTION +OVERLOAD FUNCTION OPERATOR=(A : Box, B : Box) RETURNS BOOLEAN + RETURN A.Value = B.Value +ENDFUNCTION +FUNCTION Make() RETURNS Box + RETURN 9 +ENDFUNCTION +PROCEDURE Exercise() + DECLARE B : Box + B <- 7 + OUTPUT B.Value, ":", Read(8), ":", Pick(1), ":", Only(6), ":", B + 2 + OUTPUT Read(Make()) + CASE OF B + 7 : OUTPUT TRUE + OTHERWISE : OUTPUT FALSE + ENDCASE +ENDPROCEDURE +FUNCTION Fail() RETURNS INTEGER + THROW +ENDFUNCTION +PROCEDURE Pair(Value : Box, Number : INTEGER) +ENDPROCEDURE +CALL Exercise() +OUTPUT Alive +TRY + CALL Pair(1, Fail()) +CATCH + OUTPUT Alive +ENDTRY diff --git a/tests/iii/implicit-conversion-errors/case.json b/tests/iii/implicit-conversion-errors/case.json new file mode 100644 index 0000000..a004af2 --- /dev/null +++ b/tests/iii/implicit-conversion-errors/case.json @@ -0,0 +1,24 @@ +{ + "errors": [ + { + "line": 28, + "reason": "Cannot use a value of type INTEGER where ExplicitOnly is expected" + }, + { + "line": 29, + "reason": "Cannot use a value of type INTEGER where Second is expected" + }, + { + "line": 30, + "reason": "BYREF argument I to Ref must be of type First" + }, + { + "line": 41, + "reason": "Ambiguous call to Ambiguous" + }, + { + "line": 48, + "reason": "Cannot access private method NEW" + } + ] +} diff --git a/tests/iii/implicit-conversion-errors/program.cps b/tests/iii/implicit-conversion-errors/program.cps new file mode 100644 index 0000000..6297b16 --- /dev/null +++ b/tests/iii/implicit-conversion-errors/program.cps @@ -0,0 +1,48 @@ +CLASS ExplicitOnly + Value : INTEGER + PROCEDURE NEW(Number : INTEGER) + Value <- Number + ENDPROCEDURE +ENDCLASS +CLASS First + Value : INTEGER + IMPLICIT PROCEDURE NEW(Number : INTEGER) + Value <- Number + ENDPROCEDURE +ENDCLASS +CLASS Second + Value : INTEGER + IMPLICIT PROCEDURE NEW(Other : First) + Value <- Other.Value + ENDPROCEDURE +ENDCLASS +OVERLOAD PROCEDURE Choose(Value : First) +ENDPROCEDURE +OVERLOAD PROCEDURE Choose(Value : Second) +ENDPROCEDURE +PROCEDURE Ref(BYREF Value : First) +ENDPROCEDURE +DECLARE E : ExplicitOnly(1) +DECLARE S : Second +DECLARE I : INTEGER +E <- 1 +S <- 1 +CALL Ref(I) +CLASS Alternative + Value : INTEGER + IMPLICIT PROCEDURE NEW(Number : INTEGER) + Value <- Number + ENDPROCEDURE +ENDCLASS +OVERLOAD PROCEDURE Ambiguous(Value : First) +ENDPROCEDURE +OVERLOAD PROCEDURE Ambiguous(Value : Alternative) +ENDPROCEDURE +CALL Ambiguous(1) +CLASS Hidden + PRIVATE IMPLICIT PROCEDURE NEW(Number : INTEGER) + ENDPROCEDURE +ENDCLASS +PROCEDURE HiddenArg(Value : Hidden) +ENDPROCEDURE +CALL HiddenArg(1) diff --git a/tests/iii/implicit-declaration-errors/case.json b/tests/iii/implicit-declaration-errors/case.json new file mode 100644 index 0000000..3968b70 --- /dev/null +++ b/tests/iii/implicit-declaration-errors/case.json @@ -0,0 +1,24 @@ +{ + "errors": [ + { + "line": 2, + "reason": "IMPLICIT requires a CLASS constructor NEW with one BYVAL non-array parameter" + }, + { + "line": 6, + "reason": "IMPLICIT requires a CLASS constructor NEW with one BYVAL non-array parameter" + }, + { + "line": 10, + "reason": "IMPLICIT requires a CLASS constructor NEW with one BYVAL non-array parameter" + }, + { + "line": 14, + "reason": "IMPLICIT requires a CLASS constructor NEW with one BYVAL non-array parameter" + }, + { + "line": 18, + "reason": "IMPLICIT requires a CLASS constructor NEW with one BYVAL non-array parameter" + } + ] +} diff --git a/tests/iii/implicit-declaration-errors/program.cps b/tests/iii/implicit-declaration-errors/program.cps new file mode 100644 index 0000000..31cd23c --- /dev/null +++ b/tests/iii/implicit-declaration-errors/program.cps @@ -0,0 +1,21 @@ +CLASS Invalid + IMPLICIT PROCEDURE NEW() + ENDPROCEDURE +ENDCLASS +CLASS RefInvalid + IMPLICIT PROCEDURE NEW(BYREF Value : INTEGER) + ENDPROCEDURE +ENDCLASS +CLASS ArrayInvalid + IMPLICIT PROCEDURE NEW(Value : ARRAY OF INTEGER) + ENDPROCEDURE +ENDCLASS +CLASS MethodInvalid + IMPLICIT PROCEDURE Convert(Value : INTEGER) + ENDPROCEDURE +ENDCLASS +CLASS PairInvalid + IMPLICIT PROCEDURE NEW(A : INTEGER, B : INTEGER) + ENDPROCEDURE +ENDCLASS +OUTPUT 1 |
