Skip to content

Commit

Permalink
Implement copyability and assignability primitives in dyno (chapel-la…
Browse files Browse the repository at this point in the history
…ng#24058)

Implement `PRIM_IS_COPYABLE`, `PRIM_IS_CONST_COPYABLE`,
`PRIM_IS_ASSIGNABLE`, and `PRIM_IS_CONST_ASSIGNABLE` in Dyno.

Logic is directly ported from the implementation of these in
`preFold.cpp` in the production compiler. Queries instead of flags are
used to cache computed results for composite types.

The primitives are defined in Dyno to return `false` for
non-fully-defaulted generic composite types, and production behavior is
changed (from an unclear error previously) to match. Also includes
simplifying the production tests of these primitives, so it was easier
to identify cases to test in Dyno.

Future work:
- Create and use an iterator for `TupleType`'s element types. Similarly
for `ResolvedFields` if desired, maybe not if not used elsewhere.
- Gracefully handle resolving an `init=` or `=` that contains a
`compilerError` (treat it as not an option for the logic of
copyability/assignability).
- Handle `isDefaultInitializable` on non-fully-defaulted generic
composite types. Probably should be handled similarly to what we do with
these for copyability/assignability. It looks like it already tries to
do so but errors when I try it.
- If desired, get both copyable and assignable information in a single
query, expanding `CopyableAssignableInfo` to contain both at once. This
would probably only be desirable if we have other queries that also
involve test-resolving an `init=` and `=`, see
[discussion](chapel-lang#24058 (comment)).

[reviewed by @mppf , thanks!]

Testing:
- [x] dyno tests
- [x] paratest
  • Loading branch information
riftEmber authored Dec 18, 2023
2 parents d58edb1 + 4778a30 commit adc4dfe
Show file tree
Hide file tree
Showing 35 changed files with 906 additions and 516 deletions.
8 changes: 6 additions & 2 deletions compiler/resolution/preFold.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,9 @@ static void setRecordCopyableFlags(AggregateType* at) {
if (!ts->hasFlag(FLAG_TYPE_INIT_EQUAL_FROM_CONST) &&
!ts->hasFlag(FLAG_TYPE_INIT_EQUAL_FROM_REF)) {

if (isNonNilableOwned(at)) {
if (at->isGeneric() && !at->isGenericWithDefaults()) {
// do nothing for this case
} else if (isNonNilableOwned(at)) {
// do nothing for this case
} else if (Type* eltType = arrayElementType(at)) {
if (AggregateType* eltTypeAt = toAggregateType(eltType)) {
Expand Down Expand Up @@ -308,7 +310,9 @@ static void setRecordAssignableFlags(AggregateType* at) {
if (!ts->hasFlag(FLAG_TYPE_ASSIGN_FROM_CONST) &&
!ts->hasFlag(FLAG_TYPE_ASSIGN_FROM_REF)) {

if (isNonNilableOwned(at)) {
if (at->isGeneric() && !at->isGenericWithDefaults()) {
// do nothing for this case
} else if (isNonNilableOwned(at)) {
// do nothing for this case
} else if (Type* eltType = arrayElementType(at)) {

Expand Down
15 changes: 15 additions & 0 deletions frontend/include/chpl/resolution/resolution-queries.h
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,12 @@ resolveGeneratedCallInMethod(Context* context,
const PoiScope* inPoiScope,
types::QualifiedType implicitReceiver);

// tries to resolve an (unambiguous) init=
const TypedFnSignature* tryResolveInitEq(Context* context,
const uast::AstNode* astForScopeOrErr,
const types::Type* lhsType,
const types::Type* rhsType,
const PoiScope* poiScope = nullptr);

/**
Given a type 't', compute whether or not 't' is default initializable.
Expand All @@ -389,6 +395,15 @@ resolveGeneratedCallInMethod(Context* context,
*/
bool isTypeDefaultInitializable(Context* context, const types::Type* t);

/**
Determine whether type 't' is copyable/assignable from const or/and from ref.
When checkCopyable is true, this checks copyability, and for false checks
assignability.
*/
CopyableAssignableInfo getCopyOrAssignableInfo(Context* context,
const types::Type* t,
bool checkCopyable);

/**
Determine the types of various compiler-generated globals, which depend
on the settings the compiler / Dyno was started with.
Expand Down
54 changes: 54 additions & 0 deletions frontend/include/chpl/resolution/resolution-types.h
Original file line number Diff line number Diff line change
Expand Up @@ -2057,6 +2057,60 @@ class ResolvedParamLoop {
/** See the documentation for types::CompositeType::SubstitutionsMap. */
using SubstitutionsMap = types::CompositeType::SubstitutionsMap;

// Represents result info on either a type's copyability or assignability, from
// ref and/or from const.
struct CopyableAssignableInfo {
private:
bool fromConst_ = false;
bool fromRef_ = false;

CopyableAssignableInfo(bool fromConst, bool fromRef)
: fromConst_(fromConst), fromRef_(fromRef) {
assert(!fromConst || fromRef);
}

public:
CopyableAssignableInfo() {}

bool isFromConst() const { return fromConst_; }
bool isFromRef() const { return fromRef_; }

static CopyableAssignableInfo fromConst() {
return CopyableAssignableInfo(true, true);
}

static CopyableAssignableInfo fromRef() {
return CopyableAssignableInfo(false, true);
}

static CopyableAssignableInfo fromNone() {
return CopyableAssignableInfo(false, false);
}

// Set this to the "minimum" copyability between this and other.
void intersectWith(const CopyableAssignableInfo& other) {
fromConst_ &= other.fromConst_;
fromRef_ &= other.fromRef_;
}

bool operator==(const CopyableAssignableInfo& other) const {
return fromConst_ == other.fromConst_ &&
fromRef_ == other.fromRef_;
}
bool operator!=(const CopyableAssignableInfo& other) const {
return !(*this == other);
}
void swap(CopyableAssignableInfo& other) {
std::swap(fromConst_, other.fromConst_);
std::swap(fromRef_, other.fromRef_);
}
static bool update(CopyableAssignableInfo& keep,
CopyableAssignableInfo& addin) {
return defaultUpdate(keep, addin);
}
void mark(Context* context) const {
}
};

} // end namespace resolution

Expand Down
13 changes: 13 additions & 0 deletions frontend/include/chpl/types/QualifiedType.h
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,19 @@ class QualifiedType final {
bool isImmutable() const {
return uast::isImmutableQualifier(kind_);
}
/**
Returns true if the value is a reference, whether constant or mutable.
*/
bool isRef() const {
return uast::isRefQualifier(kind_);
}
/**
Returns true if the value is an in-intent formal, whether constant or
mutable.
*/
bool isIn() const {
return uast::isInQualifier(kind_);
}

/**
Returns true if the kind is one of the non-concrete intents
Expand Down
46 changes: 9 additions & 37 deletions frontend/lib/resolution/Resolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -930,38 +930,6 @@ bool Resolver::checkForKindError(const AstNode* typeForErr,
}


const Type* Resolver::tryResolveCrossTypeInitEq(const AstNode* ast,
QualifiedType lhsType,
QualifiedType rhsType) {

const Type* t = lhsType.type();
if (t->isRecordType() || t->isUnionType()) {
// use the regular VAR kind for this query
// (don't want a type-expr lhsType to be considered a TYPE here)
lhsType = QualifiedType(QualifiedType::VAR, lhsType.type());
rhsType = QualifiedType(QualifiedType::VAR, rhsType.type());

std::vector<CallInfoActual> actuals;
actuals.push_back(CallInfoActual(lhsType, USTR("this")));
actuals.push_back(CallInfoActual(rhsType, UniqueString()));
auto ci = CallInfo (/* name */ USTR("init="),
/* calledType */ lhsType,
/* isMethodCall */ true,
/* hasQuestionArg */ false,
/* isParenless */ false,
actuals);
const Scope* scope = scopeForId(context, ast->id());
auto c = resolveGeneratedCall(context, ast, ci, scope, poiScope);
if (c.mostSpecific().isEmpty()) {
return nullptr;
} else {
return lhsType.type(); // TODO: this might need to be an instantiation
}
}

return nullptr;
}

static const CompositeType*
getTypeWithCustomInfer(Context* context, const Type* type) {
if (auto rec = type->getCompositeType()) {
Expand Down Expand Up @@ -1057,15 +1025,19 @@ QualifiedType Resolver::getTypeForDecl(const AstNode* declForErr,
QualifiedType(declKind, declaredType.type()));
if (!got.passes()) {
// For a record/union, check for an init= from the provided type
const Type* foundInitEqResultType =
tryResolveCrossTypeInitEq(declForErr, declaredType, initExprType);

if (!foundInitEqResultType) {
const bool isRecordOrUnion = (declaredType.type()->isRecordType() ||
declaredType.type()->isUnionType());
if (!(isRecordOrUnion &&
tryResolveInitEq(context, declForErr, declaredType.type(),
initExprType.type(), poiScope))) {
CHPL_REPORT(context, IncompatibleTypeAndInit, declForErr, typeForErr,
initForErr, declaredType.type(), initExprType.type());
typePtr = ErroneousType::get(context);
} else {
typePtr = foundInitEqResultType;
// TODO: this might need to be an instantiation
// when we init= to create a type on a generic declared type, we want
// the type produced by the init= call
typePtr = declaredType.type();
}
} else if (!got.instantiates()) {
// use the declared type since no conversion/promotion was needed
Expand Down
6 changes: 0 additions & 6 deletions frontend/lib/resolution/Resolver.h
Original file line number Diff line number Diff line change
Expand Up @@ -292,12 +292,6 @@ struct Resolver {
types::QualifiedType declaredType,
types::QualifiedType initExprType);

// helper for getTypeForDecl
// tries to resolve an init= that initializes one type from another
const types::Type* tryResolveCrossTypeInitEq(const uast::AstNode* ast,
types::QualifiedType lhsType,
types::QualifiedType rhsType);

const types::Type* computeCustomInferType(const uast::AstNode* initExpr,
const types::CompositeType* ct);

Expand Down
17 changes: 8 additions & 9 deletions frontend/lib/resolution/default-functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,18 +76,22 @@ areOverloadsPresentInDefiningScope(Context* context, const Type* type,

auto haveQt = QualifiedType(QualifiedType::VAR, type);

// loop through IDs and see if any are methods on the same type
// loop through IDs and see if any are methods or operators (method or
// standalone) on the same type
for (auto& ids : vec) {
for (const auto& id : ids) {
auto node = parsing::idToAst(context, id);
CHPL_ASSERT(node);

if (auto fn = node->toFunction()) {
if (fn->isMethod()) {
if (fn->isMethod() || fn->kind() == Function::Kind::OPERATOR) {
ResolutionResultByPostorderID r;
auto vis = Resolver::createForInitialSignature(context, fn, r);
fn->thisFormal()->traverse(vis);
auto receiverQualType = vis.byPostorder.byAst(fn->thisFormal()).type();
// use receiver for method, first formal for standalone operator
auto checkFormal =
(fn->isMethod() ? fn->thisFormal() : fn->formal(0));
checkFormal->traverse(vis);
auto receiverQualType = vis.byPostorder.byAst(checkFormal).type();

// return true if the receiver type matches or
// if the receiver type is a generic type and we have
Expand All @@ -96,11 +100,6 @@ areOverloadsPresentInDefiningScope(Context* context, const Type* type,
if (result.passes() && !result.converts() && !result.promotes()) {
return true;
}
} else if (fn->kind()==Function::Kind::OPERATOR) {
// TODO: There should probably be some more checks happening in here,
// but unsure of what they should be currently and this seems to work
// in basic testing.
return true;
}
}
}
Expand Down
36 changes: 32 additions & 4 deletions frontend/lib/resolution/prims.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,31 @@ primComplexGetComponent(Context* context, const CallInfo& ci) {
return ret;
}

static QualifiedType primFamilyCopyableAssignable(Context* context,
const CallInfo& ci,
const PrimitiveTag prim) {
const bool checkCopyable =
(prim == PRIM_IS_COPYABLE || prim == PRIM_IS_CONST_COPYABLE);
const bool checkAssignable =
(prim == PRIM_IS_ASSIGNABLE || prim == PRIM_IS_CONST_ASSIGNABLE);
CHPL_ASSERT((checkCopyable || checkAssignable) &&
"incorrect primitive for this handler");

if (ci.numActuals() != 1) return QualifiedType();
auto t = ci.actual(0).type().type();

auto info = getCopyOrAssignableInfo(context, t, checkCopyable);

// copyable/assignable from const is stricter than from ref
const bool isFromRefOk =
(prim == PRIM_IS_COPYABLE || prim == PRIM_IS_ASSIGNABLE);
const bool isCopyableOrAssignable =
info.isFromConst() || (info.isFromRef() && isFromRefOk);

return QualifiedType(QualifiedType::PARAM, BoolType::get(context),
BoolParam::get(context, isCopyableOrAssignable));
}

CallResolutionResult resolvePrimCall(Context* context,
const PrimCall* call,
const CallInfo& ci,
Expand Down Expand Up @@ -705,6 +730,13 @@ CallResolutionResult resolvePrimCall(Context* context,
type = primFieldByNum(context, ci);
break;

case PRIM_IS_COPYABLE:
case PRIM_IS_CONST_COPYABLE:
case PRIM_IS_ASSIGNABLE:
case PRIM_IS_CONST_ASSIGNABLE:
type = primFamilyCopyableAssignable(context, ci, prim);
break;

case PRIM_ITERATOR_RECORD_FIELD_VALUE_BY_FORMAL:
case PRIM_IS_GENERIC_TYPE:
case PRIM_IS_CLASS_TYPE:
Expand All @@ -720,10 +752,6 @@ CallResolutionResult resolvePrimCall(Context* context,
case PRIM_IS_BORROWED_CLASS_TYPE:
case PRIM_IS_ABS_ENUM_TYPE:
case PRIM_IS_POD:
case PRIM_IS_COPYABLE:
case PRIM_IS_CONST_COPYABLE:
case PRIM_IS_ASSIGNABLE:
case PRIM_IS_CONST_ASSIGNABLE:
case PRIM_HAS_DEFAULT_VALUE: // param uses in module code
case PRIM_NEEDS_AUTO_DESTROY: // param uses in module code
CHPL_UNIMPL("various primitives");
Expand Down
Loading

0 comments on commit adc4dfe

Please sign in to comment.