diff --git a/frontend/include/chpl/resolution/resolution-queries.h b/frontend/include/chpl/resolution/resolution-queries.h index c617c7f08607..b3990dbf834e 100644 --- a/frontend/include/chpl/resolution/resolution-queries.h +++ b/frontend/include/chpl/resolution/resolution-queries.h @@ -216,6 +216,19 @@ types::Type::Genericity getTypeGenericity(Context* context, types::Type::Genericity getTypeGenericity(Context* context, types::QualifiedType qt); + +/** + Returns true if the field should be included in the type constructor. + In that event, also sets formalType to the type the formal should use. + + This is also used to decide if a field needs to be include in a type's + substitutions. + */ +bool shouldIncludeFieldInTypeConstructor(Context* context, + const ID& fieldId, + const types::QualifiedType& fieldType, + types::QualifiedType* formalType = nullptr); + /** Compute an initial TypedFnSignature for a type constructor for a particular type. If some fields of `t` are still generic, diff --git a/frontend/include/chpl/resolution/resolution-types.h b/frontend/include/chpl/resolution/resolution-types.h index be00c6d88aa0..b8a01cb17dcf 100644 --- a/frontend/include/chpl/resolution/resolution-types.h +++ b/frontend/include/chpl/resolution/resolution-types.h @@ -84,25 +84,40 @@ enum struct DefaultsPolicy { */ class UntypedFnSignature { public: + enum DefaultKind { + /** Formals that have default values, like `in x = 10` */ + DK_DEFAULT, + /** Formals that do not have default values, like `ref x` */ + DK_NO_DEFAULT, + /** Formals that might have a default value. This comes up when working + with generic initializers; whether an initializer's formal has + a default depends on if its type has a default value. But if + the type is unknown -- as in a generic initializer's type signature -- + then we don't know if the formal has a default. */ + DK_MAYBE_DEFAULT, + }; + struct FormalDetail { UniqueString name; - bool hasDefaultValue = false; + DefaultKind defaultKind = DK_NO_DEFAULT; const uast::Decl* decl = nullptr; bool isVarArgs = false; FormalDetail(UniqueString name, - bool hasDefaultValue, + DefaultKind defaultKind, const uast::Decl* decl, bool isVarArgs = false) : name(name), - hasDefaultValue(hasDefaultValue), + defaultKind(defaultKind), decl(decl), isVarArgs(isVarArgs) - { } + { + CHPL_ASSERT(name != USTR("this") || defaultKind == DK_NO_DEFAULT); + } bool operator==(const FormalDetail& other) const { return name == other.name && - hasDefaultValue == other.hasDefaultValue && + defaultKind == other.defaultKind && decl == other.decl && isVarArgs == other.isVarArgs; } @@ -111,7 +126,7 @@ class UntypedFnSignature { } size_t hash() const { - return chpl::hash(name, hasDefaultValue, decl, isVarArgs); + return chpl::hash(name, defaultKind, decl, isVarArgs); } void stringify(std::ostream& ss, chpl::StringifyKind stringKind) const { @@ -309,10 +324,10 @@ class UntypedFnSignature { return formals_[i].name; } - /** Return whether the i'th formal has a default value. */ - bool formalHasDefault(int i) const { + /** Return whether the i'th formal might have a default value. */ + bool formalMightHaveDefault(int i) const { CHPL_ASSERT(0 <= i && (size_t) i < formals_.size()); - return formals_[i].hasDefaultValue; + return formals_[i].defaultKind != DK_NO_DEFAULT; } /** Returns the Decl for the i'th formal / field. @@ -1210,6 +1225,9 @@ enum PassingFailureReason { FAIL_CANNOT_CONVERT, /* An instantiation was needed but is not possible. */ FAIL_CANNOT_INSTANTIATE, + /* We had a generic formal, but the actual did not instantiate it; actual + might be generic. */ + FAIL_DID_NOT_INSTANTIATE, /* A type was used as an argument to a value, or the other way around. */ FAIL_TYPE_VS_NONTYPE, /* A param value was expected, but a non-param value was given. */ diff --git a/frontend/include/chpl/types/QualifiedType.h b/frontend/include/chpl/types/QualifiedType.h index 22b9a40b200d..31f799a6a960 100644 --- a/frontend/include/chpl/types/QualifiedType.h +++ b/frontend/include/chpl/types/QualifiedType.h @@ -67,6 +67,7 @@ class QualifiedType final { static const Kind FUNCTION = uast::Qualifier::FUNCTION; static const Kind PARENLESS_FUNCTION = uast::Qualifier::PARENLESS_FUNCTION; static const Kind MODULE = uast::Qualifier::MODULE; + static const Kind INIT_RECEIVER = uast::Qualifier::INIT_RECEIVER; static const char* kindToString(Kind k); diff --git a/frontend/include/chpl/uast/Qualifier.h b/frontend/include/chpl/uast/Qualifier.h index f8c8c38532ad..d1cd2a18f83c 100644 --- a/frontend/include/chpl/uast/Qualifier.h +++ b/frontend/include/chpl/uast/Qualifier.h @@ -101,6 +101,9 @@ enum struct Qualifier { /** A module */ MODULE, + + /** An 'imaginary' actual to 'init''s this to represent the type being constructed. */ + INIT_RECEIVER, }; /** Returns 'true' for qualifiers that are generic such as DEFAULT_INTENT */ diff --git a/frontend/lib/resolution/InitResolver.cpp b/frontend/lib/resolution/InitResolver.cpp index 0b45811495db..8b9b30fea3bd 100644 --- a/frontend/lib/resolution/InitResolver.cpp +++ b/frontend/lib/resolution/InitResolver.cpp @@ -53,15 +53,6 @@ static const Type* receiverTypeFromTfs(const TypedFnSignature* tfs) { return ret; } -static const CompositeType* typeToCompType(const Type* type) { - if (auto cls = type->toClassType()) { - return cls->manageableType()->toCompositeType(); - } else { - auto ret = type->toCompositeType(); - return ret; - } -} - owned InitResolver::create(Context* ctx, Resolver& visitor, const Function* fn) { auto tfs = visitor.typedSignature; @@ -91,7 +82,7 @@ bool InitResolver::setupFromType(const Type* type) { fieldToInitState_.clear(); fieldIdsByOrdinal_.clear(); - auto ct = typeToCompType(type); + auto ct = type->getCompositeType(); auto& rf = fieldsForTypeDecl(ctx_, ct, DefaultsPolicy::USE_DEFAULTS); // If any of the newly-set fields are type or params, setting them @@ -228,7 +219,7 @@ void InitResolver::merge(owned& A, owned& B) { } bool InitResolver::isFinalReceiverStateValid(void) { - auto ctInitial = typeToCompType(initialRecvType_); + auto ctInitial = initialRecvType_->getCompositeType(); auto& rfInitial = fieldsForTypeDecl(ctx_, ctInitial, DefaultsPolicy::USE_DEFAULTS); bool ret = true; @@ -293,7 +284,7 @@ static const Type* ctFromSubs(Context* context, } const Type* InitResolver::computeReceiverTypeConsideringState(void) { - auto ctInitial = typeToCompType(initialRecvType_); + auto ctInitial = initialRecvType_->getCompositeType(); // The non-default fields are used to determine if we need to create // substitutions. I.e., if a field is concrete even if we ignore defaults, @@ -308,7 +299,6 @@ const Type* InitResolver::computeReceiverTypeConsideringState(void) { auto isValidQtForSubstitutions = [this](const QualifiedType qt) { if (qt.isUnknown()) return false; - if (!qt.isType() && !qt.isParam()) return false; return getTypeGenericity(this->ctx_, qt.type()) == Type::CONCRETE; }; @@ -320,6 +310,8 @@ const Type* InitResolver::computeReceiverTypeConsideringState(void) { if (isInitiallyConcrete) continue; + if (!shouldIncludeFieldInTypeConstructor(ctx_, id, qtInitial)) continue; + // TODO: Will need to relax this as we go. if (isValidQtForSubstitutions(state->qt)) { subs.insert({id, state->qt}); @@ -336,7 +328,7 @@ const Type* InitResolver::computeReceiverTypeConsideringState(void) { // dependently typed, we might be able to compute defaults that // depend on these prior substitutions. auto ctIntermediate = ctFromSubs(ctx_, initialRecvType_, ctInitial, subs); - auto& rfIntermediate = fieldsForTypeDecl(ctx_, typeToCompType(ctIntermediate), + auto& rfIntermediate = fieldsForTypeDecl(ctx_, ctIntermediate->getCompositeType(), DefaultsPolicy::USE_DEFAULTS); qtForSub = rfIntermediate.fieldType(i); @@ -441,7 +433,7 @@ bool InitResolver::implicitlyResolveFieldType(ID id) { auto state = fieldStateFromId(id); if (!state || !state->initPointId.isEmpty()) return false; - auto ct = typeToCompType(currentRecvType_); + auto ct = currentRecvType_->getCompositeType(); auto& rf = resolveFieldDecl(ctx_, ct, id, DefaultsPolicy::USE_DEFAULTS); for (int i = 0; i < rf.numFields(); i++) { auto id = rf.fieldDeclId(i); @@ -493,7 +485,7 @@ bool InitResolver::isMentionOfNodeInLhsOfAssign(const AstNode* node) { ID InitResolver::fieldIdFromName(UniqueString name) { if (!isNameOfField(ctx_, name, initialRecvType_)) return ID(); // TODO: Need to replace this as we continue to build it up? - auto ct = typeToCompType(initialRecvType_); + auto ct = initialRecvType_->getCompositeType(); auto ret = parsing::fieldIdWithName(ctx_, ct->id(), name); return ret; } @@ -575,12 +567,12 @@ bool InitResolver::applyResolvedInitCallToState(const FnCall* node, CHPL_ASSERT(fn->formalName(0) == USTR("this")); auto receiverType = fn->formalType(0).type(); - auto receiverCompType = typeToCompType(receiverType); + auto receiverCompType = receiverType->getCompositeType(); if (receiverCompType->instantiatedFromCompositeType()) { receiverCompType = receiverCompType->instantiatedFromCompositeType(); } - auto initialCompType = typeToCompType(initialRecvType_); + auto initialCompType = initialRecvType_->getCompositeType(); if (initialCompType->instantiatedFromCompositeType()) { initialCompType = initialCompType->instantiatedFromCompositeType(); } diff --git a/frontend/lib/resolution/Resolver.cpp b/frontend/lib/resolution/Resolver.cpp index a7201a47b02d..204d284b6a84 100644 --- a/frontend/lib/resolution/Resolver.cpp +++ b/frontend/lib/resolution/Resolver.cpp @@ -1052,9 +1052,8 @@ Resolver::computeCustomInferType(const AstNode* decl, auto rr = resolveGeneratedCall(context, nullptr, ci, inScopes); if (rr.mostSpecific().only()) { ret = rr.exprType(); - handleResolvedAssociatedCall(byPostorder.byAst(decl), decl, ci, rr, - AssociatedAction::INFER_TYPE, - decl->id()); + handleResolvedCall(byPostorder.byAst(decl), decl, ci, rr, + { { AssociatedAction::INFER_TYPE, decl->id() } }); } else { context->error(ct->id(), "'chpl__inferCopyType' is unimplemented"); } @@ -1603,19 +1602,34 @@ void Resolver::issueErrorForFailedModuleDot(const Dot* dot, bool Resolver::handleResolvedCallWithoutError(ResolvedExpression& r, const uast::AstNode* astForErr, const CallInfo& ci, - const CallResolutionResult& c) { + const CallResolutionResult& c, + optional actionAndId) { if (!c.exprType().hasTypePtr()) { - r.setType(QualifiedType(r.type().kind(), ErroneousType::get(context))); - r.setMostSpecific(c.mostSpecific()); + if (!actionAndId) { + // Only set the type to erroneous if we're handling an actual user call, + // and not an associated action. + r.setType(QualifiedType(r.type().kind(), ErroneousType::get(context))); + r.setMostSpecific(c.mostSpecific()); + } // If the call was specially handled, assume special-case logic has already - // issued its own error. + // issued its own error, so we shouldn't emit a general error. return !c.speciallyHandled(); } else { - r.setPoiScope(c.poiInfo().poiScope()); - r.setType(c.exprType()); - validateAndSetMostSpecific(r, astForErr, c.mostSpecific()); + if (actionAndId) { + // save candidates as associated functions + for (auto& sig : c.mostSpecific()) { + if (sig) { + r.addAssociatedAction(std::get<0>(*actionAndId), sig.fn(), + std::get<1>(*actionAndId)); + } + } + } else { + r.setPoiScope(c.poiInfo().poiScope()); + r.setType(c.exprType()); + validateAndSetMostSpecific(r, astForErr, c.mostSpecific()); + } // gather the poi scopes used when resolving the call poiInfo.accumulate(c.poiInfo()); } @@ -1625,9 +1639,10 @@ bool Resolver::handleResolvedCallWithoutError(ResolvedExpression& r, void Resolver::handleResolvedCall(ResolvedExpression& r, const uast::AstNode* astForErr, const CallInfo& ci, - const CallResolutionResult& c) { + const CallResolutionResult& c, + optional actionAndId) { - if (handleResolvedCallWithoutError(r, astForErr, ci, c)) { + if (handleResolvedCallWithoutError(r, astForErr, ci, c, std::move(actionAndId))) { issueErrorForFailedCallResolution(astForErr, ci, c); } } @@ -1637,9 +1652,12 @@ void Resolver::handleResolvedCallPrintCandidates(ResolvedExpression& r, const CallInfo& ci, const CallScopeInfo& inScopes, const QualifiedType& receiverType, - const CallResolutionResult& c) { + const CallResolutionResult& c, + optional actionAndId) { - if (handleResolvedCallWithoutError(r, call, ci, c)) { + bool wasCallGenerated = (bool) actionAndId; + CHPL_ASSERT(!wasCallGenerated || receiverType.isUnknown()); + if (handleResolvedCallWithoutError(r, call, ci, c, std::move(actionAndId))) { if (c.mostSpecific().isEmpty() && !c.mostSpecific().isAmbiguous()) { // The call isn't ambiguous; it might be that we rejected all the candidates @@ -1647,8 +1665,13 @@ void Resolver::handleResolvedCallPrintCandidates(ResolvedExpression& r, // this time to preserve the list of rejected candidates. std::vector rejected; - std::ignore = resolveCallInMethod(context, call, ci, inScopes, - receiverType, &rejected); + + if (wasCallGenerated) { + std::ignore = resolveGeneratedCall(context, call, ci, inScopes, &rejected); + } else { + std::ignore = resolveCallInMethod(context, call, ci, inScopes, + receiverType, &rejected); + } if (!rejected.empty()) { // There were candidates but we threw them out. We can issue a nicer @@ -1663,26 +1686,6 @@ void Resolver::handleResolvedCallPrintCandidates(ResolvedExpression& r, } } -void Resolver::handleResolvedAssociatedCall(ResolvedExpression& r, - const uast::AstNode* astForErr, - const CallInfo& ci, - const CallResolutionResult& c, - AssociatedAction::Action action, - ID id) { - if (!c.exprType().hasTypePtr() && !c.speciallyHandled()) { - issueErrorForFailedCallResolution(astForErr, ci, c); - } else { - // save candidates as associated functions - for (auto& sig : c.mostSpecific()) { - if (sig) { - r.addAssociatedAction(action, sig.fn(), id); - } - } - // gather the poi scopes used when resolving the call - poiInfo.accumulate(c.poiInfo()); - } -} - void Resolver::adjustTypesForSplitInit(ID id, const QualifiedType& rhsType, const AstNode* lhsExprAst, @@ -1839,9 +1842,8 @@ void Resolver::resolveTupleUnpackAssign(ResolvedExpression& r, actuals); auto c = resolveGeneratedCall(context, actual, ci, inScopes); - handleResolvedAssociatedCall(r, astForErr, ci, c, - AssociatedAction::ASSIGN, - lhsTuple->id()); + handleResolvedCall(r, astForErr, ci, c, + { { AssociatedAction::ASSIGN, lhsTuple->id() } }); } i++; } @@ -1988,15 +1990,14 @@ bool Resolver::resolveSpecialNewCall(const Call* call) { // note: the resolution machinery will get compiler generated candidates auto crr = resolveGeneratedCall(context, call, ci, inScopes); + handleResolvedCallPrintCandidates(re, call, ci, inScopes, QualifiedType(), crr, + { { AssociatedAction::NEW_INIT, call->id() } }); - CHPL_ASSERT(crr.mostSpecific().numBest() <= 1); // there should be one or zero applicable candidates + CHPL_ASSERT(crr.mostSpecific().numBest() <= 1); if (auto initMsc = crr.mostSpecific().only()) { auto initTfs = initMsc.fn(); - handleResolvedAssociatedCall(re, call, ci, crr, - AssociatedAction::NEW_INIT, - call->id()); // Set the final output type based on the result of the 'new' call. auto qtInitReceiver = initTfs->formalType(0); @@ -2016,9 +2017,6 @@ bool Resolver::resolveSpecialNewCall(const Call* call) { auto qt = QualifiedType(QualifiedType::VAR, type); re.setType(qt); - - } else { - issueErrorForFailedCallResolution(call, ci, crr); } return true; @@ -2502,9 +2500,8 @@ bool Resolver::enter(const uast::Select* sel) { /* isParenless */ false, actuals); auto c = resolveGeneratedCall(context, caseExpr, ci, inScopes); - handleResolvedAssociatedCall(caseResult, caseExpr, ci, c, - AssociatedAction::COMPARE, - caseExpr->id()); + handleResolvedCall(caseResult, caseExpr, ci, c, + { { AssociatedAction::COMPARE, caseExpr->id() } }); auto type = c.exprType(); anyParamTrue = anyParamTrue || type.isParamTrue(); @@ -3566,7 +3563,7 @@ void Resolver::handleCallExpr(const uast::Call* call) { skip = UNKNOWN_PARAM; } else if (qt.isUnknown()) { skip = UNKNOWN_ACT; - } else if (t != nullptr && !(ci.name() == USTR("init") && actualIdx == 0)) { + } else if (t != nullptr && qt.kind() != QualifiedType::INIT_RECEIVER) { // For initializer calls, allow generic formals using the above // condition; this way, 'this.init(..)' while 'this' is generic // should be fine. @@ -3939,7 +3936,7 @@ static void resolveNewForClass(Resolver& rv, const New* node, const ClassType* classType) { ResolvedExpression& re = rv.byPostorder.byAst(node); auto cls = getDecoratedClassForNew(rv.context, node, classType); - auto qt = QualifiedType(QualifiedType::VAR, cls); + auto qt = QualifiedType(QualifiedType::INIT_RECEIVER, cls); re.setType(qt); } @@ -3950,7 +3947,7 @@ static void resolveNewForRecord(Resolver& rv, const New* node, if (node->management() != New::DEFAULT_MANAGEMENT) { CHPL_REPORT(rv.context, MemManagementNonClass, node, recordType); } else { - auto qt = QualifiedType(QualifiedType::VAR, recordType); + auto qt = QualifiedType(QualifiedType::INIT_RECEIVER, recordType); re.setType(qt); } } @@ -3962,7 +3959,7 @@ static void resolveNewForUnion(Resolver& rv, const New* node, if (node->management() != New::DEFAULT_MANAGEMENT) { CHPL_REPORT(rv.context, MemManagementNonClass, node, unionType); } else { - auto qt = QualifiedType(QualifiedType::VAR, unionType); + auto qt = QualifiedType(QualifiedType::INIT_RECEIVER, unionType); re.setType(qt); } } @@ -4056,9 +4053,8 @@ static QualifiedType resolveSerialIterType(Resolver& resolver, if (c.mostSpecific().only()) { idxType = c.exprType(); - resolver.handleResolvedAssociatedCall(iterandRE, astForErr, ci, c, - AssociatedAction::ITERATE, - iterand->id()); + resolver.handleResolvedCall(iterandRE, astForErr, ci, c, + { { AssociatedAction::ITERATE, iterand->id() } }); } else { idxType = CHPL_TYPE_ERROR(context, NonIterable, astForErr, iterand, iterandRE.type()); } @@ -4307,10 +4303,9 @@ constructReduceScanOpClass(Resolver& resolver, CHPL_REPORT(context, ReductionInvalidName, reduceOrScan, opName, iterType); return nullptr; } else { - resolver.handleResolvedAssociatedCall(resolver.byPostorder.byAst(reduceOrScan), - reduceOrScan, ci, c, - AssociatedAction::REDUCE_SCAN, - reduceOrScan->id()); + resolver.handleResolvedCall(resolver.byPostorder.byAst(reduceOrScan), + reduceOrScan, ci, c, + { { AssociatedAction::REDUCE_SCAN, reduceOrScan->id() } }); } // We found some type; is it a subclass of ReduceScanOp? diff --git a/frontend/lib/resolution/Resolver.h b/frontend/lib/resolution/Resolver.h index d23b3f5b01d1..ab6ccec358bc 100644 --- a/frontend/lib/resolution/Resolver.h +++ b/frontend/lib/resolution/Resolver.h @@ -33,6 +33,7 @@ namespace resolution { struct Resolver { // types used below using ReceiverScopesVec = llvm::SmallVector; + using ActionAndId = std::tuple; /** When looking up matches for a particular identifier, we might encounter @@ -399,12 +400,14 @@ struct Resolver { bool handleResolvedCallWithoutError(ResolvedExpression& r, const uast::AstNode* astForErr, const CallInfo& ci, - const CallResolutionResult& c); + const CallResolutionResult& c, + optional associatedActionAndId = {}); // Same as handleResolvedCallWithoutError, except actually issues the error. void handleResolvedCall(ResolvedExpression& r, const uast::AstNode* astForErr, const CallInfo& ci, - const CallResolutionResult& c); + const CallResolutionResult& c, + optional associatedActionAndId = {}); // like handleResolvedCall, but prints the candidates that were rejected // by the error in detail. void handleResolvedCallPrintCandidates(ResolvedExpression& r, @@ -412,14 +415,8 @@ struct Resolver { const CallInfo& ci, const CallScopeInfo& inScopes, const types::QualifiedType& receiverType, - const CallResolutionResult& c); - // like handleResolvedCall saves the call in associatedFns. - void handleResolvedAssociatedCall(ResolvedExpression& r, - const uast::AstNode* astForErr, - const CallInfo& ci, - const CallResolutionResult& c, - AssociatedAction::Action action, - ID id); + const CallResolutionResult& c, + optional associatedActionAndId = {}); // If the variable with the passed ID has unknown or generic type, // and it has not yet been initialized, set its type to rhsType. diff --git a/frontend/lib/resolution/VarScopeVisitor.cpp b/frontend/lib/resolution/VarScopeVisitor.cpp index e0f8a6ea04e7..6b6926c6b6b5 100644 --- a/frontend/lib/resolution/VarScopeVisitor.cpp +++ b/frontend/lib/resolution/VarScopeVisitor.cpp @@ -514,7 +514,10 @@ bool VarScopeVisitor::enter(const FnCall* callAst, RV& rv) { } else if (kind == Qualifier::OUT) { handleOutFormal(callAst, actualAst, actualFormalTypes[actualIdx], rv); - } else if (kind == Qualifier::IN || kind == Qualifier::CONST_IN) { + } else if ((kind == Qualifier::IN || kind == Qualifier::CONST_IN) && + !(ci.name() == "init" && actualIdx == 0)) { + // don't do this for the 'this' argument to 'init', because it + // is not getting copied. handleInFormal(callAst, actualAst, actualFormalTypes[actualIdx], rv); } else if (kind == Qualifier::INOUT) { diff --git a/frontend/lib/resolution/call-init-deinit.cpp b/frontend/lib/resolution/call-init-deinit.cpp index 2ac8f5304db5..f9a6eaedae01 100644 --- a/frontend/lib/resolution/call-init-deinit.cpp +++ b/frontend/lib/resolution/call-init-deinit.cpp @@ -161,7 +161,8 @@ static bool isValue(QualifiedType::Kind kind) { kind == QualifiedType::IN || kind == QualifiedType::CONST_IN || kind == QualifiedType::OUT || - kind == QualifiedType::INOUT); + kind == QualifiedType::INOUT || + kind == QualifiedType::INIT_RECEIVER); } static bool isValueOrParam(QualifiedType::Kind kind) { return isValue(kind) || kind == QualifiedType::PARAM; @@ -500,9 +501,8 @@ void CallInitDeinit::resolveDefaultInit(const VarLikeDecl* ast, RV& rv) { auto inScopes = CallScopeInfo::forNormalCall(scope, resolver.poiScope); auto c = resolveGeneratedCall(context, ast, ci, inScopes); ResolvedExpression& opR = rv.byAst(ast); - resolver.handleResolvedAssociatedCall(opR, ast, ci, c, - AssociatedAction::DEFAULT_INIT, - ast->id()); + resolver.handleResolvedCall(opR, ast, ci, c, + { { AssociatedAction::DEFAULT_INIT, ast->id() } }); } } @@ -543,9 +543,8 @@ void CallInitDeinit::resolveAssign(const AstNode* ast, resolver.handleResolvedCall(opR, ast, ci, c); } else { // otherwise, add an associated action - resolver.handleResolvedAssociatedCall(opR, ast, ci, c, - AssociatedAction::ASSIGN, - ast->id()); + resolver.handleResolvedCall(opR, ast, ci, c, + { { AssociatedAction::ASSIGN, ast->id() } }); } } @@ -609,7 +608,7 @@ void CallInitDeinit::resolveCopyInit(const AstNode* ast, if (lhsType.type() != rhsType.type()) { action = AssociatedAction::INIT_OTHER; } - resolver.handleResolvedAssociatedCall(opR, ast, ci, c, action, ast->id()); + resolver.handleResolvedCall(opR, ast, ci, c, { { action, ast->id() } }); // If we were trying to move, but had to run an init= to change types, // and that init= did not accept its argument by 'in' intent, we need @@ -772,9 +771,8 @@ void CallInitDeinit::resolveDeinit(const AstNode* ast, } ResolvedExpression& opR = rv.byAst(assocAst); - resolver.handleResolvedAssociatedCall(opR, assocAst, ci, c, - AssociatedAction::DEINIT, - deinitedId); + resolver.handleResolvedCall(opR, assocAst, ci, c, + { { AssociatedAction::DEINIT, deinitedId } }); } void CallInitDeinit::handleDeclaration(const VarLikeDecl* ast, RV& rv) { diff --git a/frontend/lib/resolution/can-pass.cpp b/frontend/lib/resolution/can-pass.cpp index a19661c0938a..1b3d84c3373e 100644 --- a/frontend/lib/resolution/can-pass.cpp +++ b/frontend/lib/resolution/can-pass.cpp @@ -838,7 +838,18 @@ CanPassResult CanPassResult::canInstantiate(Context* context, // check for instantiating classes if (auto formalCt = formalT->toClassType()) { CanPassResult got = canPassSubtypeOrBorrowing(context, actualCt, formalCt); - if (got.passes() && got.instantiates()) { + if (got.passes()) { + if (got.instantiates()) { + return got; + } + + // The type passed, but didn't actually instantiate. This is odd + // since we are trying to instantiate a generic formal. This suggests + // the actual is generic, too, which typically doesn't make sense. + // Explicitly set the fail reason but keep the rest of the properties + // intact, so that that the caller can dismiss this error if + // generic actuals are allowed. + got.failReason_ = FAIL_DID_NOT_INSTANTIATE; return got; } } @@ -976,16 +987,24 @@ CanPassResult CanPassResult::canPass(Context* context, // Further checking will occur after the instantiation occurs, // so checking here just rules out predictable situations. - if (formalQT.kind() != QualifiedType::TYPE && - isTypeGeneric(context, actualQT)) + bool canAcceptGenericActuals = + formalQT.kind() == QualifiedType::TYPE || + actualQT.kind() == QualifiedType::INIT_RECEIVER; + + if (isTypeGeneric(context, actualQT) && !canAcceptGenericActuals) return fail(FAIL_GENERIC_TO_NONTYPE); // generic types can only be passed to type actuals auto got = canInstantiate(context, actualQT, formalQT); - if (!got.passes() && formalQT.kind() == QualifiedType::TYPE) { - // Instantiation may not be necessary for generic type formals: we - // could be passing a (subtype) generic type actual. - // Fall through to the checks below. - } else if (!got.passes() && formalQT.isParam()) { + if (!got.passes() && + got.reason() == FAIL_DID_NOT_INSTANTIATE && canAcceptGenericActuals) { + // No instantiation occurred, but the actual isn't incompatible with + // the formal. This suggests a generic actual being passed to the + // formal; this is typically not allowed, but in this case + // (canAcceptGenericActuals) it is. So, it's not an error. + got.failReason_ = chpl::optional(); + return got; + } + if (!got.passes() && formalQT.isParam()) { // 'isTypeGeneric' will return 'true' if there is not a param value // for the given QualifiedType, which is usually the case for a param // formal despite the presence of a type expression. @@ -1058,6 +1077,7 @@ CanPassResult CanPassResult::canPass(Context* context, case QualifiedType::INOUT: case QualifiedType::VAR: // var/const var don't really make sense case QualifiedType::CONST_VAR: // as formals but we allow it for testing + case QualifiedType::INIT_RECEIVER: { // TODO: promotion return canConvert(context, actualQT, formalQT); diff --git a/frontend/lib/resolution/default-functions.cpp b/frontend/lib/resolution/default-functions.cpp index b7a441240fd0..71dccb39530c 100644 --- a/frontend/lib/resolution/default-functions.cpp +++ b/frontend/lib/resolution/default-functions.cpp @@ -78,7 +78,7 @@ areOverloadsPresentInDefiningScope(Context* context, const Type* type, // nothing found if (vec.size() == 0) return false; - auto haveQt = QualifiedType(QualifiedType::VAR, type); + auto haveQt = QualifiedType(QualifiedType::INIT_RECEIVER, type); // loop through IDs and see if any are methods or operators (method or // standalone) on the same type @@ -189,9 +189,10 @@ generateInitParts(Context* context, } // start by adding a formal for the receiver - auto ufsReceiver = UntypedFnSignature::FormalDetail(USTR("this"), - false, - nullptr); + auto ufsReceiver = + UntypedFnSignature::FormalDetail(USTR("this"), + UntypedFnSignature::DK_NO_DEFAULT, + nullptr); ufsFormals.push_back(std::move(ufsReceiver)); // Determine the receiver type and intent. @@ -232,12 +233,6 @@ generateInitSignature(Context* context, const CompositeType* inCompType) { const DefaultsPolicy defaultsPolicy = DefaultsPolicy::IGNORE_DEFAULTS; auto& rf = fieldsForTypeDecl(context, compType, defaultsPolicy); - // TODO: generic types - if (rf.isGeneric()) { - CHPL_UNIMPL("generating 'init' signatures for generic types is not yet supported"); - return nullptr; - } - // TODO: super fields and invoking super if (auto basic = compType->toBasicClassType()) { if (auto parent = basic->parentClassType()) { @@ -252,7 +247,9 @@ generateInitSignature(Context* context, const CompositeType* inCompType) { auto fieldQt = rf.fieldType(i); auto formalName = rf.fieldName(i); bool formalHasDefault = rf.fieldHasDefaultValue(i); - const uast::Decl* formalAst = nullptr; + + const uast::Decl* formalAst = + parsing::idToAst(context, rf.fieldDeclId(i))->toDecl(); // A field may not have a default value. If it is default-initializable // then the formal should still take a default value (in this case the @@ -261,7 +258,16 @@ generateInitSignature(Context* context, const CompositeType* inCompType) { // type that can be used as a sentinel. formalHasDefault |= isTypeDefaultInitializable(context, fieldQt.type()); - auto fd = UntypedFnSignature::FormalDetail(formalName, formalHasDefault, + UntypedFnSignature::DefaultKind defaultKind; + if (formalHasDefault) { + defaultKind = UntypedFnSignature::DK_DEFAULT; + } else if (rf.isGeneric()) { + defaultKind = UntypedFnSignature::DK_MAYBE_DEFAULT; + } else { + defaultKind = UntypedFnSignature::DK_NO_DEFAULT; + } + + auto fd = UntypedFnSignature::FormalDetail(formalName, defaultKind, formalAst); ufsFormals.push_back(std::move(fd)); @@ -313,10 +319,10 @@ generateInitCopySignature(Context* context, const CompositeType* inCompType) { // add a formal for the 'other' argument auto name = UniqueString::get(context, "other"); - bool hasDefault = false; + auto defaultKind = UntypedFnSignature::DK_NO_DEFAULT; const uast::Decl* node = nullptr; - auto fd = UntypedFnSignature::FormalDetail(name, hasDefault, node); + auto fd = UntypedFnSignature::FormalDetail(name, defaultKind, node); ufsFormals.push_back(std::move(fd)); CHPL_ASSERT(formalTypes.size() == 1); @@ -392,14 +398,23 @@ generateDeSerialize(Context* context, const CompositeType* compType, std::vector ufsFormals; std::vector formalTypes; - ufsFormals.push_back(UntypedFnSignature::FormalDetail(USTR("this"), false, nullptr)); + ufsFormals.push_back( + UntypedFnSignature::FormalDetail(USTR("this"), + UntypedFnSignature::DK_NO_DEFAULT, + nullptr)); formalTypes.push_back(QualifiedType(QualifiedType::CONST_REF, compType)); // TODO: Add constraints to these arguments - ufsFormals.push_back(UntypedFnSignature::FormalDetail(UniqueString::get(context, channel), false, nullptr)); + ufsFormals.push_back( + UntypedFnSignature::FormalDetail(UniqueString::get(context, channel), + UntypedFnSignature::DK_NO_DEFAULT, + nullptr)); formalTypes.push_back(QualifiedType(QualifiedType::CONST_REF, AnyType::get(context))); - ufsFormals.push_back(UntypedFnSignature::FormalDetail(UniqueString::get(context, deSerializer), false, nullptr)); + ufsFormals.push_back( + UntypedFnSignature::FormalDetail(UniqueString::get(context, deSerializer), + UntypedFnSignature::DK_NO_DEFAULT, + nullptr)); formalTypes.push_back(QualifiedType(QualifiedType::REF, AnyType::get(context))); // build the untyped signature @@ -439,7 +454,10 @@ generateDomainMethod(Context* context, std::vector formals; std::vector formalTypes; - formals.push_back(UntypedFnSignature::FormalDetail(USTR("this"), false, nullptr)); + formals.push_back( + UntypedFnSignature::FormalDetail(USTR("this"), + UntypedFnSignature::DK_NO_DEFAULT, + nullptr)); formalTypes.push_back(QualifiedType(QualifiedType::CONST_REF, dt)); auto ufs = UntypedFnSignature::get(context, @@ -475,7 +493,10 @@ generateArrayMethod(Context* context, std::vector formals; std::vector formalTypes; - formals.push_back(UntypedFnSignature::FormalDetail(USTR("this"), false, nullptr)); + formals.push_back( + UntypedFnSignature::FormalDetail(USTR("this"), + UntypedFnSignature::DK_NO_DEFAULT, + nullptr)); formalTypes.push_back(QualifiedType(QualifiedType::CONST_REF, at)); auto ufs = UntypedFnSignature::get(context, @@ -510,7 +531,10 @@ generateTupleMethod(Context* context, std::vector formals; std::vector formalTypes; - formals.push_back(UntypedFnSignature::FormalDetail(USTR("this"), false, nullptr)); + formals.push_back( + UntypedFnSignature::FormalDetail(USTR("this"), + UntypedFnSignature::DK_NO_DEFAULT, + nullptr)); formalTypes.push_back(QualifiedType(QualifiedType::CONST_REF, at)); auto ufs = UntypedFnSignature::get(context, @@ -547,9 +571,10 @@ fieldAccessorQuery(Context* context, std::vector formalTypes; // start by adding a formal for the receiver - auto ufsReceiver = UntypedFnSignature::FormalDetail(USTR("this"), - false, - nullptr); + auto ufsReceiver = + UntypedFnSignature::FormalDetail(USTR("this"), + UntypedFnSignature::DK_NO_DEFAULT, + nullptr); ufsFormals.push_back(std::move(ufsReceiver)); const Type* thisType = compType; @@ -607,7 +632,10 @@ generateOperatorFormalDetail(const UniqueString name, QualifiedType::Kind qtKind, bool hasDefault = false, const uast::Decl* decl = nullptr) { - auto fd = UntypedFnSignature::FormalDetail(name, hasDefault, decl); + auto defaultKind = hasDefault ? UntypedFnSignature::DK_DEFAULT + : UntypedFnSignature::DK_NO_DEFAULT; + + auto fd = UntypedFnSignature::FormalDetail(name, defaultKind, decl); ufsFormals.push_back(std::move(fd)); auto qtFd = QualifiedType(qtKind, compType); @@ -743,7 +771,10 @@ generateCPtrMethod(Context* context, QualifiedType receiverType, std::vector formals; std::vector formalTypes; - formals.push_back(UntypedFnSignature::FormalDetail(USTR("this"), false, nullptr)); + formals.push_back( + UntypedFnSignature::FormalDetail(USTR("this"), + UntypedFnSignature::DK_NO_DEFAULT, + nullptr)); // Allow calling 'eltType' on either a type or value auto qual = receiverType.isType() ? QualifiedType::TYPE : QualifiedType::CONST_REF; @@ -843,11 +874,11 @@ setupGeneratedEnumCastFormals(Context* context, auto ufsFrom = UntypedFnSignature::FormalDetail(UniqueString::get(context, "from"), - false, nullptr); + UntypedFnSignature::DK_NO_DEFAULT, nullptr); ufsFormals.push_back(std::move(ufsFrom)); auto ufsTo = UntypedFnSignature::FormalDetail(UniqueString::get(context, "to"), - false, nullptr); + UntypedFnSignature::DK_NO_DEFAULT, nullptr); ufsFormals.push_back(std::move(ufsTo)); formalTypes.push_back(fromQt); diff --git a/frontend/lib/resolution/intents.cpp b/frontend/lib/resolution/intents.cpp index 12a2b05ae561..8ce077cfd228 100644 --- a/frontend/lib/resolution/intents.cpp +++ b/frontend/lib/resolution/intents.cpp @@ -116,6 +116,7 @@ QualifiedType::Kind resolveIntent(const QualifiedType& t, case QualifiedType::PARENLESS_FUNCTION: case QualifiedType::FUNCTION: case QualifiedType::MODULE: + case QualifiedType::INIT_RECEIVER: // these don't really have an intent return QualifiedType::UNKNOWN; diff --git a/frontend/lib/resolution/resolution-error-classes-list.cpp b/frontend/lib/resolution/resolution-error-classes-list.cpp index a9bedf094646..53d727bc0a21 100644 --- a/frontend/lib/resolution/resolution-error-classes-list.cpp +++ b/frontend/lib/resolution/resolution-error-classes-list.cpp @@ -886,7 +886,18 @@ void ErrorNoMatchingCandidates::write(ErrorWriterBase& wr) const { } else if (formalDecl->isTupleDecl()) { formalName = "'" + buildTupleDeclName(formalDecl->toTupleDecl()) + "'"; } - wr.message("The formal ", formalName, " expects ", badPass.formalType(), ", but the actual was ", badPass.actualType(), "."); + + if (badPass.formalType().isUnknown()) { + // The formal type can be unknown in an initial instantiation if it + // depends on the previous formals' types. In that case, don't print it + // and say something nicer. + wr.message("The instantiated type of formal ", formalName, + " does not allow actuals of type '", badPass.actualType().type(), "'."); + } else { + wr.message("The formal ", formalName, " expects ", badPass.formalType(), + ", but the actual was ", badPass.actualType(), "."); + } + if (actualExpr) { wr.code(actualExpr, { actualExpr }); } diff --git a/frontend/lib/resolution/resolution-queries.cpp b/frontend/lib/resolution/resolution-queries.cpp index cacd8aea8913..68dd34e7a8c7 100644 --- a/frontend/lib/resolution/resolution-queries.cpp +++ b/frontend/lib/resolution/resolution-queries.cpp @@ -418,7 +418,7 @@ anyFormalNeedsInstantiation(Context* context, bool considerGenericity = true; if (substitutions != nullptr) { auto formalDecl = untypedSig->formalDecl(i); - if (substitutions->count(formalDecl->id())) { + if (formalDecl && substitutions->count(formalDecl->id())) { // don't consider it needing a substitution - e.g. when passing // a generic type into a type argument. considerGenericity = false; @@ -1185,13 +1185,10 @@ Type::Genericity getTypeGenericity(Context* context, QualifiedType qt) { return getTypeGenericityIgnoring(context, qt, ignore); } -// Returns true if the field should be included in the type constructor. -// In that event, also sets formalType to the type the formal should use. -static bool shouldIncludeFieldInTypeConstructor(Context* context, - const Decl* fieldDecl, + const ID& fieldId, const QualifiedType& fieldType, - QualifiedType& formalType) { + QualifiedType* formalType) { // compare with AggregateType::fieldIsGeneric // fields with concrete types don't need to be in type constructor @@ -1203,11 +1200,13 @@ bool shouldIncludeFieldInTypeConstructor(Context* context, // and we can use the same type/param intent for the type constructor if ((fieldType.isParam() && !fieldType.hasParamPtr()) || fieldType.isType()) { - formalType = fieldType; + if (formalType) *formalType = fieldType; return true; } - if (const VarLikeDecl* var = fieldDecl->toVarLikeDecl()) { + if (asttags::isVarLikeDecl(parsing::idToTag(context, fieldId))) { + auto var = parsing::idToAst(context, fieldId)->toVarLikeDecl(); + // non-type/param fields with an init expression aren't generic if (var->initExpression()) return false; @@ -1215,7 +1214,8 @@ bool shouldIncludeFieldInTypeConstructor(Context* context, // non-type/param fields that have no declared type and no initializer // are generic and these need a type variable for the argument with AnyType. if (var->typeExpression() == nullptr) { - formalType = QualifiedType(QualifiedType::TYPE, AnyType::get(context)); + if (formalType) + *formalType = QualifiedType(QualifiedType::TYPE, AnyType::get(context)); return true; } @@ -1226,10 +1226,14 @@ bool shouldIncludeFieldInTypeConstructor(Context* context, // * unknown type means it depends on a previous generic field // (and when previous generic fields are set, they will be concrete) const Type* t = fieldType.type(); - if (t && !t->isUnknownType()) { + // a 'var' field of 'AnyType' isn't itself generic, it just depends on + // another field that's 'AnyType'. In that case, treat it as unknown. + bool isVarOfAnyType = fieldType.kind() != QualifiedType::TYPE && + t && t->isAnyType(); + if (t && !isVarOfAnyType && !t->isUnknownType()) { Type::Genericity g = getTypeGenericity(context, t); if (g == Type::GENERIC) { // and not GENERIC_WITH_DEFAULTS - formalType = QualifiedType(QualifiedType::TYPE, t); + if (formalType) *formalType = QualifiedType(QualifiedType::TYPE, t); return true; } } @@ -1272,11 +1276,14 @@ typeConstructorInitialQuery(Context* context, const Type* t) CHPL_ASSERT(fieldDecl); QualifiedType fieldType = f.fieldType(i); QualifiedType formalType; - if (shouldIncludeFieldInTypeConstructor(context, fieldDecl, fieldType, - formalType)) { + if (shouldIncludeFieldInTypeConstructor(context, declId, fieldType, + &formalType)) { + auto defaultKind = f.fieldHasDefaultValue(i) ? + UntypedFnSignature::DK_DEFAULT : + UntypedFnSignature::DK_NO_DEFAULT; auto d = UntypedFnSignature::FormalDetail(f.fieldName(i), - f.fieldHasDefaultValue(i), + defaultKind, fieldDecl, fieldDecl->isVarArgFormal()); formals.push_back(d); @@ -2116,21 +2123,49 @@ ApplicabilityResult instantiateSignature(Context* context, // now pull out the field types CHPL_ASSERT(formalTypes.empty()); int nFormals = sig->numFormals(); - for (int i = 0; i < nFormals; i++) { - const Decl* fieldDecl = untypedSignature->formalDecl(i); + int formalIdx = 0; + + // The "default value" hints are set in substitutions, but at this + // point, we want to use the actual type that was computed. So, + // rebuild the substitutions. + SubstitutionsMap newSubstitutions; + + if (!sig->untyped()->isTypeConstructor()) { + // Compiler-generated initializer has an initial 'this' formal, + // skip it for now and insert a placeholder. + formalIdx++; + formalTypes.push_back(QualifiedType()); + } + + for (; formalIdx < nFormals; formalIdx++) { + const Decl* fieldDecl = untypedSignature->formalDecl(formalIdx); const ResolvedExpression& e = r.byAst(fieldDecl); QualifiedType fieldType = e.type(); - QualifiedType sigType = sig->formalType(i); + QualifiedType sigType = sig->formalType(formalIdx); // use the same kind as the old formal type but update the type, param // to reflect how instantiation occurred. formalTypes.push_back(QualifiedType(sigType.kind(), fieldType.type(), fieldType.param())); + + if (shouldIncludeFieldInTypeConstructor(context, fieldDecl->id(), sigType)){ + newSubstitutions.insert({fieldDecl->id(), fieldType}); + } } + + if (!sig->untyped()->isTypeConstructor()) { + // We've visited the rest of the formals and figured out their types. + // Time to backfill the 'this' formal. + auto newType = helpGetTypeForDecl(context, ad, newSubstitutions, + poiScope, sig->formalType(0).type()); + + formalTypes[0] = QualifiedType(sig->formalType(0).kind(), newType); + } + needsInstantiation = anyFormalNeedsInstantiation(context, formalTypes, untypedSignature, - &substitutions); + &newSubstitutions); } else if (ed) { // Fine; formal types were stored into formalTypes earlier since we're // considering a compiler-generated candidate on an enum. @@ -3525,7 +3560,8 @@ considerCompilerGeneratedCandidates(Context* context, const CallInfo& ci, const Scope* inScope, const PoiScope* inPoiScope, - CandidatesAndForwardingInfo& candidates) { + CandidatesAndForwardingInfo& candidates, + std::vector* rejected) { const TypedFnSignature* tfs = nullptr; tfs = considerCompilerGeneratedMethods(context, ci, inScope, inPoiScope, candidates); @@ -3553,9 +3589,15 @@ considerCompilerGeneratedCandidates(Context* context, tfs, ci, poi); - if (!instantiated.success() || - instantiated.candidate()->needsInstantiation()) { + if (!instantiated.success()) { + // failed when instantiating, likely due to dependent types. + if (rejected) rejected->push_back(instantiated); + return; + } + + if (instantiated.candidate()->needsInstantiation()) { context->error(tfs->id(), "invalid instantiation of compiler-generated method"); + return; // do not push invalid candidate into list } candidates.addCandidate(instantiated.candidate()); @@ -3733,7 +3775,8 @@ gatherAndFilterCandidatesForwarding(Context* context, const CallScopeInfo& inScopes, CandidatesAndForwardingInfo& nonPoiCandidates, CandidatesAndForwardingInfo& poiCandidates, - LastResortCandidateGroups& lrcGroups) { + LastResortCandidateGroups& lrcGroups, + std::vector* rejected) { const Type* receiverType = ci.actual(0).type().type(); @@ -3829,7 +3872,8 @@ gatherAndFilterCandidatesForwarding(Context* context, // consider compiler-generated candidates considerCompilerGeneratedCandidates(context, fci, inScopes.callScope(), inScopes.poiScope(), - nonPoiCandidates); + nonPoiCandidates, + rejected); // update forwardingTo nonPoiCandidates.helpComputeForwardingTo(fci, start); @@ -3857,7 +3901,7 @@ gatherAndFilterCandidatesForwarding(Context* context, inScopes.callScope(), inScopes.poiScope(), candidatesWithInstantiations, - /* rejected */ nullptr); + rejected); // filter out last resort candidates filterCandidatesLastResort(context, candidatesWithInstantiations, @@ -3902,7 +3946,7 @@ gatherAndFilterCandidatesForwarding(Context* context, inScopes.callScope(), inScopes.poiScope(), candidatesWithInstantiations, - /* rejected */ nullptr); + rejected); // filter out last resort candidates filterCandidatesLastResort(context, candidatesWithInstantiations, @@ -3928,7 +3972,8 @@ gatherAndFilterCandidatesForwarding(Context* context, inScopes, nonPoiCandidates, poiCandidates, - thisForwardingLrcGroups); + thisForwardingLrcGroups, + rejected); } } lrcGroups.getForwardingGroups().mergeWithGroups( @@ -3995,7 +4040,8 @@ gatherAndFilterCandidates(Context* context, considerCompilerGeneratedCandidates(context, ci, inScopes.callScope(), inScopes.poiScope(), - candidates); + candidates, + rejected); // don't worry about last resort for compiler generated candidates @@ -4106,7 +4152,8 @@ gatherAndFilterCandidates(Context* context, gatherAndFilterCandidatesForwarding( context, call, ci, inScopes, nonPoiCandidates, - poiCandidates, lrcGroups.getForwardingGroups()); + poiCandidates, lrcGroups.getForwardingGroups(), + rejected); // append candidates from forwarding candidates.takeFromOther(nonPoiCandidates); diff --git a/frontend/lib/resolution/resolution-types.cpp b/frontend/lib/resolution/resolution-types.cpp index f7a84c612664..cf944b16e5bf 100644 --- a/frontend/lib/resolution/resolution-types.cpp +++ b/frontend/lib/resolution/resolution-types.cpp @@ -131,7 +131,9 @@ getUntypedFnSignatureForFn(Context* context, const uast::Function* fn) { CHPL_ASSERT(varargs->initExpression() == nullptr); } - auto fd = UntypedFnSignature::FormalDetail(name, hasDefault, + auto defaultKind = hasDefault ? UntypedFnSignature::DK_DEFAULT + : UntypedFnSignature::DK_NO_DEFAULT; + auto fd = UntypedFnSignature::FormalDetail(name, defaultKind, decl, decl->isVarArgFormal()); formals.push_back(fd); } @@ -362,6 +364,14 @@ tryGetType(const AstNode* node, const ResolutionResultByPostorderID& byPostorder return {}; } +static QualifiedType convertToInitReceiverType(const QualifiedType original) { + if (original.kind() != QualifiedType::TYPE && + original.kind() != QualifiedType::PARAM) { + return QualifiedType(QualifiedType::INIT_RECEIVER, original.type()); + } + return original; +} + CallInfo CallInfo::create(Context* context, const Call* call, const ResolutionResultByPostorderID& byPostorder, @@ -418,11 +428,19 @@ CallInfo CallInfo::create(Context* context, } else if (!call->isOpCall() && dotReceiverType && isKindForMethodReceiver(dotReceiverType->kind())) { // Check for normal method call, maybe construct a receiver. - actuals.push_back(CallInfoActual(*dotReceiverType, USTR("this"))); + + // If this is a receiver to 'init', adjust the receiver type to + // use the INIT_RECEIVER intent. + auto dotReceiverQt = *dotReceiverType; + if (name == USTR("init")) { + dotReceiverQt = convertToInitReceiverType(dotReceiverQt); + } + + actuals.push_back(CallInfoActual(dotReceiverQt, USTR("this"))); if (actualAsts != nullptr) { actualAsts->push_back(dotReceiver); } - calledType = *dotReceiverType; + calledType = dotReceiverQt; isMethodCall = true; } } @@ -457,6 +475,12 @@ CallInfo CallInfo::createWithReceiver(const CallInfo& ci, // append the other actuals newActuals.insert(newActuals.end(), ci.actuals_.begin(), ci.actuals_.end()); + if (ci.name() == USTR("init")) { + // For calls to 'init', tag the receiver with a special intent to + // relax some checks on its genericity. + receiverType = convertToInitReceiverType(receiverType); + } + auto name = rename.isEmpty() ? ci.name_ : rename; return CallInfo(name, receiverType, /* isMethodCall */ true, @@ -545,7 +569,7 @@ bool FormalActualMap::computeAlignment(const UntypedFnSignature* untyped, entry.actualIdx_ = -1; entry.formalType_ = formalQT; entry.formalInstantiated_ = formalInstantiated; - entry.hasDefault_ = untyped->formalHasDefault(i); + entry.hasDefault_ = untyped->formalMightHaveDefault(i); entryIdx++; } else { @@ -598,7 +622,7 @@ bool FormalActualMap::computeAlignment(const UntypedFnSignature* untyped, entry.actualIdx_ = -1; entry.formalType_ = qt; entry.formalInstantiated_ = formalInstantiated; - entry.hasDefault_ = untyped->formalHasDefault(i); + entry.hasDefault_ = untyped->formalMightHaveDefault(i); entry.isVarArgEntry_ = true; entryIdx++; diff --git a/frontend/lib/types/Type.cpp b/frontend/lib/types/Type.cpp index 1110db8a6d2c..712c9a1a3777 100644 --- a/frontend/lib/types/Type.cpp +++ b/frontend/lib/types/Type.cpp @@ -230,7 +230,7 @@ const CompositeType* Type::getCompositeType() const { return at; if (auto ct = toClassType()) - return ct->basicClassType(); + return ct->manageableType(); return nullptr; } diff --git a/frontend/lib/uast/Qualifier.cpp b/frontend/lib/uast/Qualifier.cpp index 120b62bddba5..f4a296ec8d5c 100644 --- a/frontend/lib/uast/Qualifier.cpp +++ b/frontend/lib/uast/Qualifier.cpp @@ -43,6 +43,7 @@ bool isGenericQualifier(Qualifier kind) { case Qualifier::FUNCTION: return false; case Qualifier::PARENLESS_FUNCTION: return false; case Qualifier::MODULE: return false; + case Qualifier::INIT_RECEIVER: return false; } return false; } @@ -68,6 +69,7 @@ bool isConstQualifier(Qualifier kind) { case Qualifier::FUNCTION: return true; case Qualifier::PARENLESS_FUNCTION: return true; case Qualifier::MODULE: return true; + case Qualifier::INIT_RECEIVER: return false; } return false; } @@ -93,6 +95,7 @@ bool isImmutableQualifier(Qualifier kind) { case Qualifier::FUNCTION: return true; case Qualifier::PARENLESS_FUNCTION: return true; case Qualifier::MODULE: return true; + case Qualifier::INIT_RECEIVER: return false; } return false; } @@ -118,6 +121,7 @@ bool isRefQualifier(Qualifier kind) { case Qualifier::FUNCTION: return false; case Qualifier::PARENLESS_FUNCTION: return false; case Qualifier::MODULE: return false; + case Qualifier::INIT_RECEIVER: return false; } return false; } @@ -143,6 +147,7 @@ bool isInQualifier(Qualifier kind) { case Qualifier::FUNCTION: return false; case Qualifier::PARENLESS_FUNCTION: return false; case Qualifier::MODULE: return false; + case Qualifier::INIT_RECEIVER: return false; } return false; } @@ -168,6 +173,7 @@ const char* qualifierToString(Qualifier intent) { case Qualifier::FUNCTION: return ""; case Qualifier::PARENLESS_FUNCTION: return ""; case Qualifier::MODULE: return ""; + case Qualifier::INIT_RECEIVER: return ""; } return ""; } diff --git a/frontend/test/framework/testQueryEquivalence.cpp b/frontend/test/framework/testQueryEquivalence.cpp index 21c3270518b4..98a76c49dee4 100644 --- a/frontend/test/framework/testQueryEquivalence.cpp +++ b/frontend/test/framework/testQueryEquivalence.cpp @@ -51,7 +51,7 @@ static MostSpecificCandidate const& mscQuery(Context* context) { /* kind */ uast::Function::Kind::PROC, /* formals */ { UntypedFnSignature::FormalDetail { UniqueString::get(context, "x"), - /* hasDefaultValue */ false, + UntypedFnSignature::DK_NO_DEFAULT, /* decl */ nullptr, }}, /* whereClause */ nullptr diff --git a/frontend/test/resolution/testGenericDefaults.cpp b/frontend/test/resolution/testGenericDefaults.cpp index a39a39e6f0fb..c33e1ab6cadc 100644 --- a/frontend/test/resolution/testGenericDefaults.cpp +++ b/frontend/test/resolution/testGenericDefaults.cpp @@ -249,7 +249,7 @@ static void test5() { auto aType = r.byAst(a).type(); assert(aType.type()->isCompositeType()); auto subs = aType.type()->toCompositeType()->substitutions(); - assert(subs.size() == 2); + assert(subs.size() == 1); for (auto pair : subs) { assert(pair.second.type()->isStringType()); } diff --git a/frontend/test/resolution/testInitSemantics.cpp b/frontend/test/resolution/testInitSemantics.cpp index 5001bae8dbcc..5f8d2df1c841 100644 --- a/frontend/test/resolution/testInitSemantics.cpp +++ b/frontend/test/resolution/testInitSemantics.cpp @@ -767,15 +767,56 @@ static void testOwnedUserInit(void) { std::ignore = resolveModule(ctx, mod->id()); } -static void testInitFromInit(void) { - // test calling 'this.init' from another initializer. - Context ctx; - Context* context = &ctx; - ErrorGuard guard(context); +// Replaces a placeholder with possible values to help test more programs. +// If multuple placeholders are present, tests all combinations. +static std::vector getVersionsWithTypes(const std::string& prog, + const std::string& placeholder, + const std::vector types) { + std::vector> variants; + + std::vector occurrences; + size_t start = 0; + while ((start = prog.find(placeholder, start)) != std::string::npos) { + occurrences.push_back(start); + start += placeholder.length(); + } - auto qt = resolveTypeOfXInit(context, + std::reverse(occurrences.begin(), occurrences.end()); + std::vector programs = { prog }; + std::vector programsNext; + + for (auto occurrence : occurrences) { + for (auto& program : programs) { + for (auto& type : types) { + std::string newProg = program; + newProg.replace(occurrence, placeholder.length(), type); + programsNext.push_back(newProg); + } + } + programs = std::move(programsNext); + } + + return programs; +} + +static std::vector getAllVersions(const std::string& prog) { + // Note, const checker currently balks at non-'this' calls to 'init'. + std::vector initProgs = + getVersionsWithTypes(prog, "INIT", { "this.init" }); + + std::vector allProgs; + for (auto initProg : initProgs) { + auto versions = getVersionsWithTypes(initProg, "AGGREGATE", { "record", "class" }); + allProgs.insert(allProgs.end(), versions.begin(), versions.end()); + } + + return allProgs; +} + +static void testInitFromInit(void) { + std::string prog = R"""( - record pair { + AGGREGATE pair { type fst; type snd; @@ -785,49 +826,52 @@ static void testInitFromInit(void) { } proc init(type both) { - this.init(both, (both, both)); + INIT(both, (both, both)); } } var x = new pair(int); - )"""); + )"""; - assert(qt.type()); - auto recType = qt.type()->toCompositeType(); - assert(recType); - assert(recType->name() == "pair"); + for (auto version : getAllVersions(prog)) { + Context ctx; + Context* context = &ctx; + ErrorGuard guard(context); + auto qt = resolveTypeOfXInit(context, version); - auto fields = fieldsForTypeDecl(context, recType, DefaultsPolicy::IGNORE_DEFAULTS); + assert(qt.type()); + auto recType = qt.type()->getCompositeType(); + assert(recType); + assert(recType->name() == "pair"); + + auto fields = fieldsForTypeDecl(context, recType, DefaultsPolicy::IGNORE_DEFAULTS); - assert(fields.fieldName(0) == "fst"); - assert(fields.fieldName(1) == "snd"); + assert(fields.fieldName(0) == "fst"); + assert(fields.fieldName(1) == "snd"); - auto fstType = fields.fieldType(0); - assert(fstType.type()); - assert(fstType.type()->isIntType()); + auto fstType = fields.fieldType(0); + assert(fstType.type()); + assert(fstType.type()->isIntType()); - auto sndType = fields.fieldType(1); - assert(sndType.type()); - auto sndTypeTup = sndType.type()->toTupleType(); - assert(sndTypeTup); + auto sndType = fields.fieldType(1); + assert(sndType.type()); + auto sndTypeTup = sndType.type()->toTupleType(); + assert(sndTypeTup); - auto fstTupEltType = sndTypeTup->elementType(0); - assert(fstTupEltType.type()); - assert(fstTupEltType.type()->isIntType()); - auto sndTupEltType = sndTypeTup->elementType(1); - assert(sndTupEltType.type()); - assert(sndTupEltType.type()->isIntType()); + auto fstTupEltType = sndTypeTup->elementType(0); + assert(fstTupEltType.type()); + assert(fstTupEltType.type()->isIntType()); + auto sndTupEltType = sndTypeTup->elementType(1); + assert(sndTupEltType.type()); + assert(sndTupEltType.type()->isIntType()); + } } static void testInitInParamBranchFromInit(void) { // test calling 'this.init' from another initializer. - Context ctx; - Context* context = &ctx; - ErrorGuard guard(context); - - auto qts = resolveTypesOfVariables(context, + std::string prog = R"""( - record pair { + AGGREGATE pair { type fst; type snd; @@ -838,7 +882,7 @@ static void testInitInParamBranchFromInit(void) { proc init(param cond) { if cond { - this.init(int, (int, int)); + INIT(int, (int, int)); } else { this.fst = bool; this.snd = bool; @@ -848,67 +892,71 @@ static void testInitInParamBranchFromInit(void) { var x = new pair(true); var y = new pair(false); - )""", {"x", "y"}); + )"""; - { - auto qt = qts.at("x"); - assert(qt.type()); - auto recType = qt.type()->toCompositeType(); - assert(recType); - assert(recType->name() == "pair"); + for (auto version : getAllVersions(prog)) { + Context ctx; + Context* context = &ctx; + ErrorGuard guard(context); - auto fields = fieldsForTypeDecl(context, recType, DefaultsPolicy::IGNORE_DEFAULTS); + auto qts = resolveTypesOfVariables(context, version, {"x", "y"}); - assert(fields.fieldName(0) == "fst"); - assert(fields.fieldName(1) == "snd"); - auto fstType = fields.fieldType(0); - assert(fstType.type()); - assert(fstType.type()->isIntType()); + { + auto qt = qts.at("x"); + assert(qt.type()); + auto recType = qt.type()->getCompositeType(); + assert(recType); + assert(recType->name() == "pair"); - auto sndType = fields.fieldType(1); - assert(sndType.type()); - auto sndTypeTup = sndType.type()->toTupleType(); - assert(sndTypeTup); + auto fields = fieldsForTypeDecl(context, recType, DefaultsPolicy::IGNORE_DEFAULTS); - auto fstTupEltType = sndTypeTup->elementType(0); - assert(fstTupEltType.type()); - assert(fstTupEltType.type()->isIntType()); - auto sndTupEltType = sndTypeTup->elementType(1); - assert(sndTupEltType.type()); - assert(sndTupEltType.type()->isIntType()); - } - { - auto qt = qts.at("y"); - assert(qt.type()); - auto recType = qt.type()->toCompositeType(); - assert(recType); - assert(recType->name() == "pair"); + assert(fields.fieldName(0) == "fst"); + assert(fields.fieldName(1) == "snd"); - auto fields = fieldsForTypeDecl(context, recType, DefaultsPolicy::IGNORE_DEFAULTS); + auto fstType = fields.fieldType(0); + assert(fstType.type()); + assert(fstType.type()->isIntType()); - assert(fields.fieldName(0) == "fst"); - assert(fields.fieldName(1) == "snd"); + auto sndType = fields.fieldType(1); + assert(sndType.type()); + auto sndTypeTup = sndType.type()->toTupleType(); + assert(sndTypeTup); - auto fstType = fields.fieldType(0); - assert(fstType.type()); - assert(fstType.type()->isBoolType()); - auto sndType = fields.fieldType(1); - assert(sndType.type()); - assert(sndType.type()->isBoolType()); + auto fstTupEltType = sndTypeTup->elementType(0); + assert(fstTupEltType.type()); + assert(fstTupEltType.type()->isIntType()); + auto sndTupEltType = sndTypeTup->elementType(1); + assert(sndTupEltType.type()); + assert(sndTupEltType.type()->isIntType()); + } + { + auto qt = qts.at("y"); + assert(qt.type()); + auto recType = qt.type()->getCompositeType(); + assert(recType); + assert(recType->name() == "pair"); + + auto fields = fieldsForTypeDecl(context, recType, DefaultsPolicy::IGNORE_DEFAULTS); + + assert(fields.fieldName(0) == "fst"); + assert(fields.fieldName(1) == "snd"); + + auto fstType = fields.fieldType(0); + assert(fstType.type()); + assert(fstType.type()->isBoolType()); + auto sndType = fields.fieldType(1); + assert(sndType.type()); + assert(sndType.type()->isBoolType()); + } } } static void testInitInBranchFromInit(void) { - // test calling 'this.init' from another initializer. - Context ctx; - Context* context = &ctx; - ErrorGuard guard(context); - - auto qts = resolveTypesOfVariables(context, + std::string prog = R"""( - record pair { + AGGREGATE pair { type fst; type snd; @@ -919,7 +967,7 @@ static void testInitInBranchFromInit(void) { proc init(cond: bool) { if cond { - this.init(int, (int, int)); + INIT(int, (int, int)); } else { this.fst = int; this.snd = (int, int); @@ -929,76 +977,80 @@ static void testInitInBranchFromInit(void) { var x = new pair(true); var y = new pair(false); - )""", {"x", "y"}); + )"""; + for (auto version : getAllVersions(prog)) { + // test calling 'this.init' from another initializer. + Context ctx; + Context* context = &ctx; + ErrorGuard guard(context); - { - auto qt = qts.at("x"); - assert(qt.type()); - auto recType = qt.type()->toCompositeType(); - assert(recType); - assert(recType->name() == "pair"); + auto qts = resolveTypesOfVariables(context, version, {"x", "y"}); - auto fields = fieldsForTypeDecl(context, recType, DefaultsPolicy::IGNORE_DEFAULTS); - assert(fields.fieldName(0) == "fst"); - assert(fields.fieldName(1) == "snd"); + { + auto qt = qts.at("x"); + assert(qt.type()); + auto recType = qt.type()->getCompositeType(); + assert(recType); + assert(recType->name() == "pair"); - auto fstType = fields.fieldType(0); - assert(fstType.type()); - assert(fstType.type()->isIntType()); + auto fields = fieldsForTypeDecl(context, recType, DefaultsPolicy::IGNORE_DEFAULTS); - auto sndType = fields.fieldType(1); - assert(sndType.type()); - auto sndTypeTup = sndType.type()->toTupleType(); - assert(sndTypeTup); + assert(fields.fieldName(0) == "fst"); + assert(fields.fieldName(1) == "snd"); - auto fstTupEltType = sndTypeTup->elementType(0); - assert(fstTupEltType.type()); - assert(fstTupEltType.type()->isIntType()); - auto sndTupEltType = sndTypeTup->elementType(1); - assert(sndTupEltType.type()); - assert(sndTupEltType.type()->isIntType()); - } - { - auto qt = qts.at("y"); - assert(qt.type()); - auto recType = qt.type()->toCompositeType(); - assert(recType); - assert(recType->name() == "pair"); + auto fstType = fields.fieldType(0); + assert(fstType.type()); + assert(fstType.type()->isIntType()); - auto fields = fieldsForTypeDecl(context, recType, DefaultsPolicy::IGNORE_DEFAULTS); - - assert(fields.fieldName(0) == "fst"); - assert(fields.fieldName(1) == "snd"); - - auto fstType = fields.fieldType(0); - assert(fstType.type()); - assert(fstType.type()->isIntType()); + auto sndType = fields.fieldType(1); + assert(sndType.type()); + auto sndTypeTup = sndType.type()->toTupleType(); + assert(sndTypeTup); - auto sndType = fields.fieldType(1); - assert(sndType.type()); - auto sndTypeTup = sndType.type()->toTupleType(); - assert(sndTypeTup); - - auto fstTupEltType = sndTypeTup->elementType(0); - assert(fstTupEltType.type()); - assert(fstTupEltType.type()->isIntType()); - auto sndTupEltType = sndTypeTup->elementType(1); - assert(sndTupEltType.type()); - assert(sndTupEltType.type()->isIntType()); + auto fstTupEltType = sndTypeTup->elementType(0); + assert(fstTupEltType.type()); + assert(fstTupEltType.type()->isIntType()); + auto sndTupEltType = sndTypeTup->elementType(1); + assert(sndTupEltType.type()); + assert(sndTupEltType.type()->isIntType()); + } + { + auto qt = qts.at("y"); + assert(qt.type()); + auto recType = qt.type()->getCompositeType(); + assert(recType); + assert(recType->name() == "pair"); + + auto fields = fieldsForTypeDecl(context, recType, DefaultsPolicy::IGNORE_DEFAULTS); + + assert(fields.fieldName(0) == "fst"); + assert(fields.fieldName(1) == "snd"); + + auto fstType = fields.fieldType(0); + assert(fstType.type()); + assert(fstType.type()->isIntType()); + + auto sndType = fields.fieldType(1); + assert(sndType.type()); + auto sndTypeTup = sndType.type()->toTupleType(); + assert(sndTypeTup); + + auto fstTupEltType = sndTypeTup->elementType(0); + assert(fstTupEltType.type()); + assert(fstTupEltType.type()->isIntType()); + auto sndTupEltType = sndTypeTup->elementType(1); + assert(sndTupEltType.type()); + assert(sndTupEltType.type()->isIntType()); + } } } static void testBadInitInBranchFromInit(void) { - // test calling 'this.init' from another initializer. - Context ctx; - Context* context = &ctx; - ErrorGuard guard(context); - - std::ignore = resolveTypeOfXInit(context, + std::string prog = R"""( - record pair { + AGGREGATE pair { type fst; type snd; @@ -1009,7 +1061,7 @@ static void testBadInitInBranchFromInit(void) { proc init(cond: bool) { if cond { - this.init(bool, (bool, bool)); + INIT(bool, (bool, bool)); } else { this.fst = int; this.snd = (int, int); @@ -1018,22 +1070,25 @@ static void testBadInitInBranchFromInit(void) { } var x = new pair(true); - )"""); + )"""; + + for (auto version : getAllVersions(prog)) { + // test calling 'this.init' from another initializer. + Context ctx; + Context* context = &ctx; + ErrorGuard guard(context); + std::ignore = resolveTypeOfXInit(context, version); - // The above is invalid, since the two branches result in a different type. - assert(guard.realizeErrors() == 1); + // The above is invalid, since the two branches result in a different type. + assert(guard.realizeErrors() == 1); + } } static void testAssignThenInit(void) { - // test calling 'this.init' from another initializer. - Context ctx; - Context* context = &ctx; - ErrorGuard guard(context); - - std::ignore = resolveTypeOfXInit(context, + std::string prog = R"""( - record pair { + AGGREGATE pair { type fst; type snd; @@ -1044,20 +1099,29 @@ static void testAssignThenInit(void) { proc init(type both) { this.fst = both; - this.init(both, (both, both)); + INIT(both, (both, both)); } } var x = new pair(int); - )"""); + )"""; + for (auto version : getAllVersions(prog)) { + // test calling 'this.init' from another initializer. + Context ctx; + Context* context = &ctx; + ErrorGuard guard(context); - // Can't initialize 'fst' then call initializer. Due to - // - // https://github.com/chapel-lang/chapel/issues/24900 - // - // The errors are currently issued twice. - assert(guard.realizeErrors() == 2); + std::ignore = resolveTypeOfXInit(context, version); + + + // Can't initialize 'fst' then call initializer. Due to + // + // https://github.com/chapel-lang/chapel/issues/24900 + // + // The errors are currently issued twice. + assert(guard.realizeErrors() == 2); + } } static void testInitEqOther(void) { diff --git a/frontend/test/resolution/testNew.cpp b/frontend/test/resolution/testNew.cpp index ca7ae7d3c27e..13ce39aa069e 100644 --- a/frontend/test/resolution/testNew.cpp +++ b/frontend/test/resolution/testNew.cpp @@ -71,13 +71,16 @@ static void testEmptyRecordUserInit() { // Remember that 'new r' is the base expression of the call. auto& reNewExpr = rr.byAst(newExpr); auto& qtNewExpr = reNewExpr.type(); - assert(qtNewExpr.kind() == QualifiedType::VAR); + assert(qtNewExpr.kind() == QualifiedType::INIT_RECEIVER); assert(qtNewExpr.type() == qtR.type()); // The 'new' call should have the same type as the 'new' expr. + // However, the 'new' expr has the special INIT_RECEIVER intent, + // so don't compare that. auto& reNewCall = rr.byAst(newCall); auto& qtNewCall = reNewCall.type(); - assert(qtNewExpr == qtNewCall); + assert(qtNewExpr.type() == qtNewCall.type()); + assert(qtNewExpr.param() == qtNewCall.param()); // The 'new' call should have 'init' as an associated function. auto& associatedActions = reNewCall.associatedActions(); @@ -131,13 +134,14 @@ static void testEmptyRecordCompilerGenInit() { // Remember that 'new r' is the base expression of the call. auto& reNewExpr = rr.byAst(newExpr); auto& qtNewExpr = reNewExpr.type(); - assert(qtNewExpr.kind() == QualifiedType::VAR); + assert(qtNewExpr.kind() == QualifiedType::INIT_RECEIVER); assert(qtNewExpr.type() == qtR.type()); // The 'new' call should have the same type as the 'new' expr. auto& reNewCall = rr.byAst(newCall); auto& qtNewCall = reNewCall.type(); - assert(qtNewExpr == qtNewCall); + assert(qtNewExpr.type() == qtNewCall.type()); + assert(qtNewExpr.param() == qtNewCall.param()); // The 'new' call should have 'init' as an associated function. // This 'init' is compiler generated. @@ -229,7 +233,8 @@ static void testTertMethodCallCrossModule() { auto& reInitExpr = rr.byAst(initExpr); assert(reInitExpr.type() == reX.type()); auto& reNewExpr = rr.byAst(newExpr); - assert(reNewExpr.type() == reInitExpr.type()); + assert(reNewExpr.type().type() == reInitExpr.type().type()); + assert(reNewExpr.type().param() == reInitExpr.type().param()); assert(reInitExpr.associatedActions().size() == 1); auto tfsInit = reInitExpr.associatedActions()[0].fn(); @@ -632,6 +637,426 @@ static void testNewGenericWithDefaults() { } } +static void testCompilerGeneratedGenericNewWithDefaultInit() { + Context ctx; + Context* context = &ctx; + ErrorGuard guard(context); + + auto vars = resolveTypesOfVariables(context, + R"""( + record r { + param flag : bool; + var x : if flag then int else real; + } + + var x1 = new r(true); + var x2 = new r(false); + )""", { "x1", "x2" }); + + + { + auto ct = vars.at("x1").type()->toCompositeType(); + assert(ct); + assert(ct->name() == "r"); + + // It should already be instantiated, no need to use defaults. + auto fields = fieldsForTypeDecl(context, ct, DefaultsPolicy::IGNORE_DEFAULTS); + assert(fields.numFields() == 2); + + auto f1 = fields.fieldType(0); + assert(f1.isParamTrue()); + auto f2 = fields.fieldType(1); + assert(f2.kind() == QualifiedType::VAR); + assert(f2.type()); + assert(f2.type()->isIntType()); + assert(f2.type()->toIntType()->isDefaultWidth()); + } + + { + auto ct = vars.at("x2").type()->toCompositeType(); + assert(ct); + assert(ct->name() == "r"); + + // It should already be instantiated, no need to use defaults. + auto fields = fieldsForTypeDecl(context, ct, DefaultsPolicy::IGNORE_DEFAULTS); + assert(fields.numFields() == 2); + + auto f1 = fields.fieldType(0); + assert(f1.isParamFalse()); + auto f2 = fields.fieldType(1); + assert(f2.kind() == QualifiedType::VAR); + assert(f2.type()); + assert(f2.type()->isRealType()); + assert(f2.type()->toRealType()->isDefaultWidth()); + } +} + +static void testCompilerGeneratedGenericNew() { + Context ctx; + Context* context = &ctx; + ErrorGuard guard(context); + + auto vars = resolveTypesOfVariables(context, + R"""( + record r { + param flag : bool; + var x : if flag then int else real; + } + + var x1 = new r(true, 1); + var x2 = new r(false, 1.0); + var x3 = new r(true, 1.0); + var x4 = new r(false, 1); + )""", { "x1", "x2", "x3", "x4" }); + + + { + auto ct = vars.at("x1").type()->toCompositeType(); + assert(ct); + assert(ct->name() == "r"); + + // It should already be instantiated, no need to use defaults. + auto fields = fieldsForTypeDecl(context, ct, DefaultsPolicy::IGNORE_DEFAULTS); + assert(fields.numFields() == 2); + + auto f1 = fields.fieldType(0); + assert(f1.isParamTrue()); + auto f2 = fields.fieldType(1); + assert(f2.kind() == QualifiedType::VAR); + assert(f2.type()); + assert(f2.type()->isIntType()); + assert(f2.type()->toIntType()->isDefaultWidth()); + } + + { + auto ct = vars.at("x2").type()->toCompositeType(); + assert(ct); + assert(ct->name() == "r"); + + // It should already be instantiated, no need to use defaults. + auto fields = fieldsForTypeDecl(context, ct, DefaultsPolicy::IGNORE_DEFAULTS); + assert(fields.numFields() == 2); + + auto f1 = fields.fieldType(0); + assert(f1.isParamFalse()); + auto f2 = fields.fieldType(1); + assert(f2.kind() == QualifiedType::VAR); + assert(f2.type()); + assert(f2.type()->isRealType()); + assert(f2.type()->toRealType()->isDefaultWidth()); + } + + { + auto qt = vars.at("x3"); + assert(qt.isUnknown()); + } + + { + auto ct = vars.at("x4").type()->toCompositeType(); + assert(ct); + assert(ct->name() == "r"); + + // It should already be instantiated, no need to use defaults. + auto fields = fieldsForTypeDecl(context, ct, DefaultsPolicy::IGNORE_DEFAULTS); + assert(fields.numFields() == 2); + + auto f1 = fields.fieldType(0); + assert(f1.isParamFalse()); + auto f2 = fields.fieldType(1); + assert(f2.kind() == QualifiedType::VAR); + assert(f2.type()); + assert(f2.type()->isRealType()); + assert(f2.type()->toRealType()->isDefaultWidth()); + } + + assert(guard.realizeErrors() == 1); +} + +static void testCompilerGeneratedGenericNewWithDefaultInitClass() { + Context ctx; + Context* context = &ctx; + ErrorGuard guard(context); + + auto vars = resolveTypesOfVariables(context, + R"""( + class C { + param flag : bool; + var x : if flag then int else real; + } + + var x1 = new C(true); + var x2 = new C(false); + )""", { "x1", "x2" }); + + + { + auto ct = vars.at("x1").type()->toCompositeType(); + assert(ct); + assert(ct->name() == "C"); + + // It should already be instantiated, no need to use defaults. + auto fields = fieldsForTypeDecl(context, ct, DefaultsPolicy::IGNORE_DEFAULTS); + assert(fields.numFields() == 2); + + auto f1 = fields.fieldType(0); + assert(f1.isParamTrue()); + auto f2 = fields.fieldType(1); + assert(f2.kind() == QualifiedType::VAR); + assert(f2.type()); + assert(f2.type()->isIntType()); + assert(f2.type()->toIntType()->isDefaultWidth()); + } + + { + auto ct = vars.at("x2").type()->toCompositeType(); + assert(ct); + assert(ct->name() == "C"); + + // It should already be instantiated, no need to use defaults. + auto fields = fieldsForTypeDecl(context, ct, DefaultsPolicy::IGNORE_DEFAULTS); + assert(fields.numFields() == 2); + + auto f1 = fields.fieldType(0); + assert(f1.isParamFalse()); + auto f2 = fields.fieldType(1); + assert(f2.kind() == QualifiedType::VAR); + assert(f2.type()); + assert(f2.type()->isRealType()); + assert(f2.type()->toRealType()->isDefaultWidth()); + } +} + +static void testCompilerGeneratedGenericNewClass() { + Context ctx; + Context* context = &ctx; + ErrorGuard guard(context); + + auto vars = resolveTypesOfVariables(context, + R"""( + class C { + param flag : bool; + var x : if flag then int else real; + } + + var x1 = new C(true, 1); + var x2 = new C(false, 1.0); + var x3 = new C(true, 1.0); + var x4 = new C(false, 1); + )""", { "x1", "x2", "x3", "x4" }); + + + { + auto ct = vars.at("x1").type()->toCompositeType(); + assert(ct); + assert(ct->name() == "C"); + + // It should already be instantiated, no need to use defaults. + auto fields = fieldsForTypeDecl(context, ct, DefaultsPolicy::IGNORE_DEFAULTS); + assert(fields.numFields() == 2); + + auto f1 = fields.fieldType(0); + assert(f1.isParamTrue()); + auto f2 = fields.fieldType(1); + assert(f2.kind() == QualifiedType::VAR); + assert(f2.type()); + assert(f2.type()->isIntType()); + assert(f2.type()->toIntType()->isDefaultWidth()); + } + + { + auto ct = vars.at("x2").type()->toCompositeType(); + assert(ct); + assert(ct->name() == "C"); + + // It should already be instantiated, no need to use defaults. + auto fields = fieldsForTypeDecl(context, ct, DefaultsPolicy::IGNORE_DEFAULTS); + assert(fields.numFields() == 2); + + auto f1 = fields.fieldType(0); + assert(f1.isParamFalse()); + auto f2 = fields.fieldType(1); + assert(f2.kind() == QualifiedType::VAR); + assert(f2.type()); + assert(f2.type()->isRealType()); + assert(f2.type()->toRealType()->isDefaultWidth()); + } + + { + auto qt = vars.at("x3"); + assert(qt.isUnknown()); + } + + { + auto ct = vars.at("x4").type()->toCompositeType(); + assert(ct); + assert(ct->name() == "C"); + + // It should already be instantiated, no need to use defaults. + auto fields = fieldsForTypeDecl(context, ct, DefaultsPolicy::IGNORE_DEFAULTS); + assert(fields.numFields() == 2); + + auto f1 = fields.fieldType(0); + assert(f1.isParamFalse()); + auto f2 = fields.fieldType(1); + assert(f2.kind() == QualifiedType::VAR); + assert(f2.type()); + assert(f2.type()->isRealType()); + assert(f2.type()->toRealType()->isDefaultWidth()); + } + + assert(guard.realizeErrors() == 1); +} + +static void testSimpleUserGenericNew() { + Context ctx; + Context* context = &ctx; + ErrorGuard guard(context); + + auto vars = resolveTypesOfVariables(context, + R"""( + // Need an operator= for non-compile-time values to be assigned. + operator =(ref lhs: numeric, rhs: numeric) { + __primitive("=", lhs, rhs); + } + + class C { + var x; + + proc init(value) { + this.x = value; + } + } + + var x1 = new C(42); + var x2 = new C(1.0); + )""", { "x1", "x2" }); + + + { + auto ct = vars.at("x1").type()->getCompositeType(); + assert(ct); + assert(ct->name() == "C"); + + // It should already be instantiated, no need to use defaults. + auto fields = fieldsForTypeDecl(context, ct, DefaultsPolicy::IGNORE_DEFAULTS); + assert(fields.numFields() == 1); + + auto f1 = fields.fieldType(0); + assert(f1.kind() == QualifiedType::VAR); + assert(f1.type()); + assert(f1.type()->isIntType()); + assert(f1.type()->toIntType()->isDefaultWidth()); + } + + { + auto ct = vars.at("x2").type()->getCompositeType(); + assert(ct); + assert(ct->name() == "C"); + + // It should already be instantiated, no need to use defaults. + auto fields = fieldsForTypeDecl(context, ct, DefaultsPolicy::IGNORE_DEFAULTS); + assert(fields.numFields() == 1); + + auto f1 = fields.fieldType(0); + assert(f1.kind() == QualifiedType::VAR); + assert(f1.type()); + assert(f1.type()->isRealType()); + assert(f1.type()->toRealType()->isDefaultWidth()); + } +} + +static void testUserGenericNew() { + Context ctx; + Context* context = &ctx; + ErrorGuard guard(context); + + auto vars = resolveTypesOfVariables(context, + R"""( + // Need an operator= for non-compile-time values to be assigned. + operator =(ref lhs: numeric, rhs: numeric) { + __primitive("=", lhs, rhs); + } + + record r { + param flag : bool; + var x : if flag then int else real; + + proc init(param flag: bool, x: if flag then int else real) { + this.flag = flag; + this.x = x; + } + } + + var x1 = new r(true, 1); + var x2 = new r(false, 1.0); + var x3 = new r(true, 1.0); + var x4 = new r(false, 1); + )""", { "x1", "x2", "x3", "x4" }); + + + { + auto ct = vars.at("x1").type()->toCompositeType(); + assert(ct); + assert(ct->name() == "r"); + + // It should already be instantiated, no need to use defaults. + auto fields = fieldsForTypeDecl(context, ct, DefaultsPolicy::IGNORE_DEFAULTS); + assert(fields.numFields() == 2); + + auto f1 = fields.fieldType(0); + assert(f1.isParamTrue()); + auto f2 = fields.fieldType(1); + assert(f2.kind() == QualifiedType::VAR); + assert(f2.type()); + assert(f2.type()->isIntType()); + assert(f2.type()->toIntType()->isDefaultWidth()); + } + + { + auto ct = vars.at("x2").type()->toCompositeType(); + assert(ct); + assert(ct->name() == "r"); + + // It should already be instantiated, no need to use defaults. + auto fields = fieldsForTypeDecl(context, ct, DefaultsPolicy::IGNORE_DEFAULTS); + assert(fields.numFields() == 2); + + auto f1 = fields.fieldType(0); + assert(f1.isParamFalse()); + auto f2 = fields.fieldType(1); + assert(f2.kind() == QualifiedType::VAR); + assert(f2.type()); + assert(f2.type()->isRealType()); + assert(f2.type()->toRealType()->isDefaultWidth()); + } + + { + auto qt = vars.at("x3"); + assert(qt.isUnknown()); + } + + { + auto ct = vars.at("x4").type()->toCompositeType(); + assert(ct); + assert(ct->name() == "r"); + + // It should already be instantiated, no need to use defaults. + auto fields = fieldsForTypeDecl(context, ct, DefaultsPolicy::IGNORE_DEFAULTS); + assert(fields.numFields() == 2); + + auto f1 = fields.fieldType(0); + assert(f1.isParamFalse()); + auto f2 = fields.fieldType(1); + assert(f2.kind() == QualifiedType::VAR); + assert(f2.type()); + assert(f2.type()->isRealType()); + assert(f2.type()->toRealType()->isDefaultWidth()); + } + + assert(guard.realizeErrors() == 1); +} + + int main() { testEmptyRecordUserInit(); testEmptyRecordCompilerGenInit(); @@ -641,6 +1066,12 @@ int main() { testRecordNewSegfault(); testGenericRecordUserSecondaryInitDependentField(); testNewGenericWithDefaults(); + testCompilerGeneratedGenericNewWithDefaultInit(); + testCompilerGeneratedGenericNew(); + testCompilerGeneratedGenericNewWithDefaultInitClass(); + testCompilerGeneratedGenericNewClass(); + testSimpleUserGenericNew(); + testUserGenericNew(); return 0; }