diff --git a/include/swift/AST/DiagnosticArgument.h b/include/swift/AST/DiagnosticArgument.h index 9f8652db72eaf..0f2386ece8533 100644 --- a/include/swift/AST/DiagnosticArgument.h +++ b/include/swift/AST/DiagnosticArgument.h @@ -40,6 +40,7 @@ enum class ReferenceOwnership : uint8_t; enum class SelfAccessKind : uint8_t; enum class StaticSpellingKind : uint8_t; enum class StmtKind; +enum class ExprKind : uint8_t; /// A family of wrapper types for compiler data types that forces its /// underlying data to be formatted with full qualification. @@ -85,6 +86,7 @@ enum class DiagnosticArgumentKind { StaticSpellingKind, DescriptiveDeclKind, DescriptiveStmtKind, + DescriptiveExprKind, DeclAttribute, TypeAttribute, AvailabilityDomain, @@ -121,6 +123,7 @@ class DiagnosticArgument { StaticSpellingKind StaticSpellingKindVal; DescriptiveDeclKind DescriptiveDeclKindVal; StmtKind DescriptiveStmtKindVal; + ExprKind DescriptiveExprKindVal; const DeclAttribute *DeclAttributeVal; const TypeAttribute *TypeAttributeVal; AvailabilityDomain AvailabilityDomainVal; @@ -155,6 +158,7 @@ class DiagnosticArgument { DiagnosticArgument(StaticSpellingKind SSK); DiagnosticArgument(DescriptiveDeclKind DDK); DiagnosticArgument(StmtKind SK); + DiagnosticArgument(ExprKind EK); DiagnosticArgument(const DeclAttribute *attr); DiagnosticArgument(const TypeAttribute *attr); DiagnosticArgument(const AvailabilityDomain domain); @@ -195,6 +199,7 @@ class DiagnosticArgument { StaticSpellingKind getAsStaticSpellingKind() const; DescriptiveDeclKind getAsDescriptiveDeclKind() const; StmtKind getAsDescriptiveStmtKind() const; + ExprKind getAsDescriptiveExprKind() const; const DeclAttribute *getAsDeclAttribute() const; const TypeAttribute *getAsTypeAttribute() const; const AvailabilityDomain getAsAvailabilityDomain() const; diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 4471335044ca4..8057c59d43ab5 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -8851,5 +8851,12 @@ ERROR(invalid_redecl_of_file_isolation,none, NOTE(invalid_redecl_of_file_isolation_prev,none, "default isolation was previously declared here", ()) +//===----------------------------------------------------------------------===// +// MARK: Tooling remarks +//===----------------------------------------------------------------------===// + +REMARK(remark_inferred_type,none, + "%0 was inferred to be of type %1", (ExprKind, Type)) + #define UNDEFINE_DIAGNOSTIC_MACROS #include "DefineDiagnosticMacros.h" diff --git a/include/swift/AST/Expr.h b/include/swift/AST/Expr.h index 7d8e07de69957..219899a2885c9 100644 --- a/include/swift/AST/Expr.h +++ b/include/swift/AST/Expr.h @@ -414,6 +414,10 @@ class alignas(8) Expr : public ASTAllocated { /// to the user of the compiler in any way. static StringRef getKindName(ExprKind K); + /// Retrieve the descriptive kind name for a given expression. This is + /// suitable for use in diagnostics. + static StringRef getDescriptiveKindName(ExprKind K); + /// getType - Return the type of this expression. Type getType() const { return Ty; } diff --git a/include/swift/Basic/LangOptions.h b/include/swift/Basic/LangOptions.h index 3e807661d38fe..bab23b886091f 100644 --- a/include/swift/Basic/LangOptions.h +++ b/include/swift/Basic/LangOptions.h @@ -982,6 +982,11 @@ namespace swift { /// Should be stored sorted. llvm::SmallVector DebugConstraintSolverOnLines; + /// Expressions with source ranges containing these line/column positions + /// should emit inferred type remarks. Should be stored sorted. + llvm::SmallVector, 4> + InferredTypesRemarksAtPositions; + /// Triggers llvm fatal error if the typechecker tries to typecheck a decl /// or an identifier reference with any of the provided prefix names. This /// is for testing purposes. diff --git a/include/swift/Option/FrontendOptions.td b/include/swift/Option/FrontendOptions.td index 77322cbb18ef2..e9770f43424d4 100644 --- a/include/swift/Option/FrontendOptions.td +++ b/include/swift/Option/FrontendOptions.td @@ -276,6 +276,11 @@ def dependency_scan_cache_path : Separate<["-"], "dependency-scan-cache-path">, def dependency_scan_cache_remarks : Flag<["-"], "Rdependency-scan-cache">, HelpText<"Emit remarks indicating use of the serialized module dependency scanning cache.">; +def inferred_types_at : Separate<["-"], "Rinferred-types-at">, + Flags<[FrontendOption, DoesNotAffectIncrementalBuild]>, + HelpText<"Emit remarks describing inferred types for expression(s) at ">, + MetaVarName<"">; + def parallel_scan : Flag<["-"], "parallel-scan">, HelpText<"Perform dependency scanning in-parallel.">; def no_parallel_scan : Flag<["-"], "no-parallel-scan">, diff --git a/include/swift/Sema/ConstraintSystem.h b/include/swift/Sema/ConstraintSystem.h index 4992cfad31f81..5f99dbce37247 100644 --- a/include/swift/Sema/ConstraintSystem.h +++ b/include/swift/Sema/ConstraintSystem.h @@ -6612,6 +6612,11 @@ unsigned getNumApplications(bool hasAppliedSelf, /// Determine whether the debug output is enabled for the given target. bool debugConstraintSolverForTarget(ASTContext &C, SyntacticElementTarget target); + +/// Determine whether inferred type remarks should be emitted for the given +/// target. +bool shouldEmitInferredTypesRemarksForTarget(ASTContext &C, + SyntacticElementTarget target); } // end namespace constraints template diff --git a/lib/AST/DiagnosticArgument.cpp b/lib/AST/DiagnosticArgument.cpp index 18d4b8dc5a3b3..17cd406220ca1 100644 --- a/lib/AST/DiagnosticArgument.cpp +++ b/lib/AST/DiagnosticArgument.cpp @@ -88,6 +88,10 @@ DiagnosticArgument::DiagnosticArgument(StmtKind SK) : Kind(DiagnosticArgumentKind::DescriptiveStmtKind), DescriptiveStmtKindVal(SK) {} +DiagnosticArgument::DiagnosticArgument(ExprKind EK) + : Kind(DiagnosticArgumentKind::DescriptiveExprKind), + DescriptiveExprKindVal(EK) {} + DiagnosticArgument::DiagnosticArgument(const DeclAttribute *attr) : Kind(DiagnosticArgumentKind::DeclAttribute), DeclAttributeVal(attr) {} @@ -205,6 +209,11 @@ StmtKind DiagnosticArgument::getAsDescriptiveStmtKind() const { return DescriptiveStmtKindVal; } +ExprKind DiagnosticArgument::getAsDescriptiveExprKind() const { + ASSERT(Kind == DiagnosticArgumentKind::DescriptiveExprKind); + return DescriptiveExprKindVal; +} + const DeclAttribute *DiagnosticArgument::getAsDeclAttribute() const { ASSERT(Kind == DiagnosticArgumentKind::DeclAttribute); return DeclAttributeVal; diff --git a/lib/AST/DiagnosticEngine.cpp b/lib/AST/DiagnosticEngine.cpp index a26d6eafec562..2069c36148fb3 100644 --- a/lib/AST/DiagnosticEngine.cpp +++ b/lib/AST/DiagnosticEngine.cpp @@ -1075,6 +1075,11 @@ static void formatDiagnosticArgument(StringRef Modifier, Out << Stmt::getDescriptiveKindName(Arg.getAsDescriptiveStmtKind()); break; + case DiagnosticArgumentKind::DescriptiveExprKind: + assert(Modifier.empty() && "Improper modifier for ExprKind argument"); + Out << Expr::getDescriptiveKindName(Arg.getAsDescriptiveExprKind()); + break; + case DiagnosticArgumentKind::DeclAttribute: { auto *const attr = Arg.getAsDeclAttribute(); const auto printAttrName = [&] { diff --git a/lib/AST/Expr.cpp b/lib/AST/Expr.cpp index 85136687ead44..f1513b9e5fddc 100644 --- a/lib/AST/Expr.cpp +++ b/lib/AST/Expr.cpp @@ -54,6 +54,260 @@ StringRef Expr::getKindName(ExprKind K) { llvm_unreachable("bad ExprKind"); } +StringRef Expr::getDescriptiveKindName(ExprKind K) { + switch (K) { + case ExprKind::Error: + return "unknown expression"; + case ExprKind::CodeCompletion: + return "code completion expression"; + case ExprKind::NilLiteral: + return "`nil` literal"; + case ExprKind::IntegerLiteral: + return "integer literal"; + case ExprKind::FloatLiteral: + return "floating point literal"; + case ExprKind::BooleanLiteral: + return "boolean literal"; + case ExprKind::StringLiteral: + return "string literal"; + case ExprKind::InterpolatedStringLiteral: + return "interpolated string literal"; + case ExprKind::MagicIdentifierLiteral: + return "builtin literal"; + case ExprKind::RegexLiteral: + return "regex literal"; + case ExprKind::ObjectLiteral: + return "object literal"; + case ExprKind::DiscardAssignment: + return "discarded assignment expression"; + case ExprKind::DeclRef: + return "declaration reference"; + case ExprKind::SuperRef: + return "`super` reference"; + case ExprKind::Type: + return "type expression"; + case ExprKind::OtherConstructorDeclRef: + return "initializer reference"; + case ExprKind::OverloadedDeclRef: + return "overloaded declaration reference"; + case ExprKind::UnresolvedDeclRef: + return "declaration reference"; + case ExprKind::MemberRef: + return "member reference"; + case ExprKind::DynamicMemberRef: + return "dynamic member reference"; + case ExprKind::UnresolvedSpecialize: + return "generic specialization expression"; + case ExprKind::UnresolvedMember: + return "member reference"; + case ExprKind::UnresolvedDot: + return "`.` expression"; + case ExprKind::Sequence: + return "operator sequence expression"; + case ExprKind::Paren: + return "parenthesized expression"; + case ExprKind::DotSelf: + return "`.self` expression"; + case ExprKind::Unsafe: + return "`unsafe` expression"; + case ExprKind::UnresolvedMemberChainResult: + return "member chain expression"; + case ExprKind::Tuple: + return "tuple expression"; + case ExprKind::Array: + return "array literal"; + case ExprKind::Dictionary: + return "dictionary literal"; + case ExprKind::Subscript: + return "subscript expression"; + case ExprKind::KeyPathApplication: + return "key path application expression"; + case ExprKind::TupleElement: + return "tuple element expression"; + case ExprKind::CaptureList: + return "capture list expression"; + case ExprKind::Closure: + return "closure expression"; + case ExprKind::AutoClosure: + return "autoclosure expression"; + case ExprKind::InOut: + return "inout expression"; + case ExprKind::PackExpansion: + return "pack expansion expression"; + case ExprKind::PackElement: + return "pack element expression"; + case ExprKind::MaterializePack: + return "pack materialization expression"; + case ExprKind::ExtractFunctionIsolation: + return "function isolation extraction expression"; + case ExprKind::VarargExpansion: + return "variadic arguments expansion expression"; + case ExprKind::DynamicSubscript: + return "dynamic subscript expression"; + case ExprKind::CovariantFunctionConversion: + return "covariant function conversion expression"; + case ExprKind::CovariantReturnConversion: + return "covariant return conversion expression"; + case ExprKind::MetatypeConversion: + return "metatype conversion expression"; + case ExprKind::CollectionUpcastConversion: + return "collection upcast conversion expression"; + case ExprKind::Erasure: + return "type erasure expression"; + case ExprKind::AnyHashableErasure: + return "`AnyHashable` erasure expression"; + case ExprKind::DerivedToBase: + return "derived-to-base conversion expression"; + case ExprKind::ArchetypeToSuper: + return "archetype-to-superclass conversion expression"; + case ExprKind::InjectIntoOptional: + return "optional injection expression"; + case ExprKind::ClassMetatypeToObject: + return "class metatype conversion expression"; + case ExprKind::ExistentialMetatypeToObject: + return "existential metatype conversion expression"; + case ExprKind::ProtocolMetatypeToObject: + return "protocol metatype conversion expression"; + case ExprKind::InOutToPointer: + return "inout-to-pointer conversion expression"; + case ExprKind::ArrayToPointer: + return "array-to-pointer conversion expression"; + case ExprKind::StringToPointer: + return "string-to-pointer conversion expression"; + case ExprKind::PointerToPointer: + return "pointer conversion expression"; + case ExprKind::ForeignObjectConversion: + return "foreign object conversion expression"; + case ExprKind::UnevaluatedInstance: + return "unevaluated instance expression"; + case ExprKind::UnderlyingToOpaque: + return "underlying-to-opaque type conversion expression"; + case ExprKind::Unreachable: + return "unreachable expression"; + case ExprKind::DifferentiableFunction: + return "differentiable function conversion expression"; + case ExprKind::LinearFunction: + return "linear function conversion expression"; + case ExprKind::DifferentiableFunctionExtractOriginal: + return "differentiable function extraction expression"; + case ExprKind::LinearFunctionExtractOriginal: + return "linear function extraction expression"; + case ExprKind::LinearToDifferentiableFunction: + return "linear-to-differentiable function conversion expression"; + case ExprKind::ActorIsolationErasure: + return "actor isolation erasure expression"; + case ExprKind::UnsafeCast: + return "unsafe cast expression"; + case ExprKind::Load: + return "load expression"; + case ExprKind::DestructureTuple: + return "tuple destructuring expression"; + case ExprKind::ABISafeConversion: + return "ABI-safe conversion expression"; + case ExprKind::UnresolvedTypeConversion: + return "unresolved type conversion expression"; + case ExprKind::FunctionConversion: + return "function conversion expression"; + case ExprKind::Coerce: + return "coercion expression"; + case ExprKind::ConditionalBridgeFromObjC: + return "conditional Objective-C to Swift bridging expression"; + case ExprKind::BridgeToObjC: + return "Swift to Objective-C bridging expression"; + case ExprKind::BridgeFromObjC: + return "Objective-C to Swift bridging expression"; + case ExprKind::Call: + return "call expression"; + case ExprKind::PrefixUnary: + return "unary prefix expression"; + case ExprKind::PostfixUnary: + return "unary postfix expression"; + case ExprKind::Binary: + return "binary expression"; + case ExprKind::DotSyntaxCall: + return "method call expression"; + case ExprKind::ConstructorRefCall: + return "initializer call expression"; + case ExprKind::DotSyntaxBaseIgnored: + return "`.` expression"; + case ExprKind::DynamicType: + return "dynamic type expression"; + case ExprKind::RebindSelfInConstructor: + return "`self` reassignment expression"; + case ExprKind::OpaqueValue: + return "opaque value expression"; + case ExprKind::PropertyWrapperValuePlaceholder: + return "property wrapper placeholder value expression"; + case ExprKind::AppliedPropertyWrapper: + return "applied property wrapper expression"; + case ExprKind::DefaultArgument: + return "default argument expression"; + case ExprKind::BindOptional: + return "optional binding expression"; + case ExprKind::OptionalEvaluation: + return "optional evaluation expression"; + case ExprKind::ForceValue: + return "force unwrap expression"; + case ExprKind::OpenExistential: + return "existential opening expression"; + case ExprKind::MakeTemporarilyEscapable: + return "temporary escaping expression"; + case ExprKind::KeyPath: + return "key path expression"; + case ExprKind::KeyPathDot: + return "key path `.` expression"; + case ExprKind::Tap: + return "tap expression"; + case ExprKind::TypeJoin: + return "type join expression"; + case ExprKind::Ternary: + return "ternary expression"; + case ExprKind::Assign: + return "assignment expression"; + case ExprKind::UnresolvedPattern: + return "pattern expression"; + case ExprKind::EditorPlaceholder: + return "editor placeholder expression"; + case ExprKind::LazyInitializer: + return "lazy initializer expression"; + case ExprKind::ObjCSelector: + return "Objective-C selector expression"; + case ExprKind::Consume: + return "`consume` expression"; + case ExprKind::Copy: + return "`copy` expression"; + case ExprKind::Borrow: + return "`borrow` expression"; + case ExprKind::Try: + return "`try` expression"; + case ExprKind::ForceTry: + return "`try!` expression"; + case ExprKind::OptionalTry: + return "`try?` expression"; + case ExprKind::Await: + return "`await` expression"; + case ExprKind::Is: + return "`is` expression"; + case ExprKind::ConditionalCheckedCast: + return "`as?` expression"; + case ExprKind::Arrow: + return "`->` expression"; + case ExprKind::ForcedCheckedCast: + return "`as!` expression"; + case ExprKind::MacroExpansion: + return "macro expansion expression"; + case ExprKind::TypeValue: + return "type value expression"; + case ExprKind::CurrentContextIsolation: + return "`#isolation` expression"; + case ExprKind::SingleValueStmt: + return "statement expression"; + case ExprKind::EnumIsCase: + return "`case` cast expression"; + } + llvm_unreachable("Unhandled case in switch!"); +} + template static SourceLoc getStartLocImpl(const T *E); template static SourceLoc getEndLocImpl(const T *E); template static SourceLoc getLocImpl(const T *E); diff --git a/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp index e4d91285b4458..729f7431d7ddc 100644 --- a/lib/Frontend/CompilerInvocation.cpp +++ b/lib/Frontend/CompilerInvocation.cpp @@ -1982,6 +1982,28 @@ static bool ParseTypeCheckerArgs(TypeCheckerOptions &Opts, ArgList &Args, } llvm::sort(Opts.DebugConstraintSolverOnLines); + for (const Arg *A : Args.filtered(OPT_inferred_types_at)) { + StringRef value = A->getValue(); + auto colonPos = value.find(':'); + if (colonPos == StringRef::npos) { + Diags.diagnose(SourceLoc(), diag::error_invalid_arg_value, + A->getAsString(Args), A->getValue()); + HadError = true; + continue; + } + + unsigned line, column; + if (value.substr(0, colonPos).getAsInteger(/*radix*/ 10, line) || + value.substr(colonPos + 1).getAsInteger(/*radix*/ 10, column)) { + Diags.diagnose(SourceLoc(), diag::error_invalid_arg_value, + A->getAsString(Args), A->getValue()); + HadError = true; + } else { + Opts.InferredTypesRemarksAtPositions.push_back({line, column}); + } + } + llvm::sort(Opts.InferredTypesRemarksAtPositions); + for (auto A : Args.getAllArgValues(OPT_debug_forbid_typecheck_prefix)) { Opts.DebugForbidTypecheckPrefixes.push_back(A); } diff --git a/lib/Sema/CSSolver.cpp b/lib/Sema/CSSolver.cpp index 2f6d0c5f0100d..4283fe8ef32df 100644 --- a/lib/Sema/CSSolver.cpp +++ b/lib/Sema/CSSolver.cpp @@ -1391,6 +1391,43 @@ bool constraints::debugConstraintSolverForTarget( return startBound != endBound; } +bool constraints::shouldEmitInferredTypesRemarksForTarget( + ASTContext &C, SyntacticElementTarget target) { + if (C.TypeCheckerOpts.InferredTypesRemarksAtPositions.empty()) + return false; + + SourceRange range = target.getSourceRange(); + if (!range.isValid()) + return false; + + auto charRange = Lexer::getCharSourceRangeFromSourceRange(C.SourceMgr, range); + auto startLineCol = + C.SourceMgr.getLineAndColumnInBuffer(charRange.getStart()); + auto endLineCol = C.SourceMgr.getLineAndColumnInBuffer(charRange.getEnd()); + + auto &positions = C.TypeCheckerOpts.InferredTypesRemarksAtPositions; + assert(std::is_sorted(positions.begin(), positions.end()) && + "InferredTypesRemarksAtPositions sorting invariant violated"); + + for (const auto &pos : positions) { + if (pos.first < startLineCol.first || pos.first > endLineCol.first) + continue; + + if (pos.first == startLineCol.first && pos.first == endLineCol.first) { + return pos.second >= startLineCol.second && + pos.second <= endLineCol.second; + } else if (pos.first == startLineCol.first) { + return pos.second >= startLineCol.second; + } else if (pos.first == endLineCol.first) { + return pos.second <= endLineCol.second; + } else { + return true; + } + } + + return false; +} + std::optional> ConstraintSystem::solve(SyntacticElementTarget &target, FreeTypeVariableBinding allowFreeTypeVariables) { diff --git a/lib/Sema/TypeCheckConstraints.cpp b/lib/Sema/TypeCheckConstraints.cpp index 4650ef71c60b0..afc262e583c98 100644 --- a/lib/Sema/TypeCheckConstraints.cpp +++ b/lib/Sema/TypeCheckConstraints.cpp @@ -508,6 +508,32 @@ TypeChecker::typeCheckTarget(SyntacticElementTarget &target, return errorResult(); } + // Emit inferred type remarks if requested. + if (constraints::shouldEmitInferredTypesRemarksForTarget(Context, + *resultTarget)) { + if (auto *expr = resultTarget->getAsExpr()) { + class InferredTypeRemarkWalker : public ASTWalker { + ASTContext &Context; + + public: + InferredTypeRemarkWalker(ASTContext &ctx) : Context(ctx) {} + + PreWalkResult walkToExprPre(Expr *E) override { + if (auto type = E->getType()) { + auto diag = Context.Diags.diagnose(E->getStartLoc(), + diag::remark_inferred_type, + E->getKind(), type); + diag.highlight(E->getSourceRange()); + } + return Action::Continue(E); + } + }; + + InferredTypeRemarkWalker walker(Context); + expr->walk(walker); + } + } + // Unless the client has disabled them, perform syntactic checks on the // expression now. if (!cs.shouldSuppressDiagnostics()) { diff --git a/test/Sema/inferred-type-remarks.swift b/test/Sema/inferred-type-remarks.swift new file mode 100644 index 0000000000000..3f22b7aba7656 --- /dev/null +++ b/test/Sema/inferred-type-remarks.swift @@ -0,0 +1,15 @@ +// RUN: %target-swift-frontend -typecheck -Rinferred-types-at 9:12 -verify %s + +func bar() -> T? { nil /* No remarks should be emitted for this literal */ } + +func foo(x: Int) -> String? { + let myBool = (1 + x == 2) ? false : true // No remarks should be emitted for this expression + return if myBool { // expected-remark {{statement expression was inferred to be of type 'String?'}} + // expected-remark@-1 {{declaration reference was inferred to be of type 'Bool'}} + "hello, world!" // expected-remark {{string literal was inferred to be of type 'String'}} + // expected-remark@-1 {{optional injection expression was inferred to be of type 'String?'}} + } else { + bar() // expected-remark {{call expression was inferred to be of type 'String?'}} + // expected-remark@-1 {{declaration reference was inferred to be of type '() -> String?'}} + } +} \ No newline at end of file