diff --git a/frontend/include/chpl/resolution/resolution-types.h b/frontend/include/chpl/resolution/resolution-types.h index 3f1b1fa5fae1..5423f5685ee6 100644 --- a/frontend/include/chpl/resolution/resolution-types.h +++ b/frontend/include/chpl/resolution/resolution-types.h @@ -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& diff --git a/frontend/include/chpl/types/UintType.h b/frontend/include/chpl/types/UintType.h index caa773331d3f..411ba345e8a4 100644 --- a/frontend/include/chpl/types/UintType.h +++ b/frontend/include/chpl/types/UintType.h @@ -45,16 +45,16 @@ class UintType final : public PrimitiveType { static const owned& 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_; } diff --git a/frontend/lib/resolution/default-functions.cpp b/frontend/lib/resolution/default-functions.cpp index 452acaebb673..f78bdd7a0d45 100644 --- a/frontend/lib/resolution/default-functions.cpp +++ b/frontend/lib/resolution/default-functions.cpp @@ -719,6 +719,91 @@ getCompilerGeneratedMethodQuery(Context* context, const Type* type, return QUERY_END(result); } +static void +setupGeneratedEnumCastFormals(Context* context, + const EnumType* enumType, + std::vector& ufsFormals, + std::vector& 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 ufsFormals; + std::vector 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 @@ -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 diff --git a/frontend/lib/resolution/default-functions.h b/frontend/lib/resolution/default-functions.h index f4c3659732f5..414b8719691f 100644 --- a/frontend/lib/resolution/default-functions.h +++ b/frontend/lib/resolution/default-functions.h @@ -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 diff --git a/frontend/lib/resolution/resolution-queries.cpp b/frontend/lib/resolution/resolution-queries.cpp index d00f1bc25fda..4482667e8c7f 100644 --- a/frontend/lib/resolution/resolution-queries.cpp +++ b/frontend/lib/resolution/resolution-queries.cpp @@ -1667,20 +1667,23 @@ static QualifiedType getVarArgTupleElemType(const QualifiedType& varArgType) { } } -static Resolver createResolverForFnOrAd(Context* context, - const Function* fn, - const AggregateDecl* ad, - const SubstitutionsMap& substitutions, - const PoiScope* poiScope, - ResolutionResultByPostorderID& r) { +static Resolver createResolverForAst(Context* context, + const Function* fn, + const AggregateDecl* ad, + const Enum* ed, + const SubstitutionsMap& substitutions, + const PoiScope* poiScope, + ResolutionResultByPostorderID& r) { if (fn != nullptr) { return Resolver::createForInstantiatedSignature(context, fn, substitutions, poiScope, r); - } else { - CHPL_ASSERT(ad != nullptr); + } else if (ad != nullptr) { return Resolver::createForInstantiatedSignatureFields(context, ad, substitutions, poiScope, r); + } else { + CHPL_ASSERT(ed != nullptr); + return Resolver::createForEnumElements(context, ed, r); } } @@ -1740,11 +1743,13 @@ ApplicabilityResult instantiateSignature(Context* context, const AstNode* ast = nullptr; const Function* fn = nullptr; const AggregateDecl* ad = nullptr; + const Enum* ed = nullptr; if (!untypedSignature->id().isEmpty()) { ast = parsing::idToAst(context, untypedSignature->id()); fn = ast->toFunction(); ad = ast->toAggregateDecl(); + ed = ast->toEnum(); } const TypedFnSignature* parentFnTyped = nullptr; @@ -1766,13 +1771,22 @@ ApplicabilityResult instantiateSignature(Context* context, Bitmap formalsInstantiated; int formalIdx = 0; + // this vector is used when creating a typed signature below, after checking + // instantiations. For functions and aggregates, it's populated from + // substitutions after they are computed formal-by-formal below. + // + // For compiler-generated candidates that aren't tied to functions or records, + // we don't have an AST, so we can't store anything into substitutions. + // Instead, we'll populate the formal types right away. + std::vector formalTypes; + bool instantiateVarArgs = false; std::vector varargsTypes; int varArgIdx = -1; ResolutionResultByPostorderID r; - auto visitor = createResolverForFnOrAd(context, fn, ad, substitutions, - poiScope, r); + auto visitor = createResolverForAst(context, fn, ad, ed, substitutions, + poiScope, r); QualifiedType varArgType; for (const FormalActual& entry : faMap.byFormals()) { @@ -1796,9 +1810,13 @@ ApplicabilityResult instantiateSignature(Context* context, varArgType = r.byAst(formal).type(); } formalType = getVarArgTupleElemType(varArgType); - } else { + } else if (formal) { formal->traverse(visitor); formalType = getProperFormalType(r, entry, ad, formal); + } else { + // without a formal AST, assume that the originally provided formal type + // is right. + formalType = entry.formalType(); } // note: entry.actualType can have type()==nullptr and UNKNOWN. @@ -1878,12 +1896,15 @@ ApplicabilityResult instantiateSignature(Context* context, } else { // add the substitution if we identified that we need to if (addSub) { - // add it to the substitutions map - substitutions.insert({entry.formal()->id(), useType}); - // Explicitly override the type in the resolver to make it available - // to later fields without re-visiting and re-constructing the resolver. - // TODO: is this too hacky? - r.byAst(entry.formal()).setType(useType); + if (formal) { + // add it to the substitutions map + substitutions.insert({entry.formal()->id(), useType}); + // Explicitly override the type in the resolver to make it available + // to later fields without re-visiting and re-constructing the resolver. + // TODO: is this too hacky? + r.byAst(entry.formal()).setType(useType); + } + // note that a substitution was used here if ((size_t) formalIdx >= formalsInstantiated.size()) { formalsInstantiated.resize(sig->numFormals()); @@ -1894,6 +1915,12 @@ ApplicabilityResult instantiateSignature(Context* context, formalIdx++; } + if (!formal && ed) { + // we're in an enum-based generated method; note the type in + // formalTypes. + formalTypes.push_back(useType); + } + // At this point, we have computed the instantiated type for this // formal. However, what we're still missing some information, // and furthermore, we have not enforced type query constraints. @@ -1906,7 +1933,7 @@ ApplicabilityResult instantiateSignature(Context* context, visitor.resolveTypeQueries(te, useType); } } - } else { + } else if (formal) { // Substitutions have been updated; re-run resolution to get better // intents, vararg info, and to extract type query info. formal->traverse(visitor); @@ -1918,34 +1945,35 @@ ApplicabilityResult instantiateSignature(Context* context, // using substitutions, and to preserve previously computed type query // info. This way, we'll get as output the type expression's QualifiedType // which incorporates type query info. - if (auto vld = formal->toVarLikeDecl()) { - if (vld->typeExpression()) { - visitor.ignoreSubstitutionFor = formal; - visitor.skipTypeQueries = true; + if (formal) { + if (auto vld = formal->toVarLikeDecl()) { + if (vld->typeExpression()) { + visitor.ignoreSubstitutionFor = formal; + visitor.skipTypeQueries = true; + } + } + formal->traverse(visitor); + auto qFormalType = getProperFormalType(r, entry, ad, formal); + if (entry.isVarArgEntry()) { + // We only need to canPass the tuple element types. + qFormalType = getVarArgTupleElemType(qFormalType); + } else { + // Explicitly override the type in the resolver to what we have found it + // to be before the type-query-aware traversal. + r.byAst(entry.formal()).setType(formalType); } - } - formal->traverse(visitor); - auto qFormalType = getProperFormalType(r, entry, ad, formal); - - if (entry.isVarArgEntry()) { - // We only need to canPass the tuple element types. - qFormalType = getVarArgTupleElemType(qFormalType); - } else { - // Explicitly override the type in the resolver to what we have found it - // to be before the type-query-aware traversal. - r.byAst(entry.formal()).setType(formalType); - } - auto checkType = !useType.isUnknown() ? useType : formalType; - // With the type and query-aware type known, make sure that they're compatible - auto passResult = canPass(context, checkType, qFormalType); - if (!passResult.passes()) { - // Type query constraints were not satisfied - return ApplicabilityResult::failure(sig, passResult.reason(), entry.formalIdx()); - } + auto checkType = !useType.isUnknown() ? useType : formalType; + // With the type and query-aware type known, make sure that they're compatible + auto passResult = canPass(context, checkType, qFormalType); + if (!passResult.passes()) { + // Type query constraints were not satisfied + return ApplicabilityResult::failure(sig, passResult.reason(), entry.formalIdx()); + } - if (fn != nullptr && fn->isMethod() && fn->thisFormal() == formal) { - visitor.setCompositeType(qFormalType.type()->toCompositeType()); + if (fn != nullptr && fn->isMethod() && fn->thisFormal() == formal) { + visitor.setCompositeType(qFormalType.type()->toCompositeType()); + } } } @@ -1972,11 +2000,10 @@ ApplicabilityResult instantiateSignature(Context* context, } // use the existing signature if there were no substitutions - if (substitutions.size() == 0) { + if (substitutions.size() == 0 && formalTypes.size() == 0) { return ApplicabilityResult::success(sig); } - std::vector formalTypes; bool needsInstantiation = false; TypedFnSignature::WhereClauseResult where = TypedFnSignature::WHERE_NONE; @@ -2029,6 +2056,7 @@ ApplicabilityResult instantiateSignature(Context* context, // add formals according to the parent class type // 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); @@ -2045,6 +2073,9 @@ ApplicabilityResult instantiateSignature(Context* context, needsInstantiation = anyFormalNeedsInstantiation(context, formalTypes, untypedSignature, &substitutions); + } else if (ed) { + // Fine; formal types were stored into formalTypes earlier since we're + // considering a compiler-generated candidate on an enum. } else { CHPL_ASSERT(false && "case not handled"); } @@ -3298,15 +3329,14 @@ resolveFnCallForTypeCtor(Context* context, return mostSpecific; } -static void -considerCompilerGeneratedCandidates(Context* context, - const CallInfo& ci, - const Scope* inScope, - const PoiScope* inPoiScope, - CandidatesAndForwardingInfo& candidates) { - +static const TypedFnSignature* +considerCompilerGeneratedMethods(Context* context, + const CallInfo& ci, + const Scope* inScope, + const PoiScope* inPoiScope, + CandidatesAndForwardingInfo& candidates) { // only consider compiler-generated methods and opcalls, for now - if (!ci.isMethodCall() && !ci.isOpCall()) return; + if (!ci.isMethodCall() && !ci.isOpCall()) return nullptr; // fetch the receiver type info CHPL_ASSERT(ci.numActuals() >= 1); @@ -3317,13 +3347,58 @@ considerCompilerGeneratedCandidates(Context* context, // if not compiler-generated, then nothing to do if (!needCompilerGeneratedMethod(context, receiverType, ci.name(), ci.isParenless())) { - return; + return nullptr; } // get the compiler-generated function, may be generic auto tfs = getCompilerGeneratedMethod(context, receiverType, ci.name(), ci.isParenless()); CHPL_ASSERT(tfs); + return tfs; +} + +// not all compiler-generated procs are method. For instance, the compiler +// generates to-and-from integral casts for enums. In the to-casts, the +// receiver (or lhs) is an integral, not an enum. +// +// This helper serves to consider compiler-generated functions that can't +// be guessed based on the first argument. +static const TypedFnSignature* +considerCompilerGeneratedOperators(Context* context, + const CallInfo& ci, + const Scope* inScope, + const PoiScope* inPoiScope, + CandidatesAndForwardingInfo& candidates) { + if (!ci.isOpCall()) return nullptr; + + // Avoid invoking the query if we don't need a binary operation here. + if (ci.name() != USTR(":") || ci.numActuals() != 2) return nullptr; + + auto lhsType = ci.actual(0).type(); + auto rhsType = ci.actual(1).type(); + if (!(lhsType.type() && lhsType.type()->isEnumType()) && + !(rhsType.type() && rhsType.type()->isEnumType())) { + return nullptr; + } + + auto tfs = getCompilerGeneratedBinaryOp(context, lhsType, rhsType, ci.name()); + return tfs; +} + +static void +considerCompilerGeneratedCandidates(Context* context, + const CallInfo& ci, + const Scope* inScope, + const PoiScope* inPoiScope, + CandidatesAndForwardingInfo& candidates) { + const TypedFnSignature* tfs = nullptr; + + tfs = considerCompilerGeneratedMethods(context, ci, inScope, inPoiScope, candidates); + if (tfs == nullptr) { + tfs = considerCompilerGeneratedOperators(context, ci, inScope, inPoiScope, candidates); + } + + if (!tfs) return; // check if the initial signature matches auto faMap = FormalActualMap(tfs->untyped(), ci); @@ -3344,7 +3419,6 @@ considerCompilerGeneratedCandidates(Context* context, ci, poi); CHPL_ASSERT(instantiated.success()); - CHPL_ASSERT(instantiated.candidate()->untyped()->idIsFunction()); CHPL_ASSERT(instantiated.candidate()->instantiatedFrom()); candidates.addCandidate(instantiated.candidate()); diff --git a/frontend/lib/resolution/return-type-inference.cpp b/frontend/lib/resolution/return-type-inference.cpp index 2b829827d198..2b56d0609995 100644 --- a/frontend/lib/resolution/return-type-inference.cpp +++ b/frontend/lib/resolution/return-type-inference.cpp @@ -854,71 +854,78 @@ static bool helpComputeCompilerGeneratedReturnType(Context* context, } else if (untyped->name() == USTR("==")) { result = QualifiedType(QualifiedType::CONST_VAR, BoolType::get(context)); return true; + } else if (untyped->name() == USTR(":")) { + // Assume that compiler-generated casts are actually cast. + auto input = sig->formalType(0); + auto outputType = sig->formalType(1); + + result = QualifiedType(input.kind(), outputType.type()); + return true; } else if (untyped->idIsField() && untyped->isMethod()) { - // method accessor - compute the type of the field - QualifiedType ft = computeTypeOfField(context, - sig->formalType(0).type(), - untyped->id()); - if (ft.isType() || ft.isParam()) { - // return the type as-is (preserving param/type-ness) - result = ft; - } else if (ft.isConst()) { - // return a const ref - result = QualifiedType(QualifiedType::CONST_REF, ft.type()); - } else { - // return a ref - result = QualifiedType(QualifiedType::REF, ft.type()); - } - return true; - } else if (untyped->isMethod() && sig->formalType(0).type()->isDomainType()) { - auto dt = sig->formalType(0).type()->toDomainType(); - - if (untyped->name() == "idxType") { - result = dt->idxType(); - } else if (untyped->name() == "rank") { - // Can't use `RankType::rank` because `D.rank` is defined for associative - // domains, even though they don't have a matching substitution. - result = QualifiedType(QualifiedType::PARAM, - IntType::get(context, 64), - IntParam::get(context, dt->rankInt())); - } else if (untyped->name() == "stridable") { - result = dt->stridable(); - } else if (untyped->name() == "parSafe") { - result = dt->parSafe(); - } else if (untyped->name() == "isRectangular") { - auto val = BoolParam::get(context, dt->kind() == DomainType::Kind::Rectangular); - auto type = BoolType::get(context); - result = QualifiedType(QualifiedType::PARAM, type, val); - } else if (untyped->name() == "isAssociative") { - auto val = BoolParam::get(context, dt->kind() == DomainType::Kind::Associative); - auto type = BoolType::get(context); - result = QualifiedType(QualifiedType::PARAM, type, val); - } else { - CHPL_ASSERT(false && "unhandled compiler-generated domain method"); - return true; - } + // method accessor - compute the type of the field + QualifiedType ft = computeTypeOfField(context, + sig->formalType(0).type(), + untyped->id()); + if (ft.isType() || ft.isParam()) { + // return the type as-is (preserving param/type-ness) + result = ft; + } else if (ft.isConst()) { + // return a const ref + result = QualifiedType(QualifiedType::CONST_REF, ft.type()); + } else { + // return a ref + result = QualifiedType(QualifiedType::REF, ft.type()); + } + return true; + } else if (untyped->isMethod() && sig->formalType(0).type()->isDomainType()) { + auto dt = sig->formalType(0).type()->toDomainType(); + + if (untyped->name() == "idxType") { + result = dt->idxType(); + } else if (untyped->name() == "rank") { + // Can't use `RankType::rank` because `D.rank` is defined for associative + // domains, even though they don't have a matching substitution. + result = QualifiedType(QualifiedType::PARAM, + IntType::get(context, 64), + IntParam::get(context, dt->rankInt())); + } else if (untyped->name() == "stridable") { + result = dt->stridable(); + } else if (untyped->name() == "parSafe") { + result = dt->parSafe(); + } else if (untyped->name() == "isRectangular") { + auto val = BoolParam::get(context, dt->kind() == DomainType::Kind::Rectangular); + auto type = BoolType::get(context); + result = QualifiedType(QualifiedType::PARAM, type, val); + } else if (untyped->name() == "isAssociative") { + auto val = BoolParam::get(context, dt->kind() == DomainType::Kind::Associative); + auto type = BoolType::get(context); + result = QualifiedType(QualifiedType::PARAM, type, val); + } else { + CHPL_ASSERT(false && "unhandled compiler-generated domain method"); return true; - } else if (untyped->isMethod() && sig->formalType(0).type()->isArrayType()) { - auto at = sig->formalType(0).type()->toArrayType(); - - if (untyped->name() == "domain") { - result = QualifiedType(QualifiedType::CONST_REF, at->domainType().type()); - } else if (untyped->name() == "eltType") { - result = at->eltType(); - } else { - CHPL_ASSERT(false && "unhandled compiler-generated array method"); - } + } + return true; + } else if (untyped->isMethod() && sig->formalType(0).type()->isArrayType()) { + auto at = sig->formalType(0).type()->toArrayType(); - return true; - } else if (untyped->isMethod() && sig->formalType(0).type()->isTupleType() && - untyped->name() == "size") { - auto tup = sig->formalType(0).type()->toTupleType(); - result = QualifiedType(QualifiedType::PARAM, IntType::get(context, 0), IntParam::get(context, tup->numElements())); - return true; + if (untyped->name() == "domain") { + result = QualifiedType(QualifiedType::CONST_REF, at->domainType().type()); + } else if (untyped->name() == "eltType") { + result = at->eltType(); } else { - CHPL_ASSERT(false && "unhandled compiler-generated record method"); - return true; + CHPL_ASSERT(false && "unhandled compiler-generated array method"); } + + return true; + } else if (untyped->isMethod() && sig->formalType(0).type()->isTupleType() && + untyped->name() == "size") { + auto tup = sig->formalType(0).type()->toTupleType(); + result = QualifiedType(QualifiedType::PARAM, IntType::get(context, 0), IntParam::get(context, tup->numElements())); + return true; + } else { + CHPL_ASSERT(false && "unhandled compiler-generated record method"); + return true; + } } // returns 'true' if it was a case handled here & sets 'result' in that case diff --git a/frontend/test/resolution/testEnums.cpp b/frontend/test/resolution/testEnums.cpp index e34108559499..e4b2683a4686 100644 --- a/frontend/test/resolution/testEnums.cpp +++ b/frontend/test/resolution/testEnums.cpp @@ -373,6 +373,136 @@ static void test11() { assert(param1->toEnumParam()->value().postOrderId() == 5); } +static void test12() { + Context ctx; + auto context = &ctx; + ErrorGuard guard(context); + + // Production allows multiple constants to have the same numeric value. + // When casting backwards, the first matching constant is picked. + auto vars = resolveTypesOfVariables(context, + R"""( + enum color { + red = 0, + green = 1, + blue = 2, + gold = 3, + } + var redI: int = 0; + var greenU: uint = 1; + var blueI8: int(8) = 2; + var goldU8: uint(8) = 3; + var a = redI : color; + var b = greenU : color; + var c = blueI8 : color; + var d = goldU8 : color; + )""", {"a", "b", "c", "d"}); + + assert(vars.at("a").type()->isEnumType() && vars.at("a").type()->toEnumType()->name() == "color"); + assert(vars.at("b").type()->isEnumType() && vars.at("b").type()->toEnumType()->name() == "color"); + assert(vars.at("c").type()->isEnumType() && vars.at("c").type()->toEnumType()->name() == "color"); + assert(vars.at("d").type()->isEnumType() && vars.at("d").type()->toEnumType()->name() == "color"); +} + +static void test13() { + Context ctx; + auto context = &ctx; + ErrorGuard guard(context); + + // Production allows multiple constants to have the same numeric value. + // When casting backwards, the first matching constant is picked. + auto vars = resolveTypesOfVariables(context, + R"""( + enum color { + red = 0, + green = 1, + blue = 2, + gold = 3, + } + var red = color.red; + var a = red : int; + var b = red : uint; + var c = red : int(8); + var d = red : uint(8); + )""", {"red", "a", "b", "c", "d"}); + + assert(vars.at("red").type()->isEnumType()); + assert(vars.at("a").type()->isIntType() && vars.at("a").type()->toIntType()->bitwidth() == IntType::defaultBitwidth()); + assert(vars.at("b").type()->toUintType() && vars.at("b").type()->toUintType()->bitwidth() == UintType::defaultBitwidth()); + assert(vars.at("c").type()->isIntType() && vars.at("c").type()->toIntType()->bitwidth() == 8); + assert(vars.at("d").type()->isUintType() && vars.at("d").type()->toUintType()->bitwidth() == 8); +} + +static void test14() { + Context ctx; + auto context = &ctx; + ErrorGuard guard(context); + + // Production allows multiple constants to have the same numeric value. + // When casting backwards, the first matching constant is picked. + auto vars = resolveTypesOfVariables(context, + R"""( + enum color { + red, + green, + blue, + gold, + } + var redI: int = 0; + var greenU: uint = 1; + var blueI8: int(8) = 2; + var goldU8: uint(8) = 3; + var a = redI : color; + var b = greenU : color; + var c = blueI8 : color; + var d = goldU8 : color; + )""", {"a", "b", "c", "d"}); + + for (auto pair : vars) { + assert(pair.second.isErroneousType()); + } + + assert(guard.numErrors() == 4); + for (int i = 0; i < 4; i ++) { + assert(guard.error(i)->type() == ErrorType::NoMatchingCandidates); + } + guard.realizeErrors(); +} + + +static void test15() { + Context ctx; + auto context = &ctx; + ErrorGuard guard(context); + + // Production allows multiple constants to have the same numeric value. + // When casting backwards, the first matching constant is picked. + auto vars = resolveTypesOfVariables(context, + R"""( + enum color { + red, + green, + blue, + gold, + } + var red = color.red; + var a = red : int; + var b = red : uint; + var c = red : int(8); + var d = red : uint(8); + )""", {"a", "b", "c", "d"}); + + for (auto pair : vars) { + assert(pair.second.isErroneousType()); + } + + assert(guard.numErrors() == 4); + for (int i = 0; i < 4; i ++) { + assert(guard.error(i)->type() == ErrorType::NoMatchingCandidates); + } + guard.realizeErrors(); +} + int main() { test1(); test2(); @@ -385,5 +515,9 @@ int main() { test9(); test10(); test11(); + test12(); + test13(); + test14(); + test15(); return 0; }