Skip to content

Placeholder types: take two #36740

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 13 commits into from
Aug 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -3596,6 +3596,11 @@ ERROR(unresolved_nil_literal,none,
ERROR(cannot_force_unwrap_nil_literal,none,
"'nil' literal cannot be force unwrapped", ())

ERROR(could_not_infer_placeholder,none,
"could not infer type for placeholder", ())
ERROR(top_level_placeholder_type,none,
"placeholders are not allowed as top-level types", ())

ERROR(type_of_expression_is_ambiguous,none,
"type of expression is ambiguous without more context", ())

Expand Down Expand Up @@ -3673,7 +3678,7 @@ NOTE(descriptive_generic_type_declared_here,none,
"%0 declared here", (StringRef))

ERROR(placeholder_type_not_allowed,none,
"you cannot use a placeholder type here", ())
"type placeholder not allowed here", ())

WARNING(use_of_void_pointer,none,
"Unsafe%0Pointer<Void> has been replaced by Unsafe%0RawPointer", (StringRef))
Expand Down
3 changes: 3 additions & 0 deletions include/swift/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,9 @@ class alignas(8) Expr {
/// the parent map.
llvm::DenseMap<Expr *, Expr *> getParentMap();

/// Whether this expression is a valid parent for a TypeExpr.
bool isValidTypeExprParent() const;

SWIFT_DEBUG_DUMP;
void dump(raw_ostream &OS, unsigned Indent = 0) const;
void dump(raw_ostream &OS, llvm::function_ref<Type(Expr *)> getType,
Expand Down
23 changes: 23 additions & 0 deletions include/swift/Sema/CSFix.h
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,10 @@ enum class FixKind : uint8_t {
/// another property wrapper that is a part of the same composed
/// property wrapper.
AllowWrappedValueMismatch,

/// Specify a type for an explicitly written placeholder that could not be
/// resolved.
SpecifyTypeForPlaceholder
};

class ConstraintFix {
Expand Down Expand Up @@ -2257,6 +2261,25 @@ class SpecifyContextualTypeForNil final : public ConstraintFix {
ConstraintLocator * locator);
};

class SpecifyTypeForPlaceholder final : public ConstraintFix {
SpecifyTypeForPlaceholder(ConstraintSystem &cs, ConstraintLocator *locator)
: ConstraintFix(cs, FixKind::SpecifyTypeForPlaceholder, locator) {}

public:
std::string getName() const override {
return "specify type for placeholder";
}

bool diagnose(const Solution &solution, bool asNote = false) const override;

bool diagnoseForAmbiguity(CommonFixesArray commonFixes) const override {
return diagnose(*commonFixes.front().first);
}

static SpecifyTypeForPlaceholder *create(ConstraintSystem &cs,
ConstraintLocator *locator);
};

class AllowRefToInvalidDecl final : public ConstraintFix {
AllowRefToInvalidDecl(ConstraintSystem &cs, ConstraintLocator *locator)
: ConstraintFix(cs, FixKind::AllowRefToInvalidDecl, locator) {}
Expand Down
124 changes: 124 additions & 0 deletions lib/AST/Expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,130 @@ llvm::DenseMap<Expr *, Expr *> Expr::getParentMap() {
return parentMap;
}

bool Expr::isValidTypeExprParent() const {
// Allow references to types as a part of:
// - member references T.foo, T.Type, T.self, etc.
// - constructor calls T()
// - Subscripts T[]
//
// This is an exhaustive list of the accepted syntactic forms.
switch (getKind()) {
case ExprKind::Error:
case ExprKind::DotSelf:
case ExprKind::Call:
case ExprKind::MemberRef:
case ExprKind::UnresolvedMember:
case ExprKind::DotSyntaxCall:
case ExprKind::ConstructorRefCall:
case ExprKind::UnresolvedDot:
case ExprKind::DotSyntaxBaseIgnored:
case ExprKind::UnresolvedSpecialize:
case ExprKind::OpenExistential:
case ExprKind::Subscript:
return true;

case ExprKind::NilLiteral:
case ExprKind::BooleanLiteral:
case ExprKind::IntegerLiteral:
case ExprKind::FloatLiteral:
case ExprKind::StringLiteral:
case ExprKind::MagicIdentifierLiteral:
case ExprKind::InterpolatedStringLiteral:
case ExprKind::ObjectLiteral:
case ExprKind::DiscardAssignment:
case ExprKind::DeclRef:
case ExprKind::SuperRef:
case ExprKind::Type:
case ExprKind::OtherConstructorDeclRef:
case ExprKind::OverloadedDeclRef:
case ExprKind::UnresolvedDeclRef:
case ExprKind::DynamicMemberRef:
case ExprKind::DynamicSubscript:
case ExprKind::Sequence:
case ExprKind::Paren:
case ExprKind::Await:
case ExprKind::UnresolvedMemberChainResult:
case ExprKind::Try:
case ExprKind::ForceTry:
case ExprKind::OptionalTry:
case ExprKind::Tuple:
case ExprKind::Array:
case ExprKind::Dictionary:
case ExprKind::KeyPathApplication:
case ExprKind::TupleElement:
case ExprKind::CaptureList:
case ExprKind::Closure:
case ExprKind::AutoClosure:
case ExprKind::InOut:
case ExprKind::VarargExpansion:
case ExprKind::DynamicType:
case ExprKind::RebindSelfInConstructor:
case ExprKind::OpaqueValue:
case ExprKind::PropertyWrapperValuePlaceholder:
case ExprKind::AppliedPropertyWrapper:
case ExprKind::DefaultArgument:
case ExprKind::BindOptional:
case ExprKind::OptionalEvaluation:
case ExprKind::ForceValue:
case ExprKind::MakeTemporarilyEscapable:
case ExprKind::PrefixUnary:
case ExprKind::PostfixUnary:
case ExprKind::Binary:
case ExprKind::Load:
case ExprKind::DestructureTuple:
case ExprKind::UnresolvedTypeConversion:
case ExprKind::FunctionConversion:
case ExprKind::CovariantFunctionConversion:
case ExprKind::CovariantReturnConversion:
case ExprKind::ImplicitlyUnwrappedFunctionConversion:
case ExprKind::MetatypeConversion:
case ExprKind::CollectionUpcastConversion:
case ExprKind::Erasure:
case ExprKind::AnyHashableErasure:
case ExprKind::BridgeToObjC:
case ExprKind::BridgeFromObjC:
case ExprKind::ConditionalBridgeFromObjC:
case ExprKind::DerivedToBase:
case ExprKind::ArchetypeToSuper:
case ExprKind::InjectIntoOptional:
case ExprKind::ClassMetatypeToObject:
case ExprKind::ExistentialMetatypeToObject:
case ExprKind::ProtocolMetatypeToObject:
case ExprKind::InOutToPointer:
case ExprKind::ArrayToPointer:
case ExprKind::StringToPointer:
case ExprKind::PointerToPointer:
case ExprKind::ForeignObjectConversion:
case ExprKind::UnevaluatedInstance:
case ExprKind::UnderlyingToOpaque:
case ExprKind::DifferentiableFunction:
case ExprKind::LinearFunction:
case ExprKind::DifferentiableFunctionExtractOriginal:
case ExprKind::LinearFunctionExtractOriginal:
case ExprKind::LinearToDifferentiableFunction:
case ExprKind::ForcedCheckedCast:
case ExprKind::ConditionalCheckedCast:
case ExprKind::Is:
case ExprKind::Coerce:
case ExprKind::Arrow:
case ExprKind::If:
case ExprKind::EnumIsCase:
case ExprKind::Assign:
case ExprKind::CodeCompletion:
case ExprKind::UnresolvedPattern:
case ExprKind::LazyInitializer:
case ExprKind::EditorPlaceholder:
case ExprKind::ObjCSelector:
case ExprKind::KeyPath:
case ExprKind::KeyPathDot:
case ExprKind::OneWay:
case ExprKind::Tap:
return false;
}

llvm_unreachable("Unhandled ExprKind in switch.");
}

//===----------------------------------------------------------------------===//
// Support methods for Exprs.
//===----------------------------------------------------------------------===//
Expand Down
1 change: 0 additions & 1 deletion lib/AST/TypeCheckRequests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -957,7 +957,6 @@ void InterfaceTypeRequest::cacheResult(Type type) const {
auto *decl = std::get<0>(getStorage());
if (type) {
assert(!type->hasTypeVariable() && "Type variable in interface type");
assert(!type->hasPlaceholder() && "Type placeholder in interface type");
assert(!type->is<InOutType>() && "Interface type must be materializable");
assert(!type->hasArchetype() && "Archetype in interface type");
}
Expand Down
4 changes: 0 additions & 4 deletions lib/Parse/ParsePattern.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,6 @@ static ParserStatus parseDefaultArgument(
/// Determine whether we are at the start of a parameter name when
/// parsing a parameter.
bool Parser::startsParameterName(bool isClosure) {
// '_' cannot be a type, so it must be a parameter name.
if (Tok.is(tok::kw__))
return true;

// To have a parameter name here, we need a name.
if (!Tok.canBeArgumentLabel())
return false;
Expand Down
7 changes: 7 additions & 0 deletions lib/Parse/ParseType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ LayoutConstraint Parser::parseLayoutConstraint(Identifier LayoutConstraintID) {
/// type-simple '!'
/// type-collection
/// type-array
/// '_'
ParserResult<TypeRepr> Parser::parseTypeSimple(
Diag<> MessageID, ParseTypeReason reason) {
ParserResult<TypeRepr> ty;
Expand Down Expand Up @@ -189,6 +190,9 @@ ParserResult<TypeRepr> Parser::parseTypeSimple(
ty = parseTypeCollection();
break;
}
case tok::kw__:
ty = makeParserResult(new (Context) PlaceholderTypeRepr(consumeToken()));
break;
case tok::kw_protocol:
if (startsWithLess(peekToken())) {
ty = parseOldStyleProtocolComposition();
Expand Down Expand Up @@ -1469,6 +1473,9 @@ bool Parser::canParseType() {
if (!consumeIf(tok::r_square))
return false;
break;
case tok::kw__:
consumeToken();
break;


default:
Expand Down
13 changes: 13 additions & 0 deletions lib/Sema/CSApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6644,6 +6644,19 @@ Expr *ExprRewriter::coerceToType(Expr *expr, Type toType,
Optional<Pattern*> typeFromPattern) {
auto &ctx = cs.getASTContext();

// Diagnose conversions to invalid function types that couldn't be performed
// beforehand because of placeholders.
if (auto *fnTy = toType->getAs<FunctionType>()) {
auto contextTy = cs.getContextualType(expr);
if (cs.getConstraintLocator(locator)->isForContextualType() && contextTy &&
contextTy->hasPlaceholder()) {
bool hadError = TypeChecker::diagnoseInvalidFunctionType(
fnTy, expr->getLoc(), None, dc, None);
if (hadError)
return nullptr;
}
}

// The type we're converting from.
Type fromType = cs.getType(expr);

Expand Down
5 changes: 5 additions & 0 deletions lib/Sema/CSBindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1904,6 +1904,11 @@ TypeVariableBinding::fixForHole(ConstraintSystem &cs) const {
return std::make_pair(fix, defaultImpact);
}

if (srcLocator->isLastElement<LocatorPathElt::PlaceholderType>()) {
ConstraintFix *fix = SpecifyTypeForPlaceholder::create(cs, srcLocator);
return std::make_pair(fix, defaultImpact);
}

if (dstLocator->directlyAt<NilLiteralExpr>()) {
// This is a dramatic event, it means that there is absolutely
// no contextual information to resolve type of `nil`.
Expand Down
13 changes: 13 additions & 0 deletions lib/Sema/CSDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7501,6 +7501,19 @@ bool MissingContextualTypeForNil::diagnoseAsError() {
return true;
}

bool CouldNotInferPlaceholderType::diagnoseAsError() {
// If this placeholder was explicitly written out by the user, they can maybe
// fix things by specifying an actual type.
if (auto *typeExpr = getAsExpr<TypeExpr>(getAnchor())) {
if (typeExpr->getLoc().isValid()) {
emitDiagnostic(diag::could_not_infer_placeholder);
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we have access to any potential bindings here? It would be very cool to surface notes that tell the user what we tried.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good idea! I'll give it a look at see if there's a good way to surface any of that information...

Copy link
Member

Choose a reason for hiding this comment

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

Hmm, I would think that if a non-hole potential binding was attempted and didn't work, a more specific fix would be recorded. If several different bindings were attempted and none of them worked but the solutions all had the same score, we need to diagnose that through the ambiguity mechanism, similar to the "conflicting arguments to generic parameter" message. Here are some different examples with generic arguments, and I think placeholders should behave similarly:

protocol P {}

struct Generic<T>  {
  init() {}
  init(arg: T) where T: P {}
  init(arg1: T, arg2: T) {}
}

func test() {
  // no bindings for T -> missing generic arg fix 
  let generic1: Generic = .init() // error: Generic parameter 'T' could not be inferred

  // one binding for T that didn't work -> specific fix 
  let generic2: Generic = .init(arg: 1) // error: Initializer 'init(arg:)' requires that 'Int' conform to 'P'

  // several bindings for T and none worked -> ambiguity
  let generic3: Generic = .init(arg1: 1, arg2: "") // error: Conflicting arguments to generic parameter 'T' ('Int' vs. 'String')
}

One thing that would be cool, but is probably out of scope for this initial implementation, is recording any requirements on missing placeholders so the user has more information about what it needs to be filled in with.

return true;
}
}

return false;
}

bool ReferenceToInvalidDeclaration::diagnoseAsError() {
auto &DE = getASTContext().Diags;

Expand Down
15 changes: 15 additions & 0 deletions lib/Sema/CSDiagnostics.h
Original file line number Diff line number Diff line change
Expand Up @@ -2406,6 +2406,21 @@ class MissingContextualTypeForNil final : public FailureDiagnostic {
bool diagnoseAsError() override;
};

/// Diagnose situations where there is no context to determine the type of a
/// placeholder, e.g.,
///
/// \code
/// let _ = _.foo
/// \endcode
class CouldNotInferPlaceholderType final : public FailureDiagnostic {
public:
CouldNotInferPlaceholderType(const Solution &solution,
ConstraintLocator *locator)
: FailureDiagnostic(solution, locator) {}

bool diagnoseAsError() override;
};

/// Diagnostic situations where AST node references an invalid declaration.
///
/// \code
Expand Down
12 changes: 12 additions & 0 deletions lib/Sema/CSFix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1750,6 +1750,18 @@ SpecifyContextualTypeForNil::create(ConstraintSystem &cs,
return new (cs.getAllocator()) SpecifyContextualTypeForNil(cs, locator);
}

bool SpecifyTypeForPlaceholder::diagnose(const Solution &solution,
bool asNote) const {
CouldNotInferPlaceholderType failure(solution, getLocator());
return failure.diagnose(asNote);
}

SpecifyTypeForPlaceholder *
SpecifyTypeForPlaceholder::create(ConstraintSystem &cs,
ConstraintLocator *locator) {
return new (cs.getAllocator()) SpecifyTypeForPlaceholder(cs, locator);
}

bool AllowRefToInvalidDecl::diagnose(const Solution &solution,
bool asNote) const {
ReferenceToInvalidDeclaration failure(solution, getLocator());
Expand Down
4 changes: 3 additions & 1 deletion lib/Sema/CSGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2079,7 +2079,9 @@ namespace {
extInfo = extInfo.withGlobalActor(getExplicitGlobalActor(closure));
}

return FunctionType::get(closureParams, resultTy, extInfo);
auto *fnTy = FunctionType::get(closureParams, resultTy, extInfo);
return CS.replaceInferableTypesWithTypeVars(
fnTy, CS.getConstraintLocator(closure))->castTo<FunctionType>();
}

/// Produces a type for the given pattern, filling in any missing
Expand Down
3 changes: 2 additions & 1 deletion lib/Sema/CSSimplify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11484,7 +11484,8 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint(
case FixKind::AllowAlwaysSucceedCheckedCast:
case FixKind::AllowInvalidStaticMemberRefOnProtocolMetatype:
case FixKind::AllowWrappedValueMismatch:
case FixKind::RemoveExtraneousArguments: {
case FixKind::RemoveExtraneousArguments:
case FixKind::SpecifyTypeForPlaceholder: {
return recordFix(fix) ? SolutionKind::Error : SolutionKind::Solved;
}

Expand Down
18 changes: 1 addition & 17 deletions lib/Sema/MiscDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -620,25 +620,9 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC,
// literal since it used to be accepted.
DiagnosticBehavior behavior = DiagnosticBehavior::Error;

// Allow references to types as a part of:
// - member references T.foo, T.Type, T.self, etc.
// - constructor calls T()
// - Subscripts T[]
if (auto *ParentExpr = Parent.getAsExpr()) {
// This is an exhaustive list of the accepted syntactic forms.
if (isa<ErrorExpr>(ParentExpr) ||
isa<DotSelfExpr>(ParentExpr) || // T.self
isa<CallExpr>(ParentExpr) || // T()
isa<MemberRefExpr>(ParentExpr) || // T.foo
isa<UnresolvedMemberExpr>(ParentExpr) ||
isa<SelfApplyExpr>(ParentExpr) || // T.foo() T()
isa<UnresolvedDotExpr>(ParentExpr) ||
isa<DotSyntaxBaseIgnoredExpr>(ParentExpr) ||
isa<UnresolvedSpecializeExpr>(ParentExpr) ||
isa<OpenExistentialExpr>(ParentExpr) ||
isa<SubscriptExpr>(ParentExpr)) {
if (ParentExpr->isValidTypeExprParent())
return;
}

if (!Ctx.LangOpts.isSwiftVersionAtLeast(6)) {
auto argument = CallArgs.find(ParentExpr);
Expand Down
Loading