summaryrefslogtreecommitdiff
path: root/source/slang/slang-check-decl.cpp
diff options
context:
space:
mode:
authorTim Foley <tfoleyNV@users.noreply.github.com>2019-12-06 15:50:32 -0800
committerGitHub <noreply@github.com>2019-12-06 15:50:32 -0800
commit2e52217cb870b4101c1639fed78224f89bf119b3 (patch)
tree3290c233716b809d7e453364c20fc448b2c821a5 /source/slang/slang-check-decl.cpp
parent895fcff7df1a71af59fad6bb31939ff370920eb4 (diff)
Support conversion from int/uint to enum types (#1147)
* Support conversion from int/uint to enum types The basic feature here is tiny, and is summarized in the code added to the stdlib: ``` extension __EnumType { __init(int val); __init(uint val); } ``` The front-end already makes all `enum` types implicitly conform to `__EnumType` behind the scenes, and this `extension` makes it so that all such types inherit some initializers (`__init` declarations, aka. "constructors") that take `int` and `uint`. (Note: right now all `__init` declarations in Slang are assumed to be implemented as intrinsics using `kIROp_Construct`. This obviously needs to change some day, especially so that we can support user-defined initializers.) Actually making this *work* required a bit of fleshing out pieces of the compiler that had previously been a bit ad hoc to be a bit more "correct." Most of the rest of this description is focused on those details, since the main feature is not itself very exciting. When overload resolution sees an attempt to "call" a type (e.g., `MyType(3.0)`) it needs to add appropriate overload candidates for the initializers in that type, which may take different numbers and types of parameters. The existing code for handling this case was using an ad hoc approach to try to enumerate the initializer declarations to consider, which might be found via inheritance, `extension` declarations, etc. In practice, the ad hoc logic for looking up initializers was just doing a subset of the work that already goes into doing member lookup. Changing the code so that it effectively does lookup for `MyType.__init` allows us to look up initializers in a way that is consistent with any other case of member lookup. Generalizing this lookup step brings us one step closer to being able to go from an `enum` type `E` to an initializer defined on an `extension` of an `interface` that `E` conforms to. One casualty of using the ordinary lookup logic for initializers is that we used to pass the type being constructed down into the logic that enumerated the initializers, which made it easier to short-circuit the part of overload resolution that usually asks "what type does this candidate return." It might seem "obvious" that an initializer/constructor on type `Foo` should return a value of type `Foo`, but that isn't necessarily true. Consider the `__BuiltinFloatingPointType` interface, which requires all the built-in floating-point types (`float`, `double`, `half`) to have an initializer that can take a `float`. If we call that interface in a generic context for `T : __BuiltinFloatingPointType`, then we want to treat that initializer as returning `T` and not `__BuiltinFloatingPointType`. Without the ad hoc logic in initializer overload resolution, this is the exact problem that surfaced for the stdlib definition of `clamp`. The solution to the "what type does an initializer return" problem was to introduce a notion of a `ThisType`, which refers to the type of `this` in the body of an interface. More generally, we will eventually want to have the keyword `This` be the type-level equivalent of `this`, and be usable inside any type. The `calcThisType` function introduced here computes a reasonable `Type` to represent the value of `This` within a given declaration. Inside of concrete type it refers to the type itself, while in an `interface` it will always be a `ThisType`. The existing `ThisTypeSubstitution`s, previously only applied to associated types, now apply to `ThisType`s as well, in the same situations. The next roadblock for making the simple declarations for `__EnumType` work was that the lookup logic was only doing lookup through inheritance relationships when the type being looked up in was an `interface`. The logic in play was reasonable: if you are doing lookup in a type `T` that inherits from `IFoo`, then why bother looking for `IFoo::bar` when there must be a `T::bar` if `T` actually implements the interface? The catch in this case is that `IFoo::bar` might not be a requirement of `IFoo`, but rather a concrete method added via an `extension`, in which case `T` need not have its own concrete `bar`. The simple/obvious fix here was to make the lookup logic always include inherited members, even when looking up through a concrete type. Of course, if we allow lookup to see `IFoo::bar` when looking up on `T`, then we have the problem that both `T::bar` and `IFoo::bar` show up in the lookup results, and potentially lead to an "ambiguous overload" error. This problem arises for any interface rquirement (so both methods and associated types right now). In order to get around it, I added a somewhat grungy check for comparing overload candidates (during overload resolution) or `LookupResultItem`s (during resolution of simple overloaded identifiers) that considers a member of a concrete type as automatically "better" than a member of an interface. The Right Way to solve this problem in the long run requires some more subtlety, but for now this check should Just Work. One final wrinkle is that due to our IR lowering pass being a bit overzealous, we currently end up trying to emit IR for those new `__init` declarations, which ends up causing us to try and emit IR for a `ThisType`. That is a case that will require some subtlty to handle correctly down the line, for for now we do the expedient thing and emit the `ThisType` for `IFoo` as `IFoo` itself, which is not especially correct, but doesn't matter since the concrete initializer won't ever be called. * testing: add more debug output to Unix process launch function * testing: increase timeout when running command-line tests
Diffstat (limited to 'source/slang/slang-check-decl.cpp')
-rw-r--r--source/slang/slang-check-decl.cpp117
1 files changed, 99 insertions, 18 deletions
diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp
index 3e0a1c618..027ddc70f 100644
--- a/source/slang/slang-check-decl.cpp
+++ b/source/slang/slang-check-decl.cpp
@@ -161,6 +161,14 @@ namespace Slang
if(as<SimpleTypeDecl>(decl))
return true;
+ // Initializer/constructor declarations are effectively `static`
+ // in Slang. They behave like functions that return an instance
+ // of the enclosing type, rather than as functions that are
+ // called on a pre-existing value.
+ //
+ if(as<ConstructorDecl>(decl))
+ return true;
+
// Things nested inside functions may have dependencies
// on values from the enclosing scope, but this needs to
// be dealt with via "capture" so they are also effectively
@@ -1197,6 +1205,18 @@ namespace Slang
DeclRef<Decl> requiredMemberDeclRef,
RefPtr<WitnessTable> witnessTable)
{
+ // Sanity check: if are checking whether a type `T`
+ // implements, say, `IFoo::bar` and lookup of `bar`
+ // in type `T` yielded `IFoo::bar`, then that shouldn't
+ // be treated as a valid satisfaction of the requirement.
+ //
+ // TODO: Ideally this check should be comparing the `DeclRef`s
+ // and not just the `Decl`s, but we currently don't get exactly
+ // the same substitutions when we see the inherited `IFoo::bar`.
+ //
+ if(memberDeclRef.getDecl() == requiredMemberDeclRef.getDecl())
+ return false;
+
// At a high level, we want to check that the
// `memberDecl` and the `requiredMemberDeclRef`
// have the same AST node class, and then also
@@ -2524,6 +2544,78 @@ namespace Slang
getSink()->diagnose(decl->targetType.exp, Diagnostics::unimplemented, "expected a nominal type here");
}
+ RefPtr<Type> SemanticsVisitor::calcThisType(DeclRef<Decl> declRef)
+ {
+ if( auto interfaceDeclRef = declRef.as<InterfaceDecl>() )
+ {
+ // In the body of an `interface`, a `This` type
+ // refers to the concrete type that will eventually
+ // conform to the interface and fill in its
+ // requirements.
+ //
+ RefPtr<ThisType> thisType = new ThisType();
+ thisType->setSession(getSession());
+ thisType->interfaceDeclRef = interfaceDeclRef;
+ return thisType;
+ }
+ else if (auto aggTypeDeclRef = declRef.as<AggTypeDecl>())
+ {
+ // In the body of an ordinary aggregate type,
+ // such as a `struct`, the `This` type just
+ // refers to the type itself.
+ //
+ // TODO: If/when we support `class` types
+ // with inheritance, then `This` inside a class
+ // would need to refer to the eventual concrete
+ // type, much like the `interface` case above.
+ //
+ return DeclRefType::Create(
+ getSession(),
+ aggTypeDeclRef);
+ }
+ else if (auto extDeclRef = declRef.as<ExtensionDecl>())
+ {
+ // In the body of an `extension`, the `This`
+ // type refers to the type being extended.
+ //
+ // Note: we currently have this loop back
+ // around through `calcThisType` for the
+ // type being extended, rather than just
+ // using it directly. This makes a difference
+ // for polymorphic types like `interface`s,
+ // and there are reasonable arguments for
+ // the validity of either option.
+ //
+ // Does `extension IFoo` mean extending
+ // exactly the type `IFoo` (an existential,
+ // which could at runtime be a value of
+ // any type conforming to `IFoo`), or does
+ // it implicitly extend every type that
+ // conforms to `IFoo`? The difference is
+ // significant, and we need to make a choice
+ // sooner or later.
+ //
+ auto targetType = GetTargetType(extDeclRef);
+ return calcThisType(targetType);
+ }
+ else
+ {
+ return nullptr;
+ }
+ }
+
+ RefPtr<Type> SemanticsVisitor::calcThisType(Type* type)
+ {
+ if( auto declRefType = as<DeclRefType>(type) )
+ {
+ return calcThisType(declRefType->declRef);
+ }
+ else
+ {
+ return type;
+ }
+ }
+
RefPtr<Type> SemanticsVisitor::findResultTypeForConstructorDecl(ConstructorDecl* decl)
{
// We want to look at the parent of the declaration,
@@ -2538,27 +2630,16 @@ namespace Slang
parent = genericParent->ParentDecl;
}
- // Now look at the type of the parent (or grandparent).
- if (auto aggTypeDecl = as<AggTypeDecl>(parent))
- {
- // We are nested in an aggregate type declaration,
- // so the result type of the initializer will just
- // be the surrounding type.
- return DeclRefType::Create(
- getSession(),
- makeDeclRef(aggTypeDecl));
- }
- else if (auto extDecl = as<ExtensionDecl>(parent))
- {
- // We are nested inside an extension, so the result
- // type needs to be the type being extended.
- return extDecl->targetType.type;
- }
- else
+ // The result type for a constructor is whatever `This` would
+ // refer to in the body of the outer declaration.
+ //
+ auto thisType = calcThisType(makeDeclRef(parent));
+ if( !thisType )
{
getSink()->diagnose(decl, Diagnostics::initializerNotInsideType);
- return nullptr;
+ thisType = getSession()->getErrorType();
}
+ return thisType;
}
void SemanticsDeclHeaderVisitor::visitConstructorDecl(ConstructorDecl* decl)