summaryrefslogtreecommitdiffstats
path: root/source/slang/check.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/slang/check.cpp')
-rw-r--r--source/slang/check.cpp504
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,