Skip to content

Commit b9843fb

Browse files
authored
Dyno: support for partial type instantiations (#27861)
This PR resolves most of the issues in `test/types/partial`, __except__ `test/types/partial/formals`, which is caused by Cray/chapel-private#7669. We simply didn't have code written to support partial instantiations. Previously, invoking any type constructor reset any previously applied substitutions. Thus: ```Chapel record triple { type fst; type snd; type thd; } type a = triple(int, ?); type b = a(bool, ?); type c = b(real, ?); ``` Produced `a = triple(int, ?)`, `b = triple(bool, ?)` and `c = triple(real, ?)`. This is not correct. This PR makes the following adjustments. * When a type constructor is invoked, inserts the existing substitutions of the type into the call. This mirrors the logic for invoking `new` on a partial instantiation. The calling code already knows when defaults should or should not be included. * The exception to the above is that in inheritance expressions, we for some reason computed the type of `A` in `A(?)` as `A(A's defaults)`. This was not needed or relied on anywhere, so I removed it. * Detects cases in which are are invoking a type constructor, but don't know the type. This can happen in initial signatures, where the type-to-be-constructed its given by a preceding formal. In this case, we know the intent to be `TYPE`, but the `Type*` is `UnknownType`. Signal this case from `CallInfo::create`, and defer resolution to instantiation where necessary. * Allowed `init=`'s lhs to be partially generic. We clearly allow this (in, e.g., `var r: R(t1, ?) = ...`). * Allow the RHS of `init=` to be a `param`. We clearly allow this (see `test/types/partial/initeq.chpl`). ## Testing - [x] dyno tests - [x] paratest - [x] paratest --dyno-resolve-only (down to 54 failures to resolve working programs) Reviewed by @benharsh -- thanks!
2 parents 7af4133 + f852d84 commit b9843fb

File tree

12 files changed

+352
-20
lines changed

12 files changed

+352
-20
lines changed

frontend/include/chpl/resolution/resolution-types.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -650,14 +650,22 @@ class CallInfo {
650650
with the ID of the scope that should be searched for candidates.
651651
That is, if the call expression is 'M.f(...)' for a module 'M', then
652652
'moduleScopeId' will be set to the ID of the module 'M'.
653+
654+
This method detects calls to type constructors where the type
655+
being constructed is not yet known. if 'outIncompleteTypeConstructor' is
656+
provided and not 'nullptr', sets '*outIncompleteTypeConstructor' to 'true'
657+
when detecting such a call. In that case, the returned CallInfo
658+
should not be resolved.
659+
653660
*/
654661
static CallInfo create(Context* context,
655662
const uast::Call* call,
656663
const ResolutionResultByPostorderID& byPostorder,
657664
bool raiseErrors = true,
658665
std::vector<const uast::AstNode*>* actualAsts=nullptr,
659666
ID* moduleScopeId=nullptr,
660-
UniqueString rename = UniqueString());
667+
UniqueString rename = UniqueString(),
668+
bool* outIncompleteTypeConstructor = nullptr);
661669

662670
/** Construct a CallInfo by adding a method receiver argument to
663671
the passed CallInfo. */

frontend/lib/resolution/Resolver.cpp

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3238,14 +3238,16 @@ resolveSpecialKeywordCallAsNormalCall(Resolver& rv,
32383238
ResolvedExpression& noteInto) {
32393239
auto runResult = rv.context->runAndDetectErrors([&](Context* ctx) {
32403240
std::vector<const AstNode*> actualAsts;
3241+
bool isIncompleteTypeConstructor = false;
32413242
auto ci = CallInfo::create(rv.context, innerCall, rv.byPostorder,
32423243
/* raiseErrors */ true,
32433244
/* actualAsts */ &actualAsts,
32443245
/* moduleScopeId */ nullptr,
3245-
/* rename */ name);
3246+
/* rename */ name,
3247+
&isIncompleteTypeConstructor);
32463248

32473249
auto skip = shouldSkipCallResolution(&rv, innerCall, actualAsts, ci);
3248-
if (skip != NONE) {
3250+
if (isIncompleteTypeConstructor || skip != NONE) {
32493251
return CallResolutionResult::getEmpty();
32503252
}
32513253

@@ -5294,11 +5296,7 @@ types::QualifiedType Resolver::typeForBooleanOp(const uast::OpCall* op) {
52945296
}
52955297

52965298
bool Resolver::enter(const Call* call) {
5297-
// At this time, we don't allow method calls in inheritance expressions,
5298-
// so we assume that there can't be overloading etc.
5299-
if (call != curInheritanceExpr) {
5300-
callNodeStack.push_back(call);
5301-
}
5299+
callNodeStack.push_back(call);
53025300
auto op = call->toOpCall();
53035301

53045302
if (op && initResolver) {
@@ -5574,17 +5572,20 @@ void Resolver::handleCallExpr(const uast::Call* call) {
55745572

55755573
std::vector<const uast::AstNode*> actualAsts;
55765574
ID moduleScopeId;
5575+
bool isIncompleteTypeConstructor = false;
55775576
auto ci = CallInfo::create(context, call, byPostorder,
55785577
/* raiseErrors */ true,
55795578
&actualAsts,
5580-
&moduleScopeId);
5579+
&moduleScopeId,
5580+
/* rename */ UniqueString(),
5581+
&isIncompleteTypeConstructor);
55815582
auto inScopes =
55825583
moduleScopeId.isEmpty() ?
55835584
CallScopeInfo::forNormalCall(scope, poiScope) :
55845585
CallScopeInfo::forQualifiedCall(context, moduleScopeId, scope, poiScope);
55855586

55865587
auto skip = shouldSkipCallResolution(this, call, actualAsts, ci);
5587-
if (!skip) {
5588+
if (!skip && !isIncompleteTypeConstructor) {
55885589
ResolvedExpression& r = byPostorder.byAst(call);
55895590
QualifiedType receiverType = methodReceiverType();
55905591

@@ -5706,9 +5707,7 @@ void Resolver::exit(const Call* call) {
57065707

57075708
// Always remove the call from the stack if we pushed it there,
57085709
// to make sure it's properly set.
5709-
if (call != curInheritanceExpr) {
5710-
callNodeStack.pop_back();
5711-
}
5710+
callNodeStack.pop_back();
57125711
}
57135712

57145713
bool Resolver::enter(const Dot* dot) {

frontend/lib/resolution/call-init-deinit.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ setupCallForCopyOrMove(Resolver& resolver,
228228
std::move(actuals));
229229
} else {
230230
// For other types, use `init=`.
231-
auto varArg = QualifiedType(QualifiedType::VAR, lhsType.type(), lhsType.param());
231+
auto varArg = QualifiedType(QualifiedType::INIT_RECEIVER, lhsType.type(), lhsType.param());
232232
actuals.push_back(CallInfoActual(varArg, USTR("this")));
233233
outAsts.push_back(ast);
234234
actuals.push_back(CallInfoActual(rhsType, UniqueString()));

frontend/lib/resolution/maybe-const.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,13 @@ bool AdjustMaybeRefs::enter(const Call* ast, RV& rv) {
292292
// it was inferred, so we need to offset by one.
293293
const AstNode* actualAst = inferredReceiver ? actualAsts[actualIdx-1] :
294294
actualAsts[actualIdx];
295+
296+
// we could've inserted synthetic actuals when making a second
297+
// call to a partially instantiated type.
298+
if (!actualAst) {
299+
continue;
300+
}
301+
295302
Access access = accessForQualifier(fa->formalType().kind());
296303

297304
exprStack.push_back(ExprStackEntry(actualAst, access,

frontend/lib/resolution/resolution-queries.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6440,6 +6440,25 @@ resolveFnCall(ResolutionContext* rc,
64406440
}
64416441
}
64426442

6443+
// if the original call was a type constructor via a managed type
6444+
// (e.g., (owned C(?))(typeArg)), adjust the return type to keep
6445+
// the management of the called type.
6446+
const ClassType* calledCt = nullptr;
6447+
if (ci.calledType().type() && (calledCt = ci.calledType().type()->toClassType())) {
6448+
const ClassType* retCt = nullptr;
6449+
const BasicClassType* retBct = nullptr;
6450+
if (retType && retType->type() &&
6451+
(retCt = retType->type()->toClassType()) &&
6452+
retCt->decorator().isUnknownManagement() && retCt->manageableType() &&
6453+
(retBct = retCt->manageableType()->toBasicClassType())) {
6454+
auto newCt = ClassType::get(context, retBct,
6455+
calledCt->manager(),
6456+
calledCt->decorator());
6457+
CHPL_ASSERT(retType->param() == nullptr);
6458+
retType = QualifiedType(retType->kind(), newCt);
6459+
}
6460+
}
6461+
64436462
return CallResolutionResult(mostSpecific,
64446463
rejectedPossibleIteratorCandidates,
64456464
((bool) retType) ? *retType : QualifiedType(),
@@ -6687,6 +6706,10 @@ bool addExistingSubstitutionsAsActuals(Context* context,
66876706
auto fieldAst = parsing::idToAst(context, id)->toVarLikeDecl();
66886707
if (fieldAst->storageKind() == QualifiedType::TYPE ||
66896708
fieldAst->storageKind() == QualifiedType::PARAM) {
6709+
if (qt.isParam() && !qt.hasParamPtr()) {
6710+
// don't add param substitutions that are not known
6711+
continue;
6712+
}
66906713
addedSubs = true;
66916714
outActuals.emplace_back(qt, fieldAst->name());
66926715
outActualAsts.push_back(nullptr);

frontend/lib/resolution/resolution-types.cpp

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -428,13 +428,76 @@ static QualifiedType convertToInitReceiverType(const QualifiedType original) {
428428
return original;
429429
}
430430

431+
static void prependActualFromSubstitutions(Context* context,
432+
const QualifiedType& calledType,
433+
std::vector<CallInfoActual>& actuals,
434+
std::vector<const uast::AstNode*>* actualAsts) {
435+
if (!calledType.type()) {
436+
return;
437+
}
438+
439+
std::vector<CallInfoActual> generatedActuals;
440+
std::vector<const uast::AstNode*> generatedActualAsts;
441+
if (!addExistingSubstitutionsAsActuals(context, calledType.type(),
442+
generatedActuals, generatedActualAsts)) {
443+
// didn't do anything; just return.
444+
return;
445+
}
446+
447+
// if the user is explicitly overriding a substitution, don't include the
448+
// previous value, since that would double up the named actuals.
449+
// Unnamed actuals are assumed to be sequentially mapped to the remaining
450+
// (non-substituted) formals.
451+
452+
// find the user-mentioned actuals
453+
std::set<UniqueString> existingNames;
454+
for (const auto& actual : actuals) {
455+
existingNames.insert(actual.byName());
456+
}
457+
458+
// remove any generated actuals that the user has overridden
459+
// idx points to current element
460+
// firstToRemove points to the next available index to keep elements
461+
// two-list implementation of: https://en.cppreference.com/w/cpp/algorithm/remove.html
462+
size_t firstToRemove = 0;
463+
for(; firstToRemove < generatedActuals.size(); firstToRemove++) {
464+
if (existingNames.count(generatedActuals[firstToRemove].byName()) != 0) {
465+
break;
466+
}
467+
}
468+
for (size_t idx = firstToRemove; idx < generatedActuals.size(); idx++) {
469+
if (existingNames.count(generatedActuals[idx].byName()) == 0) {
470+
generatedActuals[firstToRemove] = std::move(generatedActuals[idx]);
471+
generatedActualAsts[firstToRemove] = generatedActualAsts[idx];
472+
firstToRemove++;
473+
}
474+
}
475+
generatedActuals.erase(generatedActuals.begin() + firstToRemove,generatedActuals.end());
476+
generatedActualAsts.erase(generatedActualAsts.begin() + firstToRemove,generatedActualAsts.end());
477+
478+
// move the user-provided actuals into the generated lists, then swap
479+
generatedActuals.insert(generatedActuals.end(),
480+
std::make_move_iterator(actuals.begin()),
481+
std::make_move_iterator(actuals.end()));
482+
actuals.swap(generatedActuals);
483+
if (actualAsts != nullptr) {
484+
generatedActualAsts.insert(generatedActualAsts.end(),
485+
std::make_move_iterator(actualAsts->begin()),
486+
std::make_move_iterator(actualAsts->end()));
487+
actualAsts->swap(generatedActualAsts);
488+
}
489+
}
490+
491+
492+
431493
CallInfo CallInfo::create(Context* context,
432494
const Call* call,
433495
const ResolutionResultByPostorderID& byPostorder,
434496
bool raiseErrors,
435497
std::vector<const uast::AstNode*>* actualAsts,
436498
ID* moduleScopeId,
437-
UniqueString rename) {
499+
UniqueString rename,
500+
bool* outIncompleteTypeConstructor) {
438501

439502
// Pieces of the CallInfo we need to prepare.
440503
UniqueString name;
@@ -497,6 +560,14 @@ CallInfo CallInfo::create(Context* context,
497560
makeCallToDotThis();
498561
} else {
499562
calledType = *calledExprType;
563+
prependActualFromSubstitutions(context, calledType, actuals, actualAsts);
564+
}
565+
} else if (calledExprType && calledExprType->isUnknown() && calledExprType->isType()) {
566+
// it looks like we're invoking a type constructor, but we don't know
567+
// what type. We don't know enough to set calledType, and the call
568+
// shouldn't be resolved. Signal this via outIncompleteTypeConstructor.
569+
if (outIncompleteTypeConstructor != nullptr) {
570+
*outIncompleteTypeConstructor = true;
500571
}
501572
} else if (!call->isOpCall() && dotReceiverType &&
502573
isKindForMethodReceiver(dotReceiverType->kind())) {

frontend/lib/resolution/signature-checks.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,18 +65,19 @@ static const bool& checkSignatureQuery(Context* context,
6565
(isInQualifier(thisIntent) && sig->formalType(0).type()->isClassType()))) {
6666
context->error(errId, "Bad 'this' intent for init=");
6767
}
68-
bool rhsIntentGenericOrRef = isGenericQualifier(rhsIntent) ||
69-
isRefQualifier(rhsIntent);
68+
bool rhsIntentGenericOrRefOrParam = isGenericQualifier(rhsIntent) ||
69+
isRefQualifier(rhsIntent) ||
70+
rhsIntent == Qualifier::PARAM;
7071
// check the intent of the rhs argument
7172
if (sig->formalType(0).type() == sig->formalType(1).type()) {
7273
// same-type case: only const/default/ref/const ref RHS is allowed
7374
// allow 'in' for borrowed RHS, such as when defining init= for a class.
74-
if (!rhsIntentGenericOrRef && !sig->formalType(0).type()->isClassType()) {
75+
if (!rhsIntentGenericOrRefOrParam && !sig->formalType(0).type()->isClassType()) {
7576
context->error(errId, "Bad intent for same-type init= other argument");
7677
}
7778
} else {
7879
// different-type case: RHS can be 'in' intent in addition
79-
if (!(rhsIntentGenericOrRef || isInQualifier(rhsIntent))) {
80+
if (!(rhsIntentGenericOrRefOrParam || isInQualifier(rhsIntent))) {
8081
context->error(errId, "Bad intent for cross-type init= other argument");
8182
}
8283
}

frontend/lib/types/CompositeType.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,21 @@ static void stringifySortedSubstitutions(std::ostream& ss,
5959
const std::vector<SubstitutionPair>& sorted,
6060
bool& emittedField) {
6161
for (const auto& sub : sorted) {
62+
if (sub.second.isParam() && sub.second.param() == nullptr &&
63+
stringKind == StringifyKind::CHPL_SYNTAX) {
64+
// we know the type of the param value, but not the value. Production
65+
// doesn't print it.
66+
continue;
67+
}
68+
6269
if (emittedField) ss << ", ";
6370

6471
if (stringKind != StringifyKind::CHPL_SYNTAX) {
6572
sub.first.stringify(ss, stringKind);
6673
ss << ":";
6774
sub.second.stringify(ss, stringKind);
6875
} else {
69-
if (sub.second.isType() || (sub.second.isParam() && sub.second.param() == nullptr)) {
76+
if (sub.second.isType()) {
7077
sub.second.type()->stringify(ss, stringKind);
7178
} else if (sub.second.isParam()) {
7279
sub.second.param()->stringify(ss, stringKind);

frontend/test/resolution/testInitSemantics.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2306,6 +2306,51 @@ static void testInitEqRecursionError() {
23062306
assert(guard.realizeErrors() > 0);
23072307
}
23082308

2309+
static void testPartialTypeInitEq() {
2310+
std::string prog = R"""(
2311+
record R {
2312+
type T;
2313+
param p : T;
2314+
}
2315+
2316+
proc R.init=(param p : this.type.T) {
2317+
this.T = p.type;
2318+
this.p = p;
2319+
}
2320+
2321+
operator :(from, type t: R(from.type)) {
2322+
var tmp: t = from;
2323+
return tmp;
2324+
}
2325+
2326+
type root = R(?);
2327+
var r1 : R(string, ?) = "hi";
2328+
var r2 : R(int, ?) = 1234;
2329+
)""";
2330+
2331+
auto context = buildStdContext();
2332+
ErrorGuard guard(context);
2333+
auto vars = resolveTypesOfVariables(context, prog, {"root", "r1", "r2"});
2334+
2335+
auto rootType = vars["root"].type();
2336+
assert(rootType && rootType->isRecordType());
2337+
assert(rootType->toRecordType()->instantiatedFrom() == nullptr);
2338+
2339+
auto r1Type = vars["r1"].type();
2340+
assert(r1Type && r1Type->isRecordType());
2341+
ensureSubs(context, r1Type->toCompositeType(), {
2342+
{"T", QualifiedType(QualifiedType::TYPE, RecordType::getStringType(context))},
2343+
{"p", QualifiedType::makeParamString(context, "hi")}
2344+
});
2345+
2346+
auto r2Type = vars["r2"].type();
2347+
assert(r2Type && r2Type->isRecordType());
2348+
ensureSubs(context, r2Type->toCompositeType(), {
2349+
{"T", QualifiedType(QualifiedType::TYPE, IntType::get(context, 64))},
2350+
{"p", QualifiedType::makeParamInt(context, 1234)}
2351+
});
2352+
}
2353+
23092354
// TODO:
23102355
// - test using defaults for types and params
23112356
// - also in conditionals
@@ -2372,6 +2417,8 @@ int main() {
23722417
testInitEqRecursion();
23732418
testInitEqRecursionError();
23742419

2420+
testPartialTypeInitEq();
2421+
23752422
return 0;
23762423
}
23772424

0 commit comments

Comments
 (0)