Hey, I am currently trying to implement type-checking for the Typed throws proposal.
We already have the syntax parsed and AbstractFunctionDecl
now contains a TypeLoc ThrowsType
.
Now I want to type-check this throws type.
I think, the right place for that is the InterfaceTypeRequest::evaluate()
function in TypeCheckDecl.cpp
, because in there the result type and the types of the parameters of the function are getting type-checked. So, based on the implementation for the result type, I added a Type getThrowsInterfaceType()
function to AbstractFunctionDecl
, which just calls evaluateOrDefault
on the newly added ThrowsTypeRequest
class.
Now it gets interesting with the ThrowsTypeRequest::evaluate()
function.
In there, I look on the AbstractFunctionDecl
.
There are two easy cases for getting the throws
type:
If the function does not throw, the type is resolved to Never
.
Else if in parsing there was no type found, the type is defaulted to Swift.Error
, because that's basically the current behaviour of throws
.
Otherwise I have to use some type resolver, with a constraint, that the type must conform to Error
. I dont know, how to do that.
My current code looks like that:
Type ThrowsTypeRequest::evaluate(Evaluator &evaluator,
AbstractFunctionDecl *decl) const {
auto &ctx = decl->getASTContext();
// Return Swift.Never, if the function doesnt throw.
if (!decl->hasThrows())
return ctx.getNeverType();
TypeRepr *throwsTyRepr = decl->getThrowsTypeRepr();
// If no type is specified, default to Swift.Error
if (throwsTyRepr == nullptr)
return ctx.getErrorDecl()->getInterfaceType();
const auto options =
TypeResolutionOptions(TypeResolverContext::FunctionThrows);
auto *const dc = decl->getInnermostDeclContext();
auto type = TypeResolution::forInterface(dc, options, /*unboundTyOpener*/ nullptr)
.resolveType(throwsTyRepr);
// the three statements above are basically copy-pasted from `ResultTypeRequest::evaluate`
// I don't know, if I can constrain the type already in the resolution
return type;
}
I also looked at the implementation of TypeResolver::resolveDictionaryType
in TypeCheckType.cpp
, as that function calls TypeChecker::applyUnboundGenericArguments
, which constrains the key type of the dictionary to Hashable
.
IIUC, somehow I have to create a TypeSubstitutionMap
and fill it in with a GenericTypeParamType
, that says, that our type has to conform to Error
. I don't know, if this explanation is somewhat understandable or if it is correct.
I hope, somebody can help me with this.
EDIT:
I left out an important detail:
The type resolution of the dictionary type causes the compiler to synthesise a where
clause.
If we have this function:
func foo<K>(_ bar: [K: Int]) {}
The compiler actually synthesises the following:
func foo<K>(_ bar: [K: Int]) where K: Hashable {}
because that is a constraint of the dictionary type.
Similarly, the proposal states, that this function:
func foo<E>() throws (E) {}
gets synthesised to that:
func foo<E>() throws (E) where E: Error {}
AFAIK, because of this, I can't just use e.g. TypeChecker::conformsToProtocol
because it doesn't place a constraint on the generic type, at least, I couldn't find that in the compiler code. If that is a wrong assumption of mine, that would be great.