Permalink
Browse files

Resolve `type(of:)` by overload resolution rather than parse hackery.

`type(of:)` has behavior whose type isn't directly representable in Swift's type system, since it produces both concrete and existential metatypes. In Swift 3 we put in a parser hack to turn `type(of: <expr>)` into a DynamicTypeExpr, but this effectively made `type(of:)` a reserved name. It's a bit more principled to put `Swift.type(of:)` on the same level as other declarations, even with its special-case type system behavior, and we can do this by special-casing the type system we produce during overload resolution if `Swift.type(of:)` shows up in an overload set. This also lays groundwork for handling other declarations we want to ostensibly behave like normal declarations but with otherwise inexpressible types, viz. `withoutActuallyEscaping` from SE-0110.
  • Loading branch information...
jckarter committed Dec 20, 2016
1 parent 474096b commit 1889fde2284916e2c368c9c7cc87906adae9155b
@@ -2432,6 +2432,9 @@ NOTE(fix_unqualified_access_top_level_multi,none,
"use '%0' to reference the %1 in module %2",
(StringRef, DescriptiveDeclKind, Identifier))

ERROR(unsupported_special_decl_ref, none,
"referencing %0 as a function value is not implemented", (Identifier))

WARNING(use_of_qq_on_non_optional_value,none,
"left side of nil coalescing operator '?""?' has non-optional type %0, "
"so the right side is never used", (Type))
@@ -968,6 +968,11 @@ getMagicIdentifierLiteralKind(tok Kind) {

/// See if type(of: <expr>) can be parsed backtracking on failure.
static bool canParseTypeOf(Parser &P) {
// We parsed `type(of:)` as a special syntactic form in Swift 3. In Swift 4
// it is handled by overload resolution.
if (!P.Context.LangOpts.isSwiftVersion3())
return false;

if (!(P.Tok.getText() == "type" && P.peekToken().is(tok::l_paren))) {
return false;
}
@@ -387,7 +387,7 @@ namespace {
AccessSemantics semantics) {
// Determine the declaration selected for this overloaded reference.
auto &ctx = cs.getASTContext();

// If this is a member of a nominal type, build a reference to the
// member with an implied base type.
if (decl->getDeclContext()->isTypeContext() && isa<FuncDecl>(decl)) {
@@ -5230,6 +5230,16 @@ ClosureExpr *ExprRewriter::coerceClosureExprFromNever(ClosureExpr *closureExpr)
return closureExpr;
}

// Look through sugar and DotSyntaxBaseIgnoredExprs.
static Expr *
getSemanticExprForDeclOrMemberRef(Expr *expr) {
auto semanticExpr = expr->getSemanticsProvidingExpr();
while (auto ignoredBase = dyn_cast<DotSyntaxBaseIgnoredExpr>(semanticExpr)){
semanticExpr = ignoredBase->getRHS()->getSemanticsProvidingExpr();
}
return semanticExpr;
}

static void
maybeDiagnoseUnsupportedFunctionConversion(ConstraintSystem &cs, Expr *expr,
AnyFunctionType *toType) {
@@ -5252,11 +5262,7 @@ maybeDiagnoseUnsupportedFunctionConversion(ConstraintSystem &cs, Expr *expr,
// Can convert a decl ref to a global or local function that doesn't
// capture context. Look through ignored bases too.
// TODO: Look through static method applications to the type.
auto semanticExpr = expr->getSemanticsProvidingExpr();
while (auto ignoredBase = dyn_cast<DotSyntaxBaseIgnoredExpr>(semanticExpr)){
semanticExpr = ignoredBase->getRHS()->getSemanticsProvidingExpr();
}

auto semanticExpr = getSemanticExprForDeclOrMemberRef(expr);
auto maybeDiagnoseFunctionRef = [&](FuncDecl *fn) {
// TODO: We could allow static (or class final) functions too by
// "capturing" the metatype in a thunk.
@@ -6330,13 +6336,49 @@ Expr *ExprRewriter::finishApply(ApplyExpr *apply, Type openedType,
TypeChecker &tc = cs.getTypeChecker();

auto fn = apply->getFn();


auto finishApplyOfDeclWithSpecialTypeCheckingSemantics
= [&](ApplyExpr *apply,
ValueDecl *decl,
Type openedType) -> Expr* {
switch (cs.TC.getDeclTypeCheckingSemantics(decl)) {
case DeclTypeCheckingSemantics::TypeOf: {
// Resolve into a DynamicTypeExpr.
auto arg = apply->getArg();
if (auto tuple = dyn_cast<TupleExpr>(arg))
arg = tuple->getElements()[0];
auto replacement = new (cs.getASTContext())
DynamicTypeExpr(apply->getFn()->getLoc(),
apply->getArg()->getStartLoc(),
arg,
apply->getArg()->getEndLoc(),
Type());
cs.setType(replacement, simplifyType(openedType));
return replacement;
}

case DeclTypeCheckingSemantics::Normal:
return nullptr;
}
};

// The function is always an rvalue.
fn = cs.coerceToRValue(fn);
assert(fn && "Rvalue conversion failed?");
if (!fn)
return nullptr;

// Resolve applications of decls with special semantics.
if (auto declRef =
dyn_cast<DeclRefExpr>(getSemanticExprForDeclOrMemberRef(fn))) {
if (auto special =
finishApplyOfDeclWithSpecialTypeCheckingSemantics(apply,
declRef->getDecl(),
openedType)) {
return special;
}
}

// Handle applications that look through ImplicitlyUnwrappedOptional<T>.
if (auto fnTy = cs.lookThroughImplicitlyUnwrappedOptionalType(cs.getType(fn)))
fn = coerceImplicitlyUnwrappedOptionalToValue(fn, fnTy, locator);
@@ -1319,18 +1319,64 @@ void ConstraintSystem::addOverloadSet(Type boundType,
addDisjunctionConstraint(overloads, locator, ForgetChoice, favoredChoice);
}

/// If we're resolving an overload set with a decl that has special type
/// checking semantics, set up the special-case type system and return true;
/// otherwise return false.
static bool
resolveOverloadForDeclWithSpecialTypeCheckingSemantics(ConstraintSystem &CS,
ConstraintLocator *locator,
Type boundType,
OverloadChoice choice,
Type &refType,
Type &openedFullType) {
assert(choice.getKind() == OverloadChoiceKind::Decl);

switch (CS.TC.getDeclTypeCheckingSemantics(choice.getDecl())) {
case DeclTypeCheckingSemantics::Normal:
return false;

case DeclTypeCheckingSemantics::TypeOf:
// Proceed with a "DynamicType" operation. This produces an existential
// metatype from existentials, or a concrete metatype from non-
// existentials (as seen from the current abstraction level), which can't
// be expressed in the type system currently.
auto input = CS.createTypeVariable(
CS.getConstraintLocator(locator, ConstraintLocator::FunctionArgument), 0);
auto output = CS.createTypeVariable(
CS.getConstraintLocator(locator, ConstraintLocator::FunctionResult), 0);

auto inputArg = TupleTypeElt(input, CS.getASTContext().getIdentifier("of"));
auto inputTuple = TupleType::get(inputArg, CS.getASTContext());

CS.addConstraint(ConstraintKind::DynamicTypeOf, output, input,
CS.getConstraintLocator(locator, ConstraintLocator::RvalueAdjustment));
refType = FunctionType::get(inputTuple, output);
openedFullType = refType;
return true;
}
}

void ConstraintSystem::resolveOverload(ConstraintLocator *locator,
Type boundType,
OverloadChoice choice) {
// Determine the type to which we'll bind the overload set's type.
Type refType;
Type openedFullType;
switch (choice.getKind()) {
case OverloadChoiceKind::DeclViaBridge:
switch (auto kind = choice.getKind()) {
case OverloadChoiceKind::Decl:
// If we refer to a top-level decl with special type-checking semantics,
// handle it now.
if (resolveOverloadForDeclWithSpecialTypeCheckingSemantics(
*this, locator, boundType, choice, refType, openedFullType))
break;

SWIFT_FALLTHROUGH;

case OverloadChoiceKind::DeclViaBridge:
case OverloadChoiceKind::DeclViaDynamic:
case OverloadChoiceKind::DeclViaUnwrappedOptional:
case OverloadChoiceKind::TypeDecl: {

bool isTypeReference = choice.getKind() == OverloadChoiceKind::TypeDecl;
bool isDynamicResult
= choice.getKind() == OverloadChoiceKind::DeclViaDynamic;
@@ -1414,7 +1460,6 @@ void ConstraintSystem::resolveOverload(ConstraintLocator *locator,
}
break;
}

assert(!refType->hasTypeParameter() && "Cannot have a dependent type here");

// If we're binding to an init member, the 'throws' need to line up between
@@ -258,6 +258,9 @@ static void diagSyntacticUseRestrictions(TypeChecker &TC, const Expr *E,

// Verify warn_unqualified_access uses.
checkUnqualifiedAccessUse(DRE);

// Verify that special decls are eliminated.
checkForDeclWithSpecialTypeCheckingSemantics(DRE);
}
if (auto *MRE = dyn_cast<MemberRefExpr>(Base)) {
if (isa<TypeDecl>(MRE->getMember().getDecl()))
@@ -639,6 +642,17 @@ static void diagSyntacticUseRestrictions(TypeChecker &TC, const Expr *E,
}
}

void checkForDeclWithSpecialTypeCheckingSemantics(const DeclRefExpr *DRE) {
// Referencing type(of:) and other decls with special type-checking
// behavior as functions is not implemented. Maybe we could wrap up the
// special-case behavior in a closure someday...
if (TC.getDeclTypeCheckingSemantics(DRE->getDecl())
!= DeclTypeCheckingSemantics::Normal) {
TC.diagnose(DRE->getLoc(), diag::unsupported_special_decl_ref,
DRE->getDecl()->getName());
}
}

/// Return true if this is 'nil' type checked as an Optional. This looks
/// like this:
/// (call_expr implicit type='Int?'
@@ -89,7 +89,7 @@ class OverloadChoice {
llvm::PointerIntPair<Type, 3, unsigned> BaseAndBits;

/// \brief Either the declaration pointer (if the low bit is clear) or the
/// overload choice kind shifted by 1 with the low bit set.
/// overload choice kind shifted two bits with the low bit set.
uintptr_t DeclOrKind;

/// The kind of function reference.
@@ -2328,3 +2328,13 @@ void TypeChecker::checkForForbiddenPrefix(StringRef Name) {
llvm::report_fatal_error(Msg);
}
}

DeclTypeCheckingSemantics
TypeChecker::getDeclTypeCheckingSemantics(ValueDecl *decl) {
// Check for a @_semantics attribute.
if (auto semantics = decl->getAttrs().getAttribute<SemanticsAttr>()) {
if (semantics->Value.equals("typechecker.type(of:)"))
return DeclTypeCheckingSemantics::TypeOf;
}
return DeclTypeCheckingSemantics::Normal;
}
@@ -55,6 +55,16 @@ namespace constraints {
typedef llvm::DenseMap<SubstitutableType *,
SmallVector<ProtocolConformance *, 2>> ConformanceMap;

/// Special-case type checking semantics for certain declarations.
enum class DeclTypeCheckingSemantics {
/// A normal declaration.
Normal,

/// The type(of:) declaration, which performs a "dynamic type" operation,
/// with different behavior for existential and non-existential arguments.
TypeOf,
};

/// The result of name lookup.
class LookupResult {
public:
@@ -2060,6 +2070,10 @@ class TypeChecker final : public LazyResolver {

void noteTypoCorrection(DeclName name, DeclNameLoc nameLoc,
const LookupResult::Result &suggestion);

/// Check if the given decl has a @_semantics attribute that gives it
/// special case type-checking behavior.
DeclTypeCheckingSemantics getDeclTypeCheckingSemantics(ValueDecl *decl);
};

/// \brief RAII object that cleans up the given expression if not explicitly
@@ -606,3 +606,54 @@ internal func _unsafeDowncastToAnyObject(fromAny any: Any) -> AnyObject {
return any as! AnyObject
#endif
}

// Game the SIL diagnostic pipeline by inlining this into the transparent
// definitions below after the stdlib's diagnostic passes run, so that the
// `staticReport`s don't fire while building the standard library, but do
// fire if they ever show up in code that uses the standard library.
@inline(__always)
public // internal with availability
func _trueAfterDiagnostics() -> Builtin.Int1 {
return true._value
}

/// Returns the dynamic type of a value.
///
/// - Parameter of: The value to take the dynamic type of.
/// - Returns: The dynamic type, which will be a value of metatype type.
///
/// - Remark: If the parameter is statically of a protocol or protocol
/// composition type, the result will be an *existential metatype*
/// (`P.Type` for a protocol `P`), and will represent the type of the value
/// inside the existential container with the same protocol conformances
/// as the value. Otherwise, the result will be a *concrete metatype*
/// (`T.Type` for a non-protocol type `T`, or `P.Protocol` for a protocol
/// `P`). Normally, this will do what you mean, but one wart to be aware
/// of is when you use `type(of:)` in a generic context with a type
/// parameter bound to a protocol type:
///
/// ```
/// func foo<T>(x: T) -> T.Type {
/// return type(of: x)
/// }
/// protocol P {}
/// func bar(x: P) {
/// foo(x: x) // Call foo with T == P
/// }
/// ```
///
/// since the call to `type(of:)` inside `foo` only sees `T` as a concrete
/// type, foo will end up returning `P.self` instead of the dynamic type
/// inside `x`. This can be worked around by writing `type(of: x as Any)`
/// to get the dynamic type inside `x` as an `Any.Type`.
@_transparent
@_semantics("typechecker.type(of:)")
public func type<Type, Metatype>(of: Type) -> Metatype {
// This implementation is never used, since calls to `Swift.type(of:)` are
// resolved as a special case by the type checker.
Builtin.staticReport(_trueAfterDiagnostics(), true._value,
("internal consistency error: 'type(of:)' operation failed to resolve"
as StaticString).utf8Start._rawValue)
Builtin.unreachable()
}

@@ -0,0 +1,38 @@
// RUN: %target-swift-frontend -module-name main -typecheck -verify -swift-version 4 %s
struct S: P {}
protocol P {}

let _: S.Type = type(of: S())
let _ = type(of: S())
let _: P.Type = type(of: S() as P)
let _ = type(of: S() as P)
let _: P.Protocol = type(of: S() as P) // expected-error{{}}
let _: S.Type = Swift.type(of: S())
let _ = Swift.type(of: S())
let _: P.Type = Swift.type(of: S() as P)
let _ = Swift.type(of: S() as P)
let _: P.Protocol = Swift.type(of: S() as P) // expected-error{{}}
let _: (S) -> S.Type = type(of:) // expected-error{{}}
func type(_: S) -> S {}
func type(kinda _: S) -> Any.Type {}

let _ = type(S())
let _: S = type(S())
let _ = type(kinda: S())
let _: Any.Type = type(kinda: S())

struct Q {}
struct R {}

func type(of: Q) -> R {}

let _: R = type(of: Q())
let _: Q.Type = type(of: Q())
let _: Q.Type = Swift.type(of: Q())
let _: R = Swift.type(of: Q()) // expected-error{{}}
let _: Q.Type = main.type(of: Q()) // expected-error{{}}
let _: R = main.type(of: Q())
@@ -131,9 +131,9 @@ func typeOfShadowing() {
return t
}

_ = type(of: Gen<Foo>.Bar) // expected-error{{expected member name or constructor call after type name}}
// expected-note@-1{{add arguments after the type to construct a value of the type}}
// expected-note@-2{{use '.self' to reference the type object}}
// TODO: Errors need improving here.
_ = type(of: Gen<Foo>.Bar) // expected-error{{argument labels '(of:)' do not match any available overloads}}
// expected-note@-1{{overloads for 'type' exist with these partially matching parameter lists: (T.Type), (fo: T.Type)}}
_ = type(Gen<Foo>.Bar) // expected-warning{{missing '.self' for reference to metatype of type 'Gen<Foo>.Bar'}}
_ = type(of: Gen<Foo>.Bar.self, flag: false) // No error here.
_ = type(fo: Foo.Bar.self) // No error here.

1 comment on commit 1889fde

@jtbandes

This comment has been minimized.

Copy link
Collaborator

jtbandes commented on 1889fde Jan 4, 2017

Cool! I had wondered about the possibility of resolving type(of:) as a regular function, but had no idea how it might work. This is nifty.

Please sign in to comment.