diff options
Diffstat (limited to 'source/slang/check.cpp')
| -rw-r--r-- | source/slang/check.cpp | 504 |
1 files changed, 493 insertions, 11 deletions
diff --git a/source/slang/check.cpp b/source/slang/check.cpp index c3eb44cfd..764764e76 100644 --- a/source/slang/check.cpp +++ b/source/slang/check.cpp @@ -359,6 +359,71 @@ namespace Slang return expr->type->As<DeclRefType>(); } + /// Is `decl` usable as a static member? + bool isDeclUsableAsStaticMember( + Decl* decl) + { + if(decl->HasModifier<HLSLStaticModifier>()) + return true; + + if(decl->As<ConstructorDecl>()) + return true; + + if(decl->As<EnumCaseDecl>()) + return true; + + if(decl->As<AggTypeDeclBase>()) + return true; + + if(decl->As<SimpleTypeDecl>()) + return true; + + return false; + } + + /// Is `item` usable as a static member? + bool isUsableAsStaticMember( + LookupResultItem const& item) + { + // There's a bit of a gotcha here, because a lookup result + // item might include "breadcrumbs" that indicate more steps + // along the lookup path. As a result it isn't always + // valid to just check whether the final decl is usable + // as a static member, because it might not even be a + // member of the thing we are trying to work with. + // + + Decl* decl = item.declRef.getDecl(); + for(auto bb = item.breadcrumbs; bb; bb = bb->next) + { + switch(bb->kind) + { + // In case lookup went through a `__transparent` member, + // we are interested in the static-ness of that transparent + // member, and *not* the static-ness of whatever was inside + // of it. + // + // TODO: This would need some work if we ever had + // transparent *type* members. + // + case LookupResultItem::Breadcrumb::Kind::Member: + decl = bb->declRef.getDecl(); + break; + + // TODO: Are there any other cases that need special-case + // handling here? + + default: + break; + } + } + + // Okay, we've found the declaration we should actually + // be checking, so lets validate that. + + return isDeclUsableAsStaticMember(decl); + } + RefPtr<Expr> ConstructDeclRefExpr( DeclRef<Decl> declRef, RefPtr<Expr> baseExpr, @@ -378,9 +443,6 @@ namespace Slang // if (baseExpr->type->As<TypeType>()) { - // If the base expression was a type, then that means we - // are constructing a static member reference. - // auto expr = new StaticMemberExpr(); expr->loc = loc; expr->type = type; @@ -2417,6 +2479,16 @@ namespace Slang // with the same name in the type declaration and // its (known) extensions. + // As a first pass, lets check if we already have a + // witness in the table for the requirement, so + // that we can bail out early. + // + if(witnessTable->requirementDictionary.ContainsKey(requiredMemberDeclRef.getDecl())) + { + return true; + } + + // An important exception to the above is that an // inheritance declaration in the interface is not going // to be satisfied by an inheritance declaration in the @@ -2530,7 +2602,15 @@ namespace Slang // *before* we go about checking fine-grained requirements, // in order to short-circuit any potential for infinite recursion. - witnessTable = new WitnessTable(); + // Note: we will re-use the witnes table attached to the inheritance decl, + // if there is one. This catches cases where semantic checking might + // have synthesized some of the conformance witnesses for us. + // + witnessTable = inheritanceDecl->witnessTable; + if(!witnessTable) + { + witnessTable = new WitnessTable(); + } context->mapInterfaceToWitnessTable.Add(interfaceDeclRef, witnessTable); bool result = true; @@ -2770,6 +2850,287 @@ namespace Slang decl->SetCheckState(getCheckedState()); } + // Validate that `type` is a suitable type to use + // as the tag type for an `enum` + void validateEnumTagType(Type* type, SourceLoc const& loc) + { + if(auto basicType = type->As<BasicExpressionType>()) + { + switch(basicType->baseType) + { + default: + // By default, don't allow a type to be used + // as an `enum` tag type. + break; + + case BaseType::Int: + case BaseType::UInt: + case BaseType::UInt64: + // These are all allowed. + return; + } + } + + getSink()->diagnose(loc, Diagnostics::invalidEnumTagType, type); + } + + void visitEnumDecl(EnumDecl* decl) + { + if (decl->IsChecked(getCheckedState())) + return; + + // Look at inheritance clauses, and + // see if one of them is making the enum + // "inherit" from a concrete type. + // This will become the "tag" type + // of the enum. + RefPtr<Type> tagType; + InheritanceDecl* tagTypeInheritanceDecl = nullptr; + for(auto inheritanceDecl : decl->getMembersOfType<InheritanceDecl>()) + { + checkDecl(inheritanceDecl); + + // Look at the type being inherited from. + auto superType = inheritanceDecl->base.type; + + if(auto errorType = superType->As<ErrorType>()) + { + // Ignore any erroneous inheritance clauses. + continue; + } + else if(auto declRefType = superType->As<DeclRefType>()) + { + if(auto interfaceDeclRef = declRefType->declRef.As<InterfaceDecl>()) + { + // Don't consider interface bases as candidates for + // the tag type. + continue; + } + } + + if(tagType) + { + // We already found a tag type. + getSink()->diagnose(inheritanceDecl, Diagnostics::enumTypeAlreadyHasTagType); + getSink()->diagnose(tagTypeInheritanceDecl, Diagnostics::seePreviousTagType); + break; + } + else + { + tagType = superType; + tagTypeInheritanceDecl = inheritanceDecl; + } + } + + // If a tag type has not been set, then we + // default it to the built-in `int` type. + // + // TODO: In the far-flung future we may want to distinguish + // `enum` types that have a "raw representation" like this from + // ones that are purely abstract and don't expose their + // type of their tag. + if(!tagType) + { + tagType = getSession()->getIntType(); + } + else + { + // TODO: Need to establish that the tag + // type is suitable. (e.g., if we are going + // to allow raw values for case tags to be + // derived automatically, then the tag + // type needs to be some kind of interer type...) + // + // For now we will just be harsh and require it + // to be one of a few builtin types. + validateEnumTagType(tagType, tagTypeInheritanceDecl->loc); + } + decl->tagType = tagType; + + + // An `enum` type should automatically conform to the `__EnumType` interface. + // The compiler needs to insert this conformance behind the scenes, and this + // seems like the best place to do it. + { + // First, look up the type of the `__EnumType` interface. + RefPtr<Type> enumTypeType = getSession()->getEnumTypeType(); + + RefPtr<InheritanceDecl> enumConformanceDecl = new InheritanceDecl(); + enumConformanceDecl->ParentDecl = decl; + enumConformanceDecl->loc = decl->loc; + enumConformanceDecl->base.type = getSession()->getEnumTypeType(); + decl->Members.Add(enumConformanceDecl); + + // The `__EnumType` interface has one required member, the `__Tag` type. + // We need to satisfy this requirement automatically, rather than require + // the user to actually declare a member with this name (otherwise we wouldn't + // let them define a tag value with the name `__Tag`). + // + RefPtr<WitnessTable> witnessTable = new WitnessTable(); + enumConformanceDecl->witnessTable = witnessTable; + + Name* tagAssociatedTypeName = getSession()->getNameObj("__Tag"); + Decl* tagAssociatedTypeDecl = nullptr; + if(auto enumTypeTypeDeclRefType = enumTypeType.As<DeclRefType>()) + { + if(auto enumTypeTypeInterfaceDecl = enumTypeTypeDeclRefType->declRef.getDecl()->As<InterfaceDecl>()) + { + for(auto memberDecl : enumTypeTypeInterfaceDecl->Members) + { + if(memberDecl->getName() == tagAssociatedTypeName) + { + tagAssociatedTypeDecl = memberDecl; + break; + } + } + } + } + if(!tagAssociatedTypeDecl) + { + SLANG_DIAGNOSE_UNEXPECTED(getSink(), decl, "failed to find built-in declaration '__Tag'"); + } + + // Okay, add the conformance withess for `__Tag` being satisfied by `tagType` + witnessTable->requirementDictionary.Add(tagAssociatedTypeDecl, RequirementWitness(tagType)); + + // TODO: we actually also need to synthesize a witness for the conformance of `tagType` + // to the `__BuiltinIntegerType` interface, because that is a constraint on the + // associated type `__Tag`. + + // TODO: eventually we should consider synthesizing other requirements for + // the min/max tag values, or the total number of tags, so that people don't + // have to declare these as additional cases. + + enumConformanceDecl->SetCheckState(DeclCheckState::Checked); + } + + + decl->SetCheckState(DeclCheckState::CheckedHeader); + + auto enumType = DeclRefType::Create( + getSession(), + makeDeclRef(decl)); + + // Check the enum cases in order. + for(auto caseDecl : decl->getMembersOfType<EnumCaseDecl>()) + { + // Each case defines a value of the enum's type. + // + // TODO: If we ever support enum cases with payloads, + // then they would probably have a type that is a + // `FunctionType` from the payload types to the + // enum type. + // + caseDecl->type.type = enumType; + + checkDecl(caseDecl); + } + + // For any enum case that didn't provide an explicit + // tag value, derived an appropriate tag value. + IntegerLiteralValue defaultTag = 0; + for(auto caseDecl : decl->getMembersOfType<EnumCaseDecl>()) + { + if(auto explicitTagValExpr = caseDecl->initExpr) + { + // This tag has an initializer, so it should establish + // the tag value for a successor case that doesn't + // provide an explicit tag. + + RefPtr<IntVal> explicitTagVal = TryConstantFoldExpr(explicitTagValExpr); + if(explicitTagVal) + { + if(auto constIntVal = explicitTagVal.As<ConstantIntVal>()) + { + defaultTag = constIntVal->value; + } + else + { + // TODO: need to handle other possibilities here + getSink()->diagnose(explicitTagValExpr, Diagnostics::unexpectedEnumTagExpr); + } + } + else + { + // If this happens, then the explicit tag value expression + // doesn't seem to be a constant after all. In this case + // we expect the checking logic to have applied already. + } + } + else + { + // This tag has no initializer, so it should use + // the default tag value we are tracking. + RefPtr<IntegerLiteralExpr> tagValExpr = new IntegerLiteralExpr(); + tagValExpr->loc = caseDecl->loc; + tagValExpr->type = QualType(tagType); + tagValExpr->value = defaultTag; + + caseDecl->initExpr = tagValExpr; + } + + // Default tag for the next case will be one more than + // for the most recent case. + // + // TODO: We might consider adding a `[flags]` attribute + // that modifies this behavior to be `defaultTagForCase <<= 1`. + // + defaultTag++; + } + + // Now check any other member declarations. + for(auto memberDecl : decl->Members) + { + // Already checked inheritance declarations above. + if(auto inheritanceDecl = memberDecl->As<InheritanceDecl>()) + continue; + + // Already checked enum case declarations above. + if(auto caseDecl = memberDecl->As<EnumCaseDecl>()) + continue; + + // TODO: Right now we don't support other kinds of + // member declarations on an `enum`, but that is + // something we may want to allow in the long run. + // + checkDecl(memberDecl); + } + decl->SetCheckState(getCheckedState()); + } + + void visitEnumCaseDecl(EnumCaseDecl* decl) + { + if (decl->IsChecked(getCheckedState())) + return; + + // An enum case had better appear inside an enum! + // + // TODO: Do we need/want to support generic cases some day? + auto parentEnumDecl = decl->ParentDecl->As<EnumDecl>(); + SLANG_ASSERT(parentEnumDecl); + + // The tag type should have already been set by + // the surrounding `enum` declaration. + auto tagType = parentEnumDecl->tagType; + SLANG_ASSERT(tagType); + + // Need to check the init expression, if present, since + // that represents the explicit tag for this case. + if(auto initExpr = decl->initExpr) + { + initExpr = CheckExpr(initExpr); + initExpr = Coerce(tagType, initExpr); + + // We want to enforce that this is an integer constant + // expression, but we don't actually care to retain + // the value. + CheckIntegerConstantExpression(initExpr); + + decl->initExpr = initExpr; + } + decl->SetCheckState(getCheckedState()); + } + void visitDeclGroup(DeclGroup* declGroup) { for (auto decl : declGroup->decls) @@ -3988,14 +4349,26 @@ namespace Slang // or NULL if the expression isn't recognized as a constant. RefPtr<IntVal> TryCheckIntegerConstantExpression(Expr* exp) { - if (!exp->type.type->Equals(getSession()->getIntType())) + // Check if type is acceptable for an integer constant expression + if(auto basicType = exp->type.type->As<BasicExpressionType>()) + { + switch(basicType->baseType) + { + default: + return nullptr; + + case BaseType::Int: + case BaseType::UInt: + case BaseType::UInt64: + break; + } + } + else { return nullptr; } - - - // Otherwise, we need to consider operations that we might be able to constant-fold... + // Consider operations that we might be able to constant-fold... return TryConstantFoldExpr(exp); } @@ -4637,6 +5010,8 @@ namespace Slang if( auto aggTypeDeclRef = declRef.As<AggTypeDecl>() ) { + checkDecl(aggTypeDeclRef.getDecl()); + for( auto inheritanceDeclRef : getMembersOfTypeWithExt<InheritanceDecl>(aggTypeDeclRef)) { checkDecl(inheritanceDeclRef.getDecl()); @@ -5035,7 +5410,7 @@ namespace Slang return resultSubst; } - + // State related to overload resolution for a call // to an overloaded symbol struct OverloadResolveContext @@ -6356,6 +6731,30 @@ namespace Slang AddCtorOverloadCandidate(typeItem, type, ctorDeclRef, context, resultType); } + // Also check for generic constructors. + // + // TODO: There is way too much duplication between this case and the extension + // handling below, and all of this is *also* duplicative with the ordinary + // overload resolution logic for function. + // + // The right solution is to handle a "constructor" call expression by + // first doing member lookup in the type (for initializer members, which + // should all share a common name), and then to do overload resolution using + // the (possibly overloaded) result of that lookup. + // + for (auto genericDeclRef : getMembersOfType<GenericDecl>(aggTypeDeclRef)) + { + if (auto ctorDecl = genericDeclRef.getDecl()->inner.As<ConstructorDecl>()) + { + DeclRef<Decl> innerRef = SpecializeGenericForOverload(genericDeclRef, context); + if (!innerRef) + continue; + + DeclRef<ConstructorDecl> innerCtorRef = innerRef.As<ConstructorDecl>(); + AddCtorOverloadCandidate(typeItem, type, innerCtorRef, context, resultType); + } + } + // Now walk through any extensions we can find for this types for (auto ext = GetCandidateExtensions(aggTypeDeclRef); ext; ext = ext->nextCandidateExtension) { @@ -7460,8 +7859,91 @@ namespace Slang return lookupResultFailure(expr, baseType); } - // TODO: need to filter for declarations that are valid to refer - // to in this context... + // We need to confirm that whatever member we + // are trying to refer to is usable via static reference. + // + // TODO: eventually we might allow a non-static + // member to be adapted by turning it into something + // like a closure that takes the missing `this` parameter. + // + // E.g., a static reference to a method could be treated + // as a value with a function type, where the first parameter + // is `type`. + // + // The biggest challenge there is that we'd need to arrange + // to generate "dispatcher" functions that could be used + // to implement that function, in the case where we are + // making a static reference to some kind of polymoprhic declaration. + // + // (Also, static refernces to fields/properties would get even + // harder, because you'd have to know whether a getter/setter/ref-er + // is needed). + // + // For now let's just be expedient and disallow all of that, because + // we can always add it back in later. + + if(!lookupResult.isOverloaded()) + { + // The non-overloaded case is relatively easy. We just want + // to look at the member being referenced, and check if + // it is allowed in a `static` context: + // + if(!isUsableAsStaticMember(lookupResult.item)) + { + getSink()->diagnose( + expr->loc, + Diagnostics::staticRefToNonStaticMember, + type, + expr->name); + } + } + else + { + // The overloaded case is trickier, because we should first + // filter the list of candidates, because if there is anything + // that *is* usable in a static context, then we should assume + // the user just wants to reference that. We should only + // issue an error if *all* of the items that were discovered + // are non-static. + bool anyNonStatic = false; + List<LookupResultItem> staticItems; + for(auto item : lookupResult.items) + { + // Is this item usable as a static member? + if(isUsableAsStaticMember(item)) + { + // If yes, then it will be part of the output. + staticItems.Add(item); + } + else + { + // If no, then we might need to output an error. + anyNonStatic = true; + } + } + + // Was there anything non-static in the list? + if(anyNonStatic) + { + // If we had some static items, then that's okay, + // we just want to use our newly-filtered list. + if(staticItems.Count()) + { + lookupResult.items = staticItems; + } + else + { + // Otherwise, it is time to report an error. + getSink()->diagnose( + expr->loc, + Diagnostics::staticRefToNonStaticMember, + type, + expr->name); + } + } + // If there were no non-static items, then the `items` + // array already represents what we'd get by filtering... + } return createLookupResultExpr( lookupResult, |
