mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Revert "Merge pull request #78280 from swiftlang/revert-77140-swift-lexical-lookup-validation"
This reverts commitae88aaca8f, reversing changes made tob0123bca14.
This commit is contained in:
@@ -101,6 +101,21 @@ struct BridgedLocatedIdentifier {
|
||||
BridgedSourceLoc NameLoc;
|
||||
};
|
||||
|
||||
struct BridgedConsumedLookupResult {
|
||||
SWIFT_NAME("name")
|
||||
BridgedIdentifier Name;
|
||||
|
||||
SWIFT_NAME("nameLoc")
|
||||
BridgedSourceLoc NameLoc;
|
||||
|
||||
SWIFT_NAME("flag")
|
||||
SwiftInt Flag;
|
||||
|
||||
BRIDGED_INLINE BridgedConsumedLookupResult(swift::Identifier name,
|
||||
swift::SourceLoc sourceLoc,
|
||||
SwiftInt flag);
|
||||
};
|
||||
|
||||
class BridgedDeclBaseName {
|
||||
BridgedIdentifier Ident;
|
||||
|
||||
|
||||
@@ -55,6 +55,15 @@ swift::DeclBaseName BridgedDeclBaseName::unbridged() const {
|
||||
return swift::DeclBaseName(Ident.unbridged());
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// MARK: BridgedDeclBaseName
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
BridgedConsumedLookupResult::BridgedConsumedLookupResult(
|
||||
swift::Identifier name, swift::SourceLoc sourceLoc, SwiftInt flag)
|
||||
: Name(BridgedIdentifier(name)), NameLoc(BridgedSourceLoc(sourceLoc)),
|
||||
Flag(flag) {}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// MARK: BridgedDeclNameRef
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@@ -236,6 +236,13 @@ NOTE(in_macro_expansion,none,
|
||||
ERROR(macro_experimental,none,
|
||||
"%0 macros are an experimental feature that is not enabled %select{|(%1)}1",
|
||||
(StringRef, StringRef))
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// MARK: lexical lookup diagnostics
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
ERROR(lookup_outputs_dont_match,none,
|
||||
"Unqualified lookup output from ASTScope and SwiftLexicalLookup don't match", ())
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// MARK: bridged diagnostics
|
||||
|
||||
@@ -301,6 +301,10 @@ EXPERIMENTAL_FEATURE(ParserRoundTrip, false)
|
||||
/// Swift parser.
|
||||
EXPERIMENTAL_FEATURE(ParserValidation, false)
|
||||
|
||||
/// Whether to perform validation of the unqualified lookup produced by
|
||||
/// ASTScope and SwiftLexicalLookup
|
||||
EXPERIMENTAL_FEATURE(UnqualifiedLookupValidation, false)
|
||||
|
||||
/// Enables implicit some while also enabling existential `any`
|
||||
EXPERIMENTAL_FEATURE(ImplicitSome, false)
|
||||
|
||||
|
||||
@@ -98,6 +98,13 @@ intptr_t swift_ASTGen_configuredRegions(
|
||||
void swift_ASTGen_freeConfiguredRegions(
|
||||
BridgedIfConfigClauseRangeInfo *_Nullable regions, intptr_t numRegions);
|
||||
|
||||
bool swift_ASTGen_validateUnqualifiedLookup(
|
||||
void *_Nonnull sourceFile,
|
||||
BridgedASTContext astContext,
|
||||
BridgedSourceLoc sourceLoc,
|
||||
bool finishInSequentialScope,
|
||||
BridgedArrayRef astScopeResultRef);
|
||||
|
||||
size_t
|
||||
swift_ASTGen_virtualFiles(void *_Nonnull sourceFile,
|
||||
BridgedVirtualFile *_Nullable *_Nonnull virtualFiles);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include "swift/AST/ASTContext.h"
|
||||
#include "swift/AST/ASTWalker.h"
|
||||
#include "swift/Bridging/ASTGen.h"
|
||||
#include "swift/AST/Decl.h"
|
||||
#include "swift/AST/Expr.h"
|
||||
#include "swift/AST/Initializer.h"
|
||||
@@ -39,6 +40,104 @@ using namespace ast_scope;
|
||||
|
||||
#pragma mark ASTScope
|
||||
|
||||
class LoggingASTScopeDeclConsumer
|
||||
: public namelookup::AbstractASTScopeDeclConsumer {
|
||||
private:
|
||||
const int shouldLookInMembers = 0b10;
|
||||
namelookup::AbstractASTScopeDeclConsumer *originalConsumer;
|
||||
|
||||
public:
|
||||
mutable SmallVector<BridgedConsumedLookupResult> recordedElements;
|
||||
|
||||
LoggingASTScopeDeclConsumer(
|
||||
namelookup::AbstractASTScopeDeclConsumer *consumer)
|
||||
: originalConsumer(consumer) {}
|
||||
|
||||
~LoggingASTScopeDeclConsumer() = default;
|
||||
|
||||
/// Called for every ValueDecl visible from the lookup.
|
||||
///
|
||||
/// Takes an array in order to batch the consumption before setting
|
||||
/// IndexOfFirstOuterResult when necessary.
|
||||
///
|
||||
/// Additionally, each name is logged to `recordedElements` and
|
||||
/// can be later used in validation of `SwiftLexicalLookup` result.
|
||||
///
|
||||
/// \param baseDC either a type context or the local context of a
|
||||
/// `self` parameter declaration. See LookupResult for a discussion
|
||||
/// of type -vs- instance lookup results.
|
||||
///
|
||||
/// \return true if the lookup should be stopped at this point.
|
||||
bool consume(ArrayRef<ValueDecl *> values,
|
||||
NullablePtr<DeclContext> baseDC = nullptr) override {
|
||||
bool endOfLookup = originalConsumer->consume(values, baseDC);
|
||||
|
||||
for (auto value : values) {
|
||||
if (auto sourceLoc = value->getLoc()) {
|
||||
recordedElements.push_back(BridgedConsumedLookupResult(
|
||||
value->getBaseIdentifier(), sourceLoc, endOfLookup));
|
||||
} else {
|
||||
// If sourceLoc is unavailable, use location of it's parent.
|
||||
recordedElements.push_back(BridgedConsumedLookupResult(
|
||||
value->getBaseIdentifier(),
|
||||
value->getDeclContext()->getAsDecl()->getLoc(), endOfLookup));
|
||||
}
|
||||
}
|
||||
|
||||
return endOfLookup;
|
||||
};
|
||||
|
||||
/// Look for members of a nominal type or extension scope.
|
||||
///
|
||||
/// Each call is recorded in `recordedElements` with a special flag set.
|
||||
/// It can be later used in validation of `SwiftLexicalLookup` result.
|
||||
///
|
||||
/// \return true if the lookup should be stopped at this point.
|
||||
bool lookInMembers(const DeclContext *scopeDC) const override {
|
||||
bool endOfLookup = originalConsumer->lookInMembers(scopeDC);
|
||||
|
||||
if (auto *extDecl = dyn_cast<ExtensionDecl>(scopeDC)) {
|
||||
recordedElements.push_back(BridgedConsumedLookupResult(
|
||||
Identifier(), extDecl->getExtendedTypeRepr()->getLoc(),
|
||||
shouldLookInMembers + endOfLookup));
|
||||
} else {
|
||||
recordedElements.push_back(BridgedConsumedLookupResult(
|
||||
scopeDC->getSelfNominalTypeDecl()->getBaseIdentifier(),
|
||||
scopeDC->getAsDecl()->getLoc(), shouldLookInMembers + endOfLookup));
|
||||
}
|
||||
|
||||
return endOfLookup;
|
||||
};
|
||||
|
||||
/// Called for local VarDecls that might not yet be in scope.
|
||||
///
|
||||
/// Note that the set of VarDecls visited here are going to be a
|
||||
/// superset of those visited in consume().
|
||||
bool consumePossiblyNotInScope(ArrayRef<VarDecl *> values) override {
|
||||
bool result = originalConsumer->consumePossiblyNotInScope(values);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Called right before looking at the parent scope of a BraceStmt.
|
||||
///
|
||||
/// \return true if the lookup should be stopped at this point.
|
||||
bool finishLookupInBraceStmt(BraceStmt *stmt) override {
|
||||
return originalConsumer->finishLookupInBraceStmt(stmt);
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
void startingNextLookupStep() override {
|
||||
originalConsumer->startingNextLookupStep();
|
||||
}
|
||||
void finishingLookup(std::string input) const override {
|
||||
originalConsumer->finishingLookup(input);
|
||||
}
|
||||
bool isTargetLookup() const override {
|
||||
return originalConsumer->isTargetLookup();
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
void ASTScope::unqualifiedLookup(
|
||||
SourceFile *SF, SourceLoc loc,
|
||||
namelookup::AbstractASTScopeDeclConsumer &consumer) {
|
||||
@@ -48,7 +147,30 @@ void ASTScope::unqualifiedLookup(
|
||||
|
||||
if (auto *s = SF->getASTContext().Stats)
|
||||
++s->getFrontendCounters().NumASTScopeLookups;
|
||||
ASTScopeImpl::unqualifiedLookup(SF, loc, consumer);
|
||||
|
||||
// Perform validation of SwiftLexicalLookup if option
|
||||
// Feature::UnqualifiedLookupValidation is enabled and lookup was not
|
||||
// performed in a macro.
|
||||
if (SF->getASTContext().LangOpts.hasFeature(
|
||||
Feature::UnqualifiedLookupValidation) &&
|
||||
!SF->getEnclosingSourceFile()) {
|
||||
LoggingASTScopeDeclConsumer loggingASTScopeDeclConsumer =
|
||||
LoggingASTScopeDeclConsumer(&consumer);
|
||||
|
||||
ASTScopeImpl::unqualifiedLookup(SF, loc, loggingASTScopeDeclConsumer);
|
||||
|
||||
bool passed = swift_ASTGen_validateUnqualifiedLookup(
|
||||
SF->getExportedSourceFile(), SF->getASTContext(), loc,
|
||||
loggingASTScopeDeclConsumer.finishLookupInBraceStmt(nullptr),
|
||||
BridgedArrayRef(loggingASTScopeDeclConsumer.recordedElements.data(),
|
||||
loggingASTScopeDeclConsumer.recordedElements.size()));
|
||||
|
||||
if (!passed) {
|
||||
SF->getASTContext().Diags.diagnose(loc, diag::lookup_outputs_dont_match);
|
||||
}
|
||||
} else {
|
||||
ASTScopeImpl::unqualifiedLookup(SF, loc, consumer);
|
||||
}
|
||||
}
|
||||
|
||||
llvm::SmallVector<LabeledStmt *, 4> ASTScope::lookupLabeledStmts(
|
||||
|
||||
@@ -103,6 +103,7 @@ UNINTERESTING_FEATURE(OpaqueTypeErasure)
|
||||
UNINTERESTING_FEATURE(PackageCMO)
|
||||
UNINTERESTING_FEATURE(ParserRoundTrip)
|
||||
UNINTERESTING_FEATURE(ParserValidation)
|
||||
UNINTERESTING_FEATURE(UnqualifiedLookupValidation)
|
||||
UNINTERESTING_FEATURE(ImplicitSome)
|
||||
UNINTERESTING_FEATURE(ParserASTGen)
|
||||
UNINTERESTING_FEATURE(BuiltinMacros)
|
||||
|
||||
@@ -56,6 +56,7 @@ let package = Package(
|
||||
dependencies: [
|
||||
.product(name: "SwiftDiagnostics", package: "swift-syntax"),
|
||||
.product(name: "SwiftIfConfig", package: "swift-syntax"),
|
||||
.product(name: "SwiftLexicalLookup", package: "swift-syntax"),
|
||||
.product(name: "SwiftOperators", package: "swift-syntax"),
|
||||
.product(name: "SwiftParser", package: "swift-syntax"),
|
||||
.product(name: "SwiftParserDiagnostics", package: "swift-syntax"),
|
||||
|
||||
@@ -10,6 +10,7 @@ add_pure_swift_host_library(swiftASTGen STATIC CXX_INTEROP
|
||||
Exprs.swift
|
||||
Fingerprint.swift
|
||||
Generics.swift
|
||||
LexicalLookup.swift
|
||||
Literals.swift
|
||||
ParameterClause.swift
|
||||
Patterns.swift
|
||||
@@ -26,6 +27,7 @@ add_pure_swift_host_library(swiftASTGen STATIC CXX_INTEROP
|
||||
_CompilerRegexParser
|
||||
_CompilerSwiftSyntax
|
||||
_CompilerSwiftIfConfig
|
||||
_CompilerSwiftLexicalLookup
|
||||
_CompilerSwiftOperators
|
||||
_CompilerSwiftSyntaxBuilder
|
||||
_CompilerSwiftParser
|
||||
|
||||
640
lib/ASTGen/Sources/ASTGen/LexicalLookup.swift
Normal file
640
lib/ASTGen/Sources/ASTGen/LexicalLookup.swift
Normal file
@@ -0,0 +1,640 @@
|
||||
//===--- LexicalLookup.swift ----------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the Swift.org open source project
|
||||
//
|
||||
// Copyright (c) 2022-2024 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import ASTBridging
|
||||
import BasicBridging
|
||||
import SwiftIfConfig
|
||||
@_spi(Experimental) import SwiftLexicalLookup
|
||||
import SwiftSyntax
|
||||
|
||||
private let rowCharWidth: Int = 30
|
||||
|
||||
/// This function validates output of SwiftLexicalLookup
|
||||
/// against the output of `ASTScope` passed to `astScopeResultRef`.
|
||||
///
|
||||
/// The function assigns specific flags to obtained names. The flags are applied in three phases:
|
||||
/// - `ASTScope` name extraction - The names obtained from `astScopeResultRef` are
|
||||
/// mapped to `ConsumedLookupResult` common representation. The names receive flags
|
||||
/// independently from `SwiftLexicalLookup` results.
|
||||
/// - `SwiftLexicalLookup` lookup - The names obtained from performing lookup with `SwiftLexicalLookup` are
|
||||
/// mapped to `ConsumedLookupResult` common representation. The names receive flags
|
||||
/// independently from `ASTScope` results.
|
||||
/// - Flagging pass - The method iterates through both result arrays together and, taking into
|
||||
/// account already applied flags and relationships between results, applies more flags to the results.
|
||||
///
|
||||
/// Fully flagged results, are then fed to the name matching pass. Using associated names, positions and
|
||||
/// flags, it asserts the equality of results and produces console output.
|
||||
///
|
||||
/// Returns `true`, if the matching was successful and `false` otherwise.
|
||||
/// Additionally, when matching fails, the function prints console output with the two results compared.
|
||||
@_cdecl("swift_ASTGen_validateUnqualifiedLookup")
|
||||
public func unqualifiedLookup(
|
||||
sourceFilePtr: UnsafeMutableRawPointer,
|
||||
astContext: BridgedASTContext,
|
||||
lookupAt: BridgedSourceLoc,
|
||||
finishInSequentialScope: Bool,
|
||||
astScopeResultRef: BridgedArrayRef
|
||||
) -> Bool {
|
||||
// Obtain source file and lookup position
|
||||
let sourceFile = sourceFilePtr.assumingMemoryBound(to: ExportedSourceFile.self)
|
||||
guard let sourceFileSyntax = sourceFile.pointee.syntax.as(SourceFileSyntax.self) else {
|
||||
print("Could not cast exported source file to SourceFileSyntax")
|
||||
return false
|
||||
}
|
||||
let sourceLocationConverter = sourceFile.pointee.sourceLocationConverter
|
||||
let configuredRegions = sourceFile.pointee.configuredRegions(astContext: astContext)
|
||||
|
||||
guard let lookupPosition = sourceFile.pointee.position(of: lookupAt),
|
||||
let lookupToken = sourceFileSyntax.token(at: lookupPosition)
|
||||
else {
|
||||
print("Could not determine lookup position")
|
||||
return false
|
||||
}
|
||||
|
||||
// Map AST result
|
||||
let astResults = astConsumedResults(
|
||||
sourceFile: sourceFile,
|
||||
astScopeResultRef: astScopeResultRef
|
||||
)
|
||||
|
||||
// Map SLL result
|
||||
let sllResults = sllConsumedResults(
|
||||
lookupToken: lookupToken,
|
||||
finishInSequentialScope: finishInSequentialScope,
|
||||
configuredRegions: configuredRegions
|
||||
)
|
||||
|
||||
// Add header to the output
|
||||
var consoleOutput =
|
||||
"-----> Lookup started at: \(sourceLocationConverter.location(for: lookupPosition).lineWithColumn) (\"\(lookupToken.text)\") finishInSequentialScope: \(finishInSequentialScope)\n"
|
||||
consoleOutput +=
|
||||
" |" + "ASTScope".addPaddingUpTo(characters: rowCharWidth) + "|"
|
||||
+ "SwiftLexicalLookup".addPaddingUpTo(characters: rowCharWidth) + "\n"
|
||||
|
||||
// Flagging pass
|
||||
flaggingPass(
|
||||
astResults: astResults,
|
||||
sllResults: sllResults,
|
||||
sourceFileSyntax: sourceFileSyntax,
|
||||
lookupPosition: lookupPosition
|
||||
)
|
||||
|
||||
// Matching pass
|
||||
let passed = matchingPass(
|
||||
astResults: astResults,
|
||||
sllResults: sllResults,
|
||||
sourceLocationConverter: sourceLocationConverter,
|
||||
consoleOutput: &consoleOutput
|
||||
)
|
||||
|
||||
// Output
|
||||
if !passed {
|
||||
print(consoleOutput)
|
||||
}
|
||||
|
||||
return passed
|
||||
}
|
||||
|
||||
/// Check if the name at `namePosition`, was improperly introduced
|
||||
/// by ASTScope (in the same declaration as lookup).
|
||||
private func isInvalidFirstNameInDeclarationIntroduction(
|
||||
sourceFile: SourceFileSyntax,
|
||||
lookupPosition: AbsolutePosition,
|
||||
namePosition: AbsolutePosition
|
||||
) -> Bool {
|
||||
func firstAncestorOfKind(
|
||||
of syntax: SyntaxProtocol?,
|
||||
kinds: [SyntaxProtocol.Type]
|
||||
) -> SyntaxProtocol? {
|
||||
guard let syntax else { return nil }
|
||||
|
||||
for kind in kinds {
|
||||
if syntax.is(kind) {
|
||||
return syntax
|
||||
}
|
||||
}
|
||||
|
||||
return firstAncestorOfKind(of: syntax.parent, kinds: kinds)
|
||||
}
|
||||
|
||||
let originToken = sourceFile.token(at: lookupPosition)
|
||||
let firstNameToken = sourceFile.token(at: namePosition)
|
||||
|
||||
let commonAncestors: [SyntaxProtocol.Type] = [
|
||||
SwitchCaseSyntax.self,
|
||||
ClosureExprSyntax.self,
|
||||
AccessorDeclSyntax.self,
|
||||
AccessorBlockSyntax.self,
|
||||
ForStmtSyntax.self,
|
||||
PatternBindingSyntax.self,
|
||||
]
|
||||
|
||||
let originAncestor = firstAncestorOfKind(
|
||||
of: originToken,
|
||||
kinds: commonAncestors
|
||||
)
|
||||
|
||||
let firstNameAncestor = firstAncestorOfKind(
|
||||
of: firstNameToken,
|
||||
kinds: commonAncestors
|
||||
)
|
||||
|
||||
guard let originAncestor,
|
||||
let firstNameAncestor,
|
||||
originAncestor.kind == firstNameAncestor.kind
|
||||
else { return false }
|
||||
|
||||
return originAncestor.kind == .patternBinding && originAncestor.id == firstNameAncestor.id
|
||||
}
|
||||
|
||||
/// Returns consumed `ASTScope` results from the
|
||||
/// given `astScopeResultRef`. Introduces appropriate flags.
|
||||
private func astConsumedResults(
|
||||
sourceFile: UnsafePointer<ExportedSourceFile>,
|
||||
astScopeResultRef: BridgedArrayRef
|
||||
) -> [ConsumedLookupResult] {
|
||||
let pointer = astScopeResultRef.data?.assumingMemoryBound(to: BridgedConsumedLookupResult.self)
|
||||
let count = astScopeResultRef.count
|
||||
|
||||
let astScopeResultArray = Array(UnsafeBufferPointer(start: pointer, count: count))
|
||||
|
||||
return astScopeResultArray.compactMap { bridgedResult in
|
||||
let identifierPointer = bridgedResult.name.raw?.assumingMemoryBound(to: CChar.self)
|
||||
|
||||
guard let astResultPosition = sourceFile.pointee.position(of: bridgedResult.nameLoc) else {
|
||||
print("One of the results, doesn't have a position")
|
||||
return nil
|
||||
}
|
||||
|
||||
let consumedResult = ConsumedLookupResult(
|
||||
rawName: identifierPointer == nil ? "" : String(cString: identifierPointer!),
|
||||
position: astResultPosition,
|
||||
flags: ConsumedLookupResultFlag(rawValue: bridgedResult.flag)
|
||||
)
|
||||
|
||||
// If the name doesn't have any flags and
|
||||
// the name is empty, it should be omitted.
|
||||
if consumedResult.flags.isEmpty && consumedResult.name.isEmpty {
|
||||
consumedResult.flags.insert(.shouldBeOmitted)
|
||||
}
|
||||
|
||||
return consumedResult
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs and returns `SwiftLexicalLookup` lookup and returns
|
||||
/// the results an array of `ConsumedLookupResult`. Introduces appropriate flags.
|
||||
private func sllConsumedResults(
|
||||
lookupToken: TokenSyntax,
|
||||
finishInSequentialScope: Bool,
|
||||
configuredRegions: ConfiguredRegions
|
||||
) -> [ConsumedLookupResult] {
|
||||
let resultsWithoutMacroReordering = lookupToken.lookup(
|
||||
nil,
|
||||
with: LookupConfig(finishInSequentialScope: finishInSequentialScope, configuredRegions: configuredRegions)
|
||||
)
|
||||
|
||||
// Early reordering of macro declaration parameters with its generic parameters.
|
||||
var results: [LookupResult] = []
|
||||
var previousMacroResult: LookupResult?
|
||||
|
||||
for result in resultsWithoutMacroReordering {
|
||||
if let unwrappedMacroResult = previousMacroResult,
|
||||
result.scope.is(GenericParameterClauseSyntax.self)
|
||||
{
|
||||
results += [result, unwrappedMacroResult]
|
||||
previousMacroResult = nil
|
||||
continue
|
||||
} else if let unwrappedMacroResult = previousMacroResult {
|
||||
results.append(unwrappedMacroResult)
|
||||
previousMacroResult = nil
|
||||
}
|
||||
|
||||
if result.scope.is(MacroDeclSyntax.self) {
|
||||
previousMacroResult = result
|
||||
} else {
|
||||
results.append(result)
|
||||
}
|
||||
}
|
||||
|
||||
if let previousMacroResult {
|
||||
results.append(previousMacroResult)
|
||||
}
|
||||
|
||||
return results.flatMap { result in
|
||||
switch result {
|
||||
case .lookInMembers(let lookInMembers):
|
||||
return [
|
||||
ConsumedLookupResult(
|
||||
rawName: "",
|
||||
position: lookInMembers.lookupMembersPosition,
|
||||
flags: .shouldLookInMembers
|
||||
)
|
||||
]
|
||||
case .lookInGenericParametersOfExtendedType(let extensionDecl):
|
||||
return [
|
||||
ConsumedLookupResult(
|
||||
rawName: "",
|
||||
position: extensionDecl.extensionKeyword.positionAfterSkippingLeadingTrivia,
|
||||
flags: .ignoreNextFromHere
|
||||
)
|
||||
]
|
||||
case .mightIntroduceDollarIdentifiers(let closure):
|
||||
return [
|
||||
ConsumedLookupResult(
|
||||
rawName: "",
|
||||
position: closure.positionAfterSkippingLeadingTrivia,
|
||||
flags: .ignoreNextFromHere
|
||||
)
|
||||
]
|
||||
default:
|
||||
if let parent = result.scope.parent, result.scope.is(GenericParameterClauseSyntax.self) {
|
||||
if let parentFunctionDecl = parent.as(FunctionDeclSyntax.self),
|
||||
parentFunctionDecl.attributes.range.contains(lookupToken.position)
|
||||
{
|
||||
// If lookup started from inside function attributes, don't reverse.
|
||||
return result.names.map { name in
|
||||
ConsumedLookupResult(rawName: name.identifier?.name ?? "", position: name.position, flags: [])
|
||||
}
|
||||
} else if parent.is(FunctionDeclSyntax.self) || parent.is(SubscriptDeclSyntax.self)
|
||||
|| result.scope.range.contains(lookupToken.position)
|
||||
{
|
||||
// If a result from function generic parameter clause or lookup started within it, reverse introduced names.
|
||||
return result.names.reversed().map { name in
|
||||
ConsumedLookupResult(
|
||||
rawName: name.identifier?.name ?? "",
|
||||
position: name.position,
|
||||
flags: .placementRearranged
|
||||
)
|
||||
}
|
||||
} else if let nominalTypeScope = Syntax(parent).asProtocol(SyntaxProtocol.self) as? NominalTypeDeclSyntax,
|
||||
nominalTypeScope.inheritanceClause?.range.contains(lookupToken.position) ?? false
|
||||
{
|
||||
// If lookup started from nominal type inheritance clause, reverse introduced names.
|
||||
return result.names.reversed().map { name in
|
||||
ConsumedLookupResult(
|
||||
rawName: name.identifier?.name ?? "",
|
||||
position: name.position,
|
||||
flags: .placementRearranged
|
||||
)
|
||||
}
|
||||
} else if let initializerDecl = parent.as(InitializerDeclSyntax.self),
|
||||
initializerDecl.range.contains(lookupToken.position)
|
||||
{
|
||||
// If lookup from inside the parent initializer decl, reverse introduced names.
|
||||
return result.names.reversed().map { name in
|
||||
ConsumedLookupResult(
|
||||
rawName: name.identifier?.name ?? "",
|
||||
position: name.position,
|
||||
flags: .placementRearranged
|
||||
)
|
||||
}
|
||||
} else if let parentTypeAlias = parent.as(TypeAliasDeclSyntax.self),
|
||||
parentTypeAlias.initializer.range.contains(lookupToken.position)
|
||||
{
|
||||
// If lookup started from inside type alias initializer, reverse introduced names.
|
||||
return result.names.reversed().map { name in
|
||||
ConsumedLookupResult(
|
||||
rawName: name.identifier?.name ?? "",
|
||||
position: name.position,
|
||||
flags: .placementRearranged
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// No flags or reorderings to perform.
|
||||
return result.names.map { name in
|
||||
ConsumedLookupResult(rawName: name.identifier?.name ?? "", position: name.position, flags: [])
|
||||
}
|
||||
} else {
|
||||
return result.names.map { name in
|
||||
// If a Self name not from protocol declaration, should be omitted if no match is found.
|
||||
let shouldBeOmitted = name.identifier?.name == "Self" ? !result.scope.is(ProtocolDeclSyntax.self) : false
|
||||
|
||||
return ConsumedLookupResult(
|
||||
rawName: name.identifier?.name ?? "",
|
||||
position: name.position,
|
||||
flags: shouldBeOmitted ? [.shouldBeOptionallyOmitted] : []
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds all appropriate flags to `astResults` and `sllResults`.
|
||||
private func flaggingPass(
|
||||
astResults: [ConsumedLookupResult],
|
||||
sllResults: [ConsumedLookupResult],
|
||||
sourceFileSyntax: SourceFileSyntax,
|
||||
lookupPosition: AbsolutePosition
|
||||
) {
|
||||
var i = 0
|
||||
var astOffset = 0
|
||||
var sllOffset = 0
|
||||
var encounteredASTNames = Set<ConsumedLookupResult>()
|
||||
var ignoreAt: AbsolutePosition?
|
||||
var wasLookupStopped = false
|
||||
|
||||
while i < max(astResults.count, sllResults.count) {
|
||||
var astResult: ConsumedLookupResult?
|
||||
|
||||
if astOffset + i < astResults.count {
|
||||
astResult = astResults[astOffset + i]
|
||||
|
||||
// Here only to not have to perform force unwraps later.
|
||||
guard let astResult else { break }
|
||||
|
||||
// Check if lookup was stopped earlier. If so, flag this result with lookupStopped.
|
||||
if wasLookupStopped {
|
||||
astResult.flags.insert(.lookupStopped)
|
||||
}
|
||||
|
||||
// Check if this is the end of ast lookup. If so, set wasLookupStopped to true.
|
||||
if astResult.isTheEndOfLookup {
|
||||
wasLookupStopped = true
|
||||
}
|
||||
|
||||
// Check if this is not the first encounter of this ast name. If so, should be omitted.
|
||||
if !astResult.shouldLookInMembers {
|
||||
let isFirstEncounter = !encounteredASTNames.contains(astResult)
|
||||
|
||||
if !isFirstEncounter {
|
||||
astResult.flags.insert(.shouldBeOmitted)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if names are being currently ignored from at this position. If so, should be omitted.
|
||||
if astResult.position == ignoreAt {
|
||||
astResult.flags.insert(.shouldBeOmitted)
|
||||
}
|
||||
|
||||
// Check if this is an invalid introduction within the same declaration. If so, should be omitted.
|
||||
if isInvalidFirstNameInDeclarationIntroduction(
|
||||
sourceFile: sourceFileSyntax,
|
||||
lookupPosition: lookupPosition,
|
||||
namePosition: astResult.position
|
||||
) && astResult.name != "self" {
|
||||
astResult.flags.insert(.shouldBeOmitted)
|
||||
}
|
||||
|
||||
// Check if this name should be omitted. If so, continue the loop and add one to offset.
|
||||
if astResult.shouldBeOmitted {
|
||||
astOffset += 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if i + sllOffset < sllResults.count {
|
||||
let sllResult = sllResults[i + sllOffset]
|
||||
|
||||
// Check if lookup was stopped earlier. If so, flag this result with lookupStopped.
|
||||
if wasLookupStopped && !(astResult?.isTheEndOfLookup ?? false) {
|
||||
sllResult.flags.insert(.lookupStopped)
|
||||
}
|
||||
|
||||
if sllResult.shouldBeOptionallyOmitted {
|
||||
if let astResult,
|
||||
astResult.name == sllResult.name
|
||||
{
|
||||
sllResult.flags.remove(.shouldBeOptionallyOmitted)
|
||||
} else {
|
||||
sllResult.flags.insert(.shouldBeOmitted)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if next results at this position should be ignored. If so, set ignoreAt and omit this name.
|
||||
if sllResult.ignoreNextFromHere && sllResult.position != ignoreAt {
|
||||
ignoreAt = sllResult.position
|
||||
sllResult.flags.insert(.shouldBeOmitted)
|
||||
}
|
||||
|
||||
// Check if this name should be omitted. If so, continue the loop and add one to offset.
|
||||
if sllResult.shouldBeOmitted {
|
||||
sllOffset += 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if let astResult {
|
||||
encounteredASTNames.insert(astResult)
|
||||
}
|
||||
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to match both results taking into account previously set
|
||||
/// flags. Returns whether the test validation succeeded.
|
||||
private func matchingPass(
|
||||
astResults: [ConsumedLookupResult],
|
||||
sllResults: [ConsumedLookupResult],
|
||||
sourceLocationConverter: SourceLocationConverter,
|
||||
consoleOutput: inout String
|
||||
) -> Bool {
|
||||
var i = 0
|
||||
var astOffset = 0
|
||||
var sllOffset = 0
|
||||
var passed = true
|
||||
|
||||
while i < max(astResults.count, sllResults.count) {
|
||||
var prefix = ""
|
||||
var astResultStr = ""
|
||||
var sllResultStr = ""
|
||||
|
||||
var astResult: ConsumedLookupResult?
|
||||
|
||||
if astOffset + i < astResults.count {
|
||||
astResult = astResults[astOffset + i]
|
||||
|
||||
guard let astResult else { break }
|
||||
|
||||
if astResult.shouldBeOmitted {
|
||||
consoleOutput +=
|
||||
"> ℹ️ | Omitted ASTScope name: \(astResult.consoleLogStr(sourceLocationConverter: sourceLocationConverter))\n"
|
||||
astOffset += 1
|
||||
continue
|
||||
}
|
||||
|
||||
astResultStr += astResult.consoleLogStr(sourceLocationConverter: sourceLocationConverter)
|
||||
} else {
|
||||
astResultStr = "-----"
|
||||
}
|
||||
|
||||
var sllResult: ConsumedLookupResult?
|
||||
|
||||
if i + sllOffset < sllResults.count {
|
||||
sllResult = sllResults[i + sllOffset]
|
||||
|
||||
guard let sllResult else { break }
|
||||
|
||||
if sllResult.shouldBeOmitted {
|
||||
consoleOutput +=
|
||||
"> ℹ️ | Omitted SwiftLexicalLookup name: \(sllResult.consoleLogStr(sourceLocationConverter: sourceLocationConverter))\n"
|
||||
sllOffset += 1
|
||||
continue
|
||||
}
|
||||
|
||||
sllResultStr = sllResult.consoleLogStr(sourceLocationConverter: sourceLocationConverter)
|
||||
} else {
|
||||
sllResultStr = "-----"
|
||||
}
|
||||
|
||||
i += 1
|
||||
|
||||
guard astResult != nil || sllResult != nil else { continue }
|
||||
|
||||
if let astResult, let sllResult {
|
||||
if (astResult.position == sllResult.position && astResult.name == sllResult.name) {
|
||||
prefix = "✅"
|
||||
} else if astResult.lookupStopped || sllResult.lookupStopped {
|
||||
prefix = "⏩"
|
||||
} else if astResult.position == sllResult.position || astResult.name == sllResult.name {
|
||||
prefix = "⚠️"
|
||||
passed = false
|
||||
} else {
|
||||
prefix = "❌"
|
||||
passed = false
|
||||
}
|
||||
} else if (astResult?.lookupStopped ?? false) || (sllResult?.lookupStopped ?? false) {
|
||||
prefix = "⏩"
|
||||
} else {
|
||||
prefix = "❌"
|
||||
passed = false
|
||||
}
|
||||
|
||||
consoleOutput +=
|
||||
"> \(prefix) |\(astResultStr.addPaddingUpTo(characters: rowCharWidth))|\(sllResultStr.addPaddingUpTo(characters: rowCharWidth))"
|
||||
|
||||
consoleOutput += "\n"
|
||||
}
|
||||
|
||||
return passed
|
||||
}
|
||||
|
||||
/// Simple representation of lookup result.
|
||||
/// Contains flags that indicate additional behaviour.
|
||||
private class ConsumedLookupResult: Hashable {
|
||||
var rawName: String
|
||||
var position: AbsolutePosition
|
||||
var flags: ConsumedLookupResultFlag
|
||||
|
||||
init(
|
||||
rawName: String,
|
||||
position: AbsolutePosition,
|
||||
flags: ConsumedLookupResultFlag
|
||||
) {
|
||||
self.rawName = rawName
|
||||
self.position = position
|
||||
self.flags = flags
|
||||
}
|
||||
|
||||
var name: String {
|
||||
shouldLookInMembers ? "" : rawName
|
||||
}
|
||||
|
||||
var isTheEndOfLookup: Bool {
|
||||
flags.contains(.endOfLookup)
|
||||
}
|
||||
|
||||
var shouldLookInMembers: Bool {
|
||||
flags.contains(.shouldLookInMembers)
|
||||
}
|
||||
|
||||
var resultPlacementRearranged: Bool {
|
||||
flags.contains(.placementRearranged)
|
||||
}
|
||||
|
||||
var shouldBeOmitted: Bool {
|
||||
flags.contains(.shouldBeOmitted)
|
||||
}
|
||||
|
||||
var shouldBeOptionallyOmitted: Bool {
|
||||
flags.contains(.shouldBeOptionallyOmitted)
|
||||
}
|
||||
|
||||
var ignoreNextFromHere: Bool {
|
||||
flags.contains(.ignoreNextFromHere)
|
||||
}
|
||||
|
||||
var lookupStopped: Bool {
|
||||
flags.contains(.lookupStopped)
|
||||
}
|
||||
|
||||
func consoleLogStr(sourceLocationConverter: SourceLocationConverter) -> String {
|
||||
(isTheEndOfLookup ? "End here: " : "") + (resultPlacementRearranged ? "↕️ " : "")
|
||||
+ (ignoreNextFromHere ? "Ignore next from: " : "") + (shouldLookInMembers ? "Look memb: " : "\(name) ")
|
||||
+ sourceLocationConverter.location(for: position).lineWithColumn
|
||||
}
|
||||
|
||||
static func == (lhs: ConsumedLookupResult, rhs: ConsumedLookupResult) -> Bool {
|
||||
return lhs.rawName == rhs.rawName && lhs.position == rhs.position && lhs.flags == rhs.flags
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(rawName)
|
||||
hasher.combine(position)
|
||||
hasher.combine(flags)
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine behaviour during matching pass.
|
||||
struct ConsumedLookupResultFlag: OptionSet, Hashable {
|
||||
let rawValue: Int
|
||||
|
||||
/// Indicates lookup ended at this name. Continue with
|
||||
/// other names without matching and mark them as skipped.
|
||||
static let endOfLookup = ConsumedLookupResultFlag(rawValue: 1 << 0)
|
||||
/// This name prompts client to look in members of associated scope.
|
||||
static let shouldLookInMembers = ConsumedLookupResultFlag(rawValue: 1 << 1)
|
||||
/// The original position in result of this name
|
||||
/// might be different than displayed.
|
||||
static let placementRearranged = ConsumedLookupResultFlag(rawValue: 1 << 2)
|
||||
/// The name should be ignored.
|
||||
static let shouldBeOmitted = ConsumedLookupResultFlag(rawValue: 1 << 3)
|
||||
/// If no match is found, this name should be ignored.
|
||||
static let shouldBeOptionallyOmitted = ConsumedLookupResultFlag(rawValue: 1 << 4)
|
||||
/// Means that one of the previous
|
||||
/// names indicated the end of lookup.
|
||||
static let lookupStopped = ConsumedLookupResultFlag(rawValue: 1 << 5)
|
||||
/// Next names from associated position should be omitted.
|
||||
/// Filtering is applied until then next name of this kind is found and
|
||||
/// position used for ignoring is updated.
|
||||
static let ignoreNextFromHere = ConsumedLookupResultFlag(rawValue: 1 << 6)
|
||||
}
|
||||
|
||||
extension SourceLocation {
|
||||
fileprivate var lineWithColumn: String {
|
||||
return "\(line):\(column)"
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
fileprivate func addPaddingUpTo(characters charCount: Int) -> String {
|
||||
guard self.count < charCount else { return self }
|
||||
|
||||
let lengthDifference = charCount - self.count
|
||||
|
||||
var leftPad = ""
|
||||
var rightPad = ""
|
||||
|
||||
for _ in 0..<(lengthDifference / 2) {
|
||||
leftPad += " "
|
||||
}
|
||||
|
||||
for _ in 0..<((lengthDifference + 1) / 2) {
|
||||
rightPad += " "
|
||||
}
|
||||
|
||||
return leftPad + self + rightPad
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ includeSwiftSyntax()
|
||||
set(compiler_swiftsyntax_libs
|
||||
_CompilerSwiftSyntax
|
||||
_CompilerSwiftIfConfig
|
||||
_CompilerSwiftLexicalLookup
|
||||
_CompilerSwiftOperators
|
||||
_CompilerSwiftSyntaxBuilder
|
||||
_CompilerSwiftParser
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
// REQUIRES: swift_feature_UnqualifiedLookupValidation
|
||||
//
|
||||
// RUN: %target-typecheck-verify-swift -enable-experimental-feature UnqualifiedLookupValidation
|
||||
|
||||
protocol P1 {
|
||||
associatedtype A
|
||||
func f() -> A
|
||||
}
|
||||
|
||||
protocol P2 {
|
||||
associatedtype A: P2
|
||||
associatedtype B: P2 where Self.A.A == Self.B.A
|
||||
}
|
||||
|
||||
protocol P3 {
|
||||
associatedtype A: P3
|
||||
}
|
||||
|
||||
struct Basic: P1 {
|
||||
typealias A = Int
|
||||
func f() -> Int { fatalError() }
|
||||
}
|
||||
|
||||
struct Recur: P2 {
|
||||
typealias A = Recur
|
||||
typealias B = Recur
|
||||
}
|
||||
|
||||
struct NonRecur: P2 {
|
||||
typealias A = Recur
|
||||
typealias B = Recur
|
||||
}
|
||||
|
||||
struct Generic<T> {}
|
||||
|
||||
class Super<T, U> {}
|
||||
|
||||
extension Super: P2 where T: P2, U: P2 {
|
||||
typealias A = T
|
||||
typealias B = T
|
||||
|
||||
func foo() -> Int { fatalError() }
|
||||
}
|
||||
|
||||
class Sub: Super<NonRecur, Recur> {}
|
||||
|
||||
struct RecurGeneric<T: P3>: P3 {
|
||||
typealias A = RecurGeneric<T>
|
||||
}
|
||||
|
||||
struct Specialize: P3 {
|
||||
typealias A = RecurGeneric<Specialize>
|
||||
}
|
||||
|
||||
protocol P48a { associatedtype A = Int }
|
||||
protocol P48b { associatedtype B }
|
||||
protocol P48c: P48a, P48b where A == B {}
|
||||
|
||||
public extension Array where Element == Int {
|
||||
mutating func foo(
|
||||
at index: Int,
|
||||
byCalling closure: (inout Element) -> Void
|
||||
) where Element: Differentiable { // expected-error{{cannot find type 'Differentiable' in scope}}
|
||||
closure(&self[index])
|
||||
}
|
||||
}
|
||||
|
||||
public extension Array {
|
||||
mutating func bar(
|
||||
at index: Int,
|
||||
byCalling closure:(inout Element) -> Void
|
||||
) where Element: Differentiable { // expected-error{{cannot find type 'Differentiable' in scope}}
|
||||
closure(&self[index])
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user