Skip to content

Commit

Permalink
Merge pull request #33439 from xymus/expand-avail
Browse files Browse the repository at this point in the history
[Sema] Define availability specification macros with a frontend flag
  • Loading branch information
xymus committed Oct 8, 2020
2 parents a7a47e5 + 5ed2616 commit fa6f1b6
Show file tree
Hide file tree
Showing 13 changed files with 432 additions and 16 deletions.
7 changes: 7 additions & 0 deletions include/swift/AST/AvailabilitySpec.h
Expand Up @@ -85,6 +85,9 @@ class PlatformVersionConstraintAvailabilitySpec : public AvailabilitySpec {

SourceRange VersionSrcRange;

// Location of the macro expanded to create this spec.
SourceLoc MacroLoc;

public:
PlatformVersionConstraintAvailabilitySpec(PlatformKind Platform,
SourceLoc PlatformLoc,
Expand Down Expand Up @@ -117,6 +120,10 @@ class PlatformVersionConstraintAvailabilitySpec : public AvailabilitySpec {

SourceRange getSourceRange() const;

// Location of the macro expanded to create this spec.
SourceLoc getMacroLoc() const { return MacroLoc; }
void setMacroLoc(SourceLoc loc) { MacroLoc = loc; }

void print(raw_ostream &OS, unsigned Indent) const;

static bool classof(const AvailabilitySpec *Spec) {
Expand Down
16 changes: 16 additions & 0 deletions include/swift/AST/DiagnosticsParse.def
Expand Up @@ -1431,6 +1431,22 @@ WARNING(attr_availability_nonspecific_platform_unexpected_version,none,
"unexpected version number in '%0' attribute for non-specific platform "
"'*'", (StringRef))

// availability macro
ERROR(attr_availability_wildcard_in_macro, none,
"future platforms identified by '*' cannot be used in "
"an availability macro definition", ())
ERROR(attr_availability_missing_macro_name,none,
"expected an identifier to begin an availability macro definition", ())
ERROR(attr_availability_expected_colon_macro,none,
"expected ':' after '%0' in availability macro definition",
(StringRef))
ERROR(attr_availability_unknown_version,none,
"reference to undefined version '%0' for availability macro '%1'",
(StringRef, StringRef))
ERROR(attr_availability_duplicate,none,
"duplicate definition of availability macro '%0' for version '%1'",
(StringRef, StringRef))

// originallyDefinedIn
ERROR(originally_defined_in_missing_rparen,none,
"expected ')' in @_originallyDefinedIn argument list", ())
Expand Down
4 changes: 4 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Expand Up @@ -4932,6 +4932,10 @@ WARNING(public_decl_needs_availability, none,
"public declarations should have an availability attribute when building "
"with -require-explicit-availability", ())

ERROR(availability_macro_in_inlinable, none,
"availability macro cannot be used in inlinable %0",
(DescriptiveDeclKind))

// This doesn't display as an availability diagnostic, but it's
// implemented there and fires when these subscripts are marked
// unavailable, so it seems appropriate to put it here.
Expand Down
3 changes: 3 additions & 0 deletions include/swift/Basic/LangOptions.h
Expand Up @@ -111,6 +111,9 @@ namespace swift {
/// when using RequireExplicitAvailability.
std::string RequireExplicitAvailabilityTarget;

// Availability macros definitions to be expanded at parsing.
SmallVector<StringRef, 4> AvailabilityMacros;

/// If false, '#file' evaluates to the full path rather than a
/// human-readable string.
bool EnableConcisePoundFile = false;
Expand Down
5 changes: 5 additions & 0 deletions include/swift/Option/Options.td
Expand Up @@ -401,6 +401,11 @@ def require_explicit_availability_target : Separate<["-"], "require-explicit-ava
HelpText<"Suggest fix-its adding @available(<target>, *) to public declarations without availability">,
MetaVarName<"<target>">;

def define_availability : Separate<["-"], "define-availability">,
Flags<[FrontendOption, NoInteractiveOption]>,
HelpText<"Define an availability macro in the format 'macroName : iOS 13.0, macOS 10.15'">,
MetaVarName<"<macro>">;

def module_name : Separate<["-"], "module-name">,
Flags<[FrontendOption, ModuleInterfaceOption]>,
HelpText<"Name of the module to build">;
Expand Down
49 changes: 47 additions & 2 deletions include/swift/Parse/Parser.h
Expand Up @@ -397,6 +397,22 @@ class Parser {
/// Current syntax parsing context where call backs should be directed to.
SyntaxParsingContext *SyntaxContext;

/// Maps of macro name and version to availability specifications.
typedef llvm::DenseMap<llvm::VersionTuple,
SmallVector<AvailabilitySpec *, 4>>
AvailabilityMacroVersionMap;
typedef llvm::DenseMap<StringRef, AvailabilityMacroVersionMap>
AvailabilityMacroMap;

/// Cache of the availability macros parsed from the command line arguments.
/// Organized as two nested \c DenseMap keyed first on the macro name then
/// the macro version. This structure allows to peek at macro names before
/// parsing a version tuple.
AvailabilityMacroMap AvailabilityMacros;

/// Has \c AvailabilityMacros been computed?
bool AvailabilityMacrosComputed = false;

public:
Parser(unsigned BufferID, SourceFile &SF, DiagnosticEngine* LexerDiags,
SILParserStateBase *SIL, PersistentParserState *PersistentState,
Expand Down Expand Up @@ -1682,9 +1698,38 @@ class Parser {
//===--------------------------------------------------------------------===//
// Availability Specification Parsing

/// Parse a comma-separated list of availability specifications.
/// Parse a comma-separated list of availability specifications. Try to
/// expand availability macros when /p ParsingMacroDefinition is false.
ParserStatus
parseAvailabilitySpecList(SmallVectorImpl<AvailabilitySpec *> &Specs,
bool ParsingMacroDefinition = false);

/// Does the current matches an argument macro name? Parsing compiler
/// arguments as required without consuming tokens from the source file
/// parser.
bool peekAvailabilityMacroName();

/// Try to parse a reference to an availability macro and append its result
/// to \p Specs. If the current token doesn't match a macro name, return
/// a success without appending anything to \c Specs.
ParserStatus
parseAvailabilityMacro(SmallVectorImpl<AvailabilitySpec *> &Specs);

/// Parse the availability macros definitions passed as arguments.
void parseAllAvailabilityMacroArguments();

/// Result of parsing an availability macro definition.
struct AvailabilityMacroDefinition {
StringRef Name;
llvm::VersionTuple Version;
SmallVector<AvailabilitySpec *, 4> Specs;
};

/// Parse an availability macro definition from a command line argument.
/// This function should be called on a Parser set up on the command line
/// argument code.
ParserStatus
parseAvailabilitySpecList(SmallVectorImpl<AvailabilitySpec *> &Specs);
parseAvailabilityMacroDefinition(AvailabilityMacroDefinition &Result);

ParserResult<AvailabilitySpec> parseAvailabilitySpec();
ParserResult<PlatformVersionConstraintAvailabilitySpec>
Expand Down
4 changes: 4 additions & 0 deletions lib/Frontend/CompilerInvocation.cpp
Expand Up @@ -498,6 +498,10 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
}
}

for (const Arg *A : Args.filtered(OPT_define_availability)) {
Opts.AvailabilityMacros.push_back(A->getValue());
}

if (const Arg *A = Args.getLastArg(OPT_value_recursion_threshold)) {
unsigned threshold;
if (StringRef(A->getValue()).getAsInteger(10, threshold)) {
Expand Down
122 changes: 121 additions & 1 deletion lib/Parse/ParseDecl.cpp
Expand Up @@ -1372,6 +1372,125 @@ void Parser::parseObjCSelector(SmallVector<Identifier, 4> &Names,
}
}

bool Parser::peekAvailabilityMacroName() {
parseAllAvailabilityMacroArguments();
AvailabilityMacroMap Map = AvailabilityMacros;

StringRef MacroName = Tok.getText();
return Map.find(MacroName) != Map.end();
}

ParserStatus
Parser::parseAvailabilityMacro(SmallVectorImpl<AvailabilitySpec *> &Specs) {
// Get the macros from the compiler arguments.
parseAllAvailabilityMacroArguments();
AvailabilityMacroMap Map = AvailabilityMacros;

StringRef MacroName = Tok.getText();
auto NameMatch = Map.find(MacroName);
if (NameMatch == Map.end())
return makeParserSuccess(); // No match, it could be a standard platform.

consumeToken();

llvm::VersionTuple Version;
SourceRange VersionRange;
if (Tok.isAny(tok::integer_literal, tok::floating_literal)) {
if (parseVersionTuple(Version, VersionRange,
diag::avail_query_expected_version_number))
return makeParserError();
}

auto VersionMatch = NameMatch->getSecond().find(Version);
if (VersionMatch == NameMatch->getSecond().end()) {
diagnose(PreviousLoc, diag::attr_availability_unknown_version,
Version.getAsString(), MacroName);
return makeParserError(); // Failed to match the version, that's an error.
}

// Make a copy of the specs to add the macro source location
// for the diagnostic about the use of macros in inlinable code.
SourceLoc MacroLoc = Tok.getLoc();
for (auto *Spec : VersionMatch->getSecond())
if (auto *PlatformVersionSpec =
dyn_cast<PlatformVersionConstraintAvailabilitySpec>(Spec)) {
auto SpecCopy =
new (Context) PlatformVersionConstraintAvailabilitySpec(
*PlatformVersionSpec);
SpecCopy->setMacroLoc(MacroLoc);
Specs.push_back(SpecCopy);
}

return makeParserSuccess();
}

void Parser::parseAllAvailabilityMacroArguments() {

if (AvailabilityMacrosComputed) return;

AvailabilityMacroMap Map;

SourceManager &SM = Context.SourceMgr;
const LangOptions &LangOpts = Context.LangOpts;

for (StringRef macro: LangOpts.AvailabilityMacros) {

// Create temporary parser.
int bufferID = SM.addMemBufferCopy(macro,
"-define-availability argument");
swift::ParserUnit PU(SM,
SourceFileKind::Main, bufferID,
LangOpts,
TypeCheckerOptions(), "unknown");

ForwardingDiagnosticConsumer PDC(Context.Diags);
PU.getDiagnosticEngine().addConsumer(PDC);

// Parse the argument.
AvailabilityMacroDefinition ParsedMacro;
ParserStatus Status =
PU.getParser().parseAvailabilityMacroDefinition(ParsedMacro);
if (Status.isError())
continue;

// Copy the Specs to the requesting ASTContext from the temporary context
// that parsed the argument.
auto SpecsCopy = SmallVector<AvailabilitySpec*, 4>();
for (auto *Spec : ParsedMacro.Specs)
if (auto *PlatformVersionSpec =
dyn_cast<PlatformVersionConstraintAvailabilitySpec>(Spec)) {
auto SpecCopy =
new (Context) PlatformVersionConstraintAvailabilitySpec(
*PlatformVersionSpec);
SpecsCopy.push_back(SpecCopy);
}

ParsedMacro.Specs = SpecsCopy;

// Find the macro info by name.
AvailabilityMacroVersionMap MacroDefinition;
auto NameMatch = Map.find(ParsedMacro.Name);
if (NameMatch != Map.end()) {
MacroDefinition = NameMatch->getSecond();
}

// Set the macro info by version.
auto PreviousEntry =
MacroDefinition.insert({ParsedMacro.Version, ParsedMacro.Specs});
if (!PreviousEntry.second) {
diagnose(PU.getParser().PreviousLoc, diag::attr_availability_duplicate,
ParsedMacro.Name, ParsedMacro.Version.getAsString());
}

// Save back the macro spec.
Map.erase(ParsedMacro.Name);
Map.insert({ParsedMacro.Name, MacroDefinition});
}

AvailabilityMacros = Map;
AvailabilityMacrosComputed = true;
}

bool Parser::parseNewDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc,
DeclAttrKind DK) {
// Ok, it is a valid attribute, eat it, and then process it.
Expand Down Expand Up @@ -1975,7 +2094,8 @@ bool Parser::parseNewDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc,
StringRef Platform = Tok.getText();

if (Platform != "*" &&
peekToken().isAny(tok::integer_literal, tok::floating_literal)) {
(peekToken().isAny(tok::integer_literal, tok::floating_literal) ||
peekAvailabilityMacroName())) {
// We have the short form of available: @available(iOS 8.0.1, *)
SmallVector<AvailabilitySpec *, 5> Specs;
ParserStatus Status = parseAvailabilitySpecList(Specs);
Expand Down

0 comments on commit fa6f1b6

Please sign in to comment.