Skip to content

[ConstraintSystem] Introduce a new type to represent a type hole #33658

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Aug 28, 2020
1 change: 1 addition & 0 deletions include/swift/AST/TypeNodes.def
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@

TYPE(Error, Type)
UNCHECKED_TYPE(Unresolved, Type)
UNCHECKED_TYPE(Hole, Type)
ABSTRACT_TYPE(Builtin, Type)
ABSTRACT_TYPE(AnyBuiltinInteger, BuiltinType)
BUILTIN_TYPE(BuiltinInteger, AnyBuiltinIntegerType)
Expand Down
38 changes: 34 additions & 4 deletions include/swift/AST/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,10 @@ class RecursiveTypeProperties {
/// This type contains an OpaqueTypeArchetype.
HasOpaqueArchetype = 0x400,

Last_Property = HasOpaqueArchetype
/// This type contains a type hole.
HasTypeHole = 0x800,

Last_Property = HasTypeHole
};
enum { BitWidth = countBitsUsed(Property::Last_Property) };

Expand Down Expand Up @@ -203,6 +206,10 @@ class RecursiveTypeProperties {
/// generic type?
bool hasUnboundGeneric() const { return Bits & HasUnboundGeneric; }

/// Does a type with these properties structurally contain a
/// type hole?
bool hasTypeHole() const { return Bits & HasTypeHole; }

/// Returns the set of properties present in either set.
friend RecursiveTypeProperties operator|(Property lhs, Property rhs) {
return RecursiveTypeProperties(unsigned(lhs) | unsigned(rhs));
Expand Down Expand Up @@ -574,9 +581,7 @@ class alignas(1 << TypeAlignInBits) TypeBase {
}

/// Determine whether this type involves a hole.
bool hasHole() const {
return getRecursiveProperties().hasUnresolvedType();
}
bool hasHole() const { return getRecursiveProperties().hasTypeHole(); }

/// Determine whether the type involves a context-dependent archetype.
bool hasArchetype() const {
Expand Down Expand Up @@ -5728,6 +5733,31 @@ TypeVariableType : public TypeBase {
};
DEFINE_EMPTY_CAN_TYPE_WRAPPER(TypeVariableType, Type)

/// HoleType - This represents a placeholder type for a type variable
/// or dependent member type that cannot be resolved to a concrete type
/// because the expression is ambiguous. This type is only used by the
/// constraint solver and transformed into UnresolvedType to be used in AST.
class HoleType : public TypeBase {
using OriginatorType =
llvm::PointerUnion<TypeVariableType *, DependentMemberType *>;

OriginatorType Originator;

HoleType(ASTContext &C, OriginatorType originator,
RecursiveTypeProperties properties)
: TypeBase(TypeKind::Hole, &C, properties), Originator(originator) {}

public:
static Type get(ASTContext &ctx, OriginatorType originatorType);

OriginatorType getOriginatorType() const { return Originator; }

static bool classof(const TypeBase *T) {
return T->getKind() == TypeKind::Hole;
}
};
DEFINE_EMPTY_CAN_TYPE_WRAPPER(HoleType, Type)

inline bool TypeBase::isTypeVariableOrMember() {
if (is<TypeVariableType>())
return true;
Expand Down
22 changes: 18 additions & 4 deletions lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1466,8 +1466,9 @@ bool ASTContext::hadError() const {
/// Retrieve the arena from which we should allocate storage for a type.
static AllocationArena getArena(RecursiveTypeProperties properties) {
bool hasTypeVariable = properties.hasTypeVariable();
return hasTypeVariable? AllocationArena::ConstraintSolver
: AllocationArena::Permanent;
bool hasHole = properties.hasTypeHole();
return hasTypeVariable || hasHole ? AllocationArena::ConstraintSolver
: AllocationArena::Permanent;
}

void ASTContext::addSearchPath(StringRef searchPath, bool isFramework,
Expand Down Expand Up @@ -2358,6 +2359,17 @@ Type ErrorType::get(Type originalType) {
return entry = new (mem) ErrorType(ctx, originalType, properties);
}

Type HoleType::get(ASTContext &ctx, OriginatorType originator) {
assert(originator);
auto properties = reinterpret_cast<TypeBase *>(originator.getOpaqueValue())
->getRecursiveProperties();
properties |= RecursiveTypeProperties::HasTypeHole;

auto arena = getArena(properties);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is going to always allocate the type in the permanent arena, which is bad if it contains a pointer to a TypeVariableType. The recursive properties used to determine the arena should be the properties of the originator type, not the new properties of the hole type itself.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn’t realize that, thank you, Slava!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to make sure that types containing holes as well as hole types themselves are allocated in ConstraintSolver arena...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed! Every type which contains holes or is a HoleType itself is not going to be allocated in ConstraintSolver arena. @slavapestov WDYT?

return new (ctx, arena)
HoleType(ctx, originator, RecursiveTypeProperties::HasTypeHole);
}

BuiltinIntegerType *BuiltinIntegerType::get(BuiltinIntegerWidth BitWidth,
const ASTContext &C) {
assert(!BitWidth.isArbitraryWidth());
Expand Down Expand Up @@ -2799,6 +2811,7 @@ ReferenceStorageType *ReferenceStorageType::get(Type T,
ReferenceOwnership ownership,
const ASTContext &C) {
assert(!T->hasTypeVariable()); // not meaningful in type-checker
assert(!T->hasHole());
switch (optionalityOf(ownership)) {
case ReferenceOwnershipOptionality::Disallowed:
assert(!T->getOptionalObjectType() && "optional type is disallowed");
Expand Down Expand Up @@ -2957,7 +2970,7 @@ isFunctionTypeCanonical(ArrayRef<AnyFunctionType::Param> params,
static RecursiveTypeProperties
getGenericFunctionRecursiveProperties(ArrayRef<AnyFunctionType::Param> params,
Type result) {
static_assert(RecursiveTypeProperties::BitWidth == 11,
static_assert(RecursiveTypeProperties::BitWidth == 12,
"revisit this if you add new recursive type properties");
RecursiveTypeProperties properties;

Expand Down Expand Up @@ -3185,6 +3198,7 @@ GenericFunctionType *GenericFunctionType::get(GenericSignature sig,
ExtInfo info) {
assert(sig && "no generic signature for generic function type?!");
assert(!result->hasTypeVariable());
assert(!result->hasHole());

llvm::FoldingSetNodeID id;
GenericFunctionType::Profile(id, sig, params, result, info);
Expand Down Expand Up @@ -3513,7 +3527,7 @@ CanSILFunctionType SILFunctionType::get(
void *mem = ctx.Allocate(bytes, alignof(SILFunctionType));

RecursiveTypeProperties properties;
static_assert(RecursiveTypeProperties::BitWidth == 11,
static_assert(RecursiveTypeProperties::BitWidth == 12,
"revisit this if you add new recursive type properties");
for (auto &param : params)
properties |= param.getInterfaceType()->getRecursiveProperties();
Expand Down
12 changes: 12 additions & 0 deletions lib/AST/ASTDumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3496,6 +3496,18 @@ namespace {

TRIVIAL_TYPE_PRINTER(Unresolved, unresolved)

void visitHoleType(HoleType *T, StringRef label) {
printCommon(label, "hole_type");
auto originatorTy = T->getOriginatorType();
if (auto *typeVar = originatorTy.dyn_cast<TypeVariableType *>()) {
printRec("type_variable", typeVar);
} else {
printRec("dependent_member_type",
originatorTy.get<DependentMemberType *>());
}
PrintWithColorRAII(OS, ParenthesisColor) << ')';
}

void visitBuiltinIntegerType(BuiltinIntegerType *T, StringRef label) {
printCommon(label, "builtin_integer_type");
if (T->isFixedWidth())
Expand Down
1 change: 1 addition & 0 deletions lib/AST/ASTMangler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,7 @@ void ASTMangler::appendType(Type type, const ValueDecl *forDecl) {
TypeBase *tybase = type.getPointer();
switch (type->getKind()) {
case TypeKind::TypeVariable:
case TypeKind::Hole:
llvm_unreachable("mangling type variable");

case TypeKind::Module:
Expand Down
11 changes: 11 additions & 0 deletions lib/AST/ASTPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3813,6 +3813,17 @@ class TypePrinter : public TypeVisitor<TypePrinter> {
Printer << "_";
}

void visitHoleType(HoleType *T) {
if (Options.PrintTypesForDebugging) {
Printer << "<<hole for ";
auto originatorTy = T->getOriginatorType();
visit(Type(reinterpret_cast<TypeBase *>(originatorTy.getOpaqueValue())));
Printer << ">>";
} else {
Printer << "<<hole>>";
}
}

#ifdef ASTPRINTER_HANDLE_BUILTINTYPE
#error "ASTPRINTER_HANDLE_BUILTINTYPE should not be defined?!"
#endif
Expand Down
4 changes: 2 additions & 2 deletions lib/AST/Expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ namespace {
} // end anonymous namespace

void Expr::setType(Type T) {
assert(!T || !T->hasTypeVariable());
assert(!T || !T->hasTypeVariable() || !T->hasHole());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about creating a isCheckedType() method that ensures that the type is not a UNCHECKED_TYPE node? That way we don't need to update every call site that does this: !T->hasTypeVariable().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense to me but I think we can't do it at the moment unless UnresolvedType is excluded from that check because code completion is still replying on solutions with unresolved types applied to AST (we are currently working on fixing that)...

Ty = T;
}

Expand Down Expand Up @@ -2001,7 +2001,7 @@ bool ClosureExpr::capturesSelfEnablingImplictSelf() const {
}

void ClosureExpr::setExplicitResultType(Type ty) {
assert(ty && !ty->hasTypeVariable());
assert(ty && !ty->hasTypeVariable() && !ty->hasHole());
ExplicitResultTypeAndBodyState.getPointer()
->setType(MetatypeType::get(ty));
}
Expand Down
2 changes: 2 additions & 0 deletions lib/AST/SILLayout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ static void verifyFields(CanGenericSignature Sig, ArrayRef<SILField> Fields) {
&& "SILLayout field cannot have an archetype type");
assert(!ty->hasTypeVariable()
&& "SILLayout cannot contain constraint system type variables");
assert(!ty->hasHole() &&
"SILLayout cannot contain constraint system type holes");
if (!ty->hasTypeParameter())
continue;
field.getLoweredType().findIf([Sig](Type t) -> bool {
Expand Down
10 changes: 6 additions & 4 deletions lib/AST/Type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ Type QuerySubstitutionMap::operator()(SubstitutableType *type) const {
}

void TypeLoc::setType(Type Ty) {
assert(!Ty || !Ty->hasTypeVariable());
assert(!Ty || !Ty->hasTypeVariable() || !Ty->hasHole());
this->Ty = Ty;
}

Expand Down Expand Up @@ -153,9 +153,7 @@ bool TypeBase::isAny() {
return isEqual(getASTContext().TheAnyType);
}

bool TypeBase::isHole() {
return isEqual(getASTContext().TheUnresolvedType);
}
bool TypeBase::isHole() { return getCanonicalType()->is<HoleType>(); }

bool TypeBase::isAnyClassReferenceType() {
return getCanonicalType().isAnyClassReferenceType();
Expand Down Expand Up @@ -221,6 +219,7 @@ bool CanType::isReferenceTypeImpl(CanType type, const GenericSignatureImpl *sig,
case TypeKind::LValue:
case TypeKind::InOut:
case TypeKind::TypeVariable:
case TypeKind::Hole:
case TypeKind::BoundGenericEnum:
case TypeKind::BoundGenericStruct:
case TypeKind::SILToken:
Expand Down Expand Up @@ -1149,6 +1148,7 @@ CanType TypeBase::computeCanonicalType() {
case TypeKind::Error:
case TypeKind::Unresolved:
case TypeKind::TypeVariable:
case TypeKind::Hole:
llvm_unreachable("these types are always canonical");

#define SUGARED_TYPE(id, parent) \
Expand Down Expand Up @@ -4244,6 +4244,7 @@ case TypeKind::Id:
case TypeKind::Error:
case TypeKind::Unresolved:
case TypeKind::TypeVariable:
case TypeKind::Hole:
case TypeKind::GenericTypeParam:
case TypeKind::SILToken:
case TypeKind::Module:
Expand Down Expand Up @@ -4983,6 +4984,7 @@ ReferenceCounting TypeBase::getReferenceCounting() {
case TypeKind::LValue:
case TypeKind::InOut:
case TypeKind::TypeVariable:
case TypeKind::Hole:
case TypeKind::BoundGenericEnum:
case TypeKind::BoundGenericStruct:
case TypeKind::SILToken:
Expand Down
1 change: 1 addition & 0 deletions lib/AST/TypeCheckRequests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1013,6 +1013,7 @@ void InterfaceTypeRequest::cacheResult(Type type) const {
auto *decl = std::get<0>(getStorage());
if (type) {
assert(!type->hasTypeVariable() && "Type variable in interface type");
assert(!type->hasHole() && "Type hole in interface type");
assert(!type->is<InOutType>() && "Interface type must be materializable");
assert(!type->hasArchetype() && "Archetype in interface type");
}
Expand Down
3 changes: 3 additions & 0 deletions lib/AST/TypeJoinMeet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ struct TypeJoin : CanTypeVisitor<TypeJoin, CanType> {
assert(!first->hasTypeVariable() && !second->hasTypeVariable() &&
"Cannot compute join of types involving type variables");

assert(!first->hasHole() && !second->hasHole() &&
"Cannot compute join of types involving type holes");

assert(first->getWithoutSpecifierType()->isEqual(first) &&
"Expected simple type!");
assert(second->getWithoutSpecifierType()->isEqual(second) &&
Expand Down
1 change: 1 addition & 0 deletions lib/AST/TypeWalker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class Traversal : public TypeVisitor<Traversal, bool>

bool visitErrorType(ErrorType *ty) { return false; }
bool visitUnresolvedType(UnresolvedType *ty) { return false; }
bool visitHoleType(HoleType *ty) { return false; }
bool visitBuiltinType(BuiltinType *ty) { return false; }
bool visitTypeAliasType(TypeAliasType *ty) {
if (auto parent = ty->getParent())
Expand Down
1 change: 1 addition & 0 deletions lib/IRGen/IRGenDebugInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1571,6 +1571,7 @@ class IRGenDebugInfoImpl : public IRGenDebugInfo {
case TypeKind::Unresolved:
case TypeKind::LValue:
case TypeKind::TypeVariable:
case TypeKind::Hole:
case TypeKind::Module:
case TypeKind::SILBlockStorage:
case TypeKind::SILBox:
Expand Down
4 changes: 4 additions & 0 deletions lib/Sema/CSApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7662,6 +7662,8 @@ namespace {
// "Mismatched types!");
assert(!exprType->hasTypeVariable() &&
"Should not write type variable into expression!");
assert(!exprType->hasHole() &&
"Should not write type holes into expression!");
expr->setType(exprType);

if (auto kp = dyn_cast<KeyPathExpr>(expr)) {
Expand All @@ -7671,6 +7673,8 @@ namespace {
componentType = solution.simplifyType(cs.getType(kp, i));
assert(!componentType->hasTypeVariable() &&
"Should not write type variable into key-path component");
assert(!componentType->hasHole() &&
"Should not write type hole into key-path component");
kp->getMutableComponents()[i].setComponentType(componentType);
}
}
Expand Down
3 changes: 2 additions & 1 deletion lib/Sema/CSBindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ void ConstraintSystem::PotentialBindings::finalize(
if (locator->isLastElement<LocatorPathElt::MemberRefBase>())
PotentiallyIncomplete = true;

addPotentialBinding(PotentialBinding::forHole(cs.getASTContext(), locator));
addPotentialBinding(PotentialBinding::forHole(TypeVar, locator));
}

// Let's always consider `Any` to be a last resort binding because
Expand Down Expand Up @@ -481,6 +481,7 @@ void ConstraintSystem::PotentialBindings::addPotentialBinding(
if (binding.Kind == AllowedBindingKind::Supertypes &&
!binding.BindingType->hasUnresolvedType() &&
!binding.BindingType->hasTypeVariable() &&
!binding.BindingType->hasHole() &&
!binding.BindingType->hasUnboundGenericType() &&
!binding.hasDefaultedLiteralProtocol() &&
!binding.isDefaultableBinding() && allowJoinMeet) {
Expand Down
6 changes: 4 additions & 2 deletions lib/Sema/CSDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1235,8 +1235,10 @@ bool MissingOptionalUnwrapFailure::diagnoseAsError() {

assert(!baseType->hasTypeVariable() &&
"Base type must not be a type variable");
assert(!baseType->isHole() && "Base type must not be a type hole");
assert(!unwrappedType->hasTypeVariable() &&
"Unwrapped type must not be a type variable");
assert(!unwrappedType->isHole() && "Unwrapped type must not be a type hole");

if (!baseType->getOptionalObjectType())
return false;
Expand Down Expand Up @@ -5057,7 +5059,7 @@ bool CollectionElementContextualFailure::diagnoseAsError() {
// holes present in the contextual type.
if (FailureDiagnostic::getContextualTypePurpose(getAnchor()) ==
ContextualTypePurpose::CTP_ForEachStmt &&
contextualType->hasHole()) {
contextualType->hasUnresolvedType()) {
diagnostic.emplace(emitDiagnostic(
(contextualType->is<TupleType>() && !eltType->is<TupleType>())
? diag::cannot_match_expr_tuple_pattern_with_nontuple_value
Expand Down Expand Up @@ -6315,7 +6317,7 @@ bool UnableToInferClosureParameterType::diagnoseAsError() {
if (parentExpr) {
// Missing or invalid member reference in call.
if (auto *AE = dyn_cast<ApplyExpr>(parentExpr)) {
if (getType(AE->getFn())->isHole())
if (getType(AE->getFn())->is<UnresolvedType>())
return false;
}

Expand Down
30 changes: 18 additions & 12 deletions lib/Sema/CSDiagnostics.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,20 +94,26 @@ class FailureDiagnostic {
/// Resolve type variables present in the raw type, if any.
Type resolveType(Type rawType, bool reconstituteSugar = false,
bool wantRValue = true) const {
if (!rawType->hasTypeVariable()) {
if (reconstituteSugar)
rawType = rawType->reconstituteSugar(/*recursive*/ true);
return wantRValue ? rawType->getRValueType() : rawType;
auto &cs = getConstraintSystem();

if (rawType->hasTypeVariable() || rawType->hasHole()) {
rawType = rawType.transform([&](Type type) {
if (auto *typeVar = type->getAs<TypeVariableType>()) {
auto resolvedType = S.simplifyType(typeVar);
Type GP = typeVar->getImpl().getGenericParameter();
return resolvedType->is<UnresolvedType>() && GP
? GP
: resolvedType;
}

return type->isHole() ? Type(cs.getASTContext().TheUnresolvedType)
: type;
});
}

auto &cs = getConstraintSystem();
return cs.simplifyTypeImpl(rawType, [&](TypeVariableType *typeVar) -> Type {
auto fixed = S.simplifyType(typeVar);
auto *genericParam = typeVar->getImpl().getGenericParameter();
if (fixed->isHole() && genericParam)
return genericParam;
return resolveType(fixed, reconstituteSugar, wantRValue);
});
if (reconstituteSugar)
rawType = rawType->reconstituteSugar(/*recursive*/ true);
return wantRValue ? rawType->getRValueType() : rawType;
}

template <typename... ArgTypes>
Expand Down
Loading