Skip to content

Commit

Permalink
Dyno: add non-param casts to and from enums (chapel-lang#24563)
Browse files Browse the repository at this point in the history
This PR adds casts to and from enums when the enum / integer is not a
param value. It does so by mimicking what the production compiler does:
building a single, generic "from" function, and a single, generic "to"
function. The functions look something like this:

```Chapel
operator :(from: theEnum, type to: integral)
operator :(from: integral, type to: theEnum)
```

This requires some adjustments:
* An easy initial adjustment is that the existing framework relies on
the first formal to determine whether a compiler-generated candidate
should be considered. However, in the above, the cast _to_ an enum uses
a generic integral type as its "receiver". Thus, I make changes to
support finding compiler-generated candidates for general binary
operation.
* A more significant adjustment is that to make the functions generic,
we need to allow them to be instantiated. However, instantiation until
this point has relied on the ability to find ASTs associated with each
formal. In this PR I adjust the instantiation code to no longer have
this assumption.

With this PR, @benharsh's example resolves (when the standard library is
enabled):

<details>
<summary>See motivatign example</summary>

```Chapel
enum numbers {
  zero = 0,
  one = 1,
  two = 2,
  three = 3
}

proc foo(param num : int) {
  select num:numbers { // no matching candidates
    when numbers.zero do return 42;
    when numbers.one do return 5.0;
    when numbers.two do return "hello";
  }

  return b"hello";
}

proc baz(num: int) {
  select num:numbers { // no matching candidates
    when numbers.zero do return 0;
    when numbers.one do return 1;
    when numbers.two do return 2;
    otherwise return 3;
  }
}

var x = foo(2);

var y = baz(2);
````
</details>

Reviewed by @benharsh -- thanks!

## Testing
- [x] dyno tests
- [x] paratest
  • Loading branch information
DanilaFe authored Mar 7, 2024
2 parents 8c75c06 + 1f91e6a commit af9441a
Show file tree
Hide file tree
Showing 7 changed files with 463 additions and 121 deletions.
3 changes: 2 additions & 1 deletion frontend/include/chpl/resolution/resolution-types.h
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ class UntypedFnSignature {
idTag == uast::asttags::Record ||
idTag == uast::asttags::Tuple ||
idTag == uast::asttags::Union ||
idTag == uast::asttags::Variable);
idTag == uast::asttags::Variable ||
idTag == uast::asttags::Enum);
}

static const owned<UntypedFnSignature>&
Expand Down
10 changes: 5 additions & 5 deletions frontend/include/chpl/types/UintType.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,16 @@ class UintType final : public PrimitiveType {

static const owned<UintType>& getUintType(Context* context, int bitwidth);

/** what is stored in bitwidth_ for the default 'uint'? */
static int defaultBitwidth() {
return 64;
}

public:
~UintType() = default;

static const UintType* get(Context* context, int bitwidth);

/** what is stored in bitwidth_ for the default 'uint'? */
static int defaultBitwidth() {
return 64;
}

int bitwidth() const override {
return bitwidth_;
}
Expand Down
112 changes: 112 additions & 0 deletions frontend/lib/resolution/default-functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,91 @@ getCompilerGeneratedMethodQuery(Context* context, const Type* type,
return QUERY_END(result);
}

static void
setupGeneratedEnumCastFormals(Context* context,
const EnumType* enumType,
std::vector<UntypedFnSignature::FormalDetail>& ufsFormals,
std::vector<QualifiedType>& formalTypes,
bool isFromCast /* otherwise, it's a "to" cast */) {
const Type* fromType;
const Type* toType;

if (isFromCast) {
fromType = enumType;
toType = AnyIntegralType::get(context);
} else {
fromType = AnyIntegralType::get(context);
toType = enumType;
}

auto fromQt = QualifiedType(QualifiedType::DEFAULT_INTENT, fromType);
auto toQt = QualifiedType(QualifiedType::TYPE, toType);

auto ufsFrom =
UntypedFnSignature::FormalDetail(UniqueString::get(context, "from"),
false, nullptr);
ufsFormals.push_back(std::move(ufsFrom));
auto ufsTo =
UntypedFnSignature::FormalDetail(UniqueString::get(context, "to"),
false, nullptr);
ufsFormals.push_back(std::move(ufsTo));

formalTypes.push_back(fromQt);
formalTypes.push_back(toQt);
}

static const TypedFnSignature*
generateToOrFromCastForEnum(Context* context,
const types::QualifiedType& lhs,
const types::QualifiedType& rhs,
bool isFromCast /* otherwise, it's a "to" cast */) {
std::vector<UntypedFnSignature::FormalDetail> ufsFormals;
std::vector<QualifiedType> formalTypes;

auto enumType = isFromCast ? lhs.type()->toEnumType() : rhs.type()->toEnumType();

if (enumType->isAbstract()) return nullptr;

setupGeneratedEnumCastFormals(context, enumType, ufsFormals, formalTypes,
isFromCast);

auto ufs = UntypedFnSignature::get(context,
/*id*/ enumType->id(),
/*name*/ USTR(":"),
/*isMethod*/ false,
/*isTypeConstructor*/ false,
/*isCompilerGenerated*/ true,
/*throws*/ false,
/*idTag*/ parsing::idToTag(context, enumType->id()),
/*kind*/ uast::Function::Kind::OPERATOR,
/*formals*/ std::move(ufsFormals),
/*whereClause*/ nullptr);

auto ret = TypedFnSignature::get(context,
ufs,
std::move(formalTypes),
TypedFnSignature::WHERE_NONE,
/* needsInstantiation */ true,
/* instantiatedFrom */ nullptr,
/* parentFn */ nullptr,
/* formalsInstantiated */ Bitmap());
return ret;
}

static const TypedFnSignature*
generateCastFromEnum(Context* context,
const types::QualifiedType& lhs,
const types::QualifiedType& rhs) {
return generateToOrFromCastForEnum(context, lhs, rhs, /*isFromCast*/ true);
}

static const TypedFnSignature*
generateCastToEnum(Context* context,
const types::QualifiedType& lhs,
const types::QualifiedType& rhs) {
return generateToOrFromCastForEnum(context, lhs, rhs, /*isFromCast*/ false);
}

/**
Given a type and a UniqueString representing the name of a method,
determine if the type needs a method with such a name to be
Expand All @@ -733,6 +818,33 @@ getCompilerGeneratedMethod(Context* context, const Type* type,
return getCompilerGeneratedMethodQuery(context, type, name, parenless);
}

static const TypedFnSignature* const&
getCompilerGeneratedBinaryOpQuery(Context* context,
types::QualifiedType lhs,
types::QualifiedType rhs,
UniqueString name) {
QUERY_BEGIN(getCompilerGeneratedBinaryOpQuery, context, lhs, rhs, name);

const TypedFnSignature* result = nullptr;
if (name == USTR(":")) {
if (lhs.type() && lhs.type()->isEnumType()) {
result = generateCastFromEnum(context, lhs, rhs);
} else if (rhs.type() && rhs.type()->isEnumType()) {
result = generateCastToEnum(context, lhs, rhs);
}
}

return QUERY_END(result);
}

const TypedFnSignature*
getCompilerGeneratedBinaryOp(Context* context,
const types::QualifiedType lhs,
const types::QualifiedType rhs,
UniqueString name) {
return getCompilerGeneratedBinaryOpQuery(context, lhs, rhs, name);
}


} // end namespace resolution
} // end namespace chpl
14 changes: 14 additions & 0 deletions frontend/lib/resolution/default-functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,20 @@ const TypedFnSignature*
getCompilerGeneratedMethod(Context* context, const types::Type* type,
UniqueString name, bool parenless);

/**
Given the name of a binary operation and the types of its operands,
determine if the compiler needs to provide a generated implementation,
and if so, generates and returns a TypedFnSignature representing the
generated binary operation.
If no operation was generated, returns nullptr.
*/
const TypedFnSignature*
getCompilerGeneratedBinaryOp(Context* context,
const types::QualifiedType lhs,
const types::QualifiedType rhs,
UniqueString name);


} // end namespace resolution
} // end namespace chpl
Expand Down
Loading

0 comments on commit af9441a

Please sign in to comment.