Skip to content

Commit 5e0f4b7

Browse files
committed
[AST] Introduce AvailabilityContext to wrap VersionRange.
This is the beginning of the extension of the availability model introduced in Swift 2.0 to support two interesting things: inlineable code and binary frameworks not tied to an OS. The former is critical to having a stable standard library that isn't shipped with a client app. (For more information on both of these, see docs/LibraryEvolution.rst.) The existing availability model enforces that API is not used unless the developer has already guaranteed its existence. We want to reuse this logic for these new purposes. Additionally, certain queries about the AST are dependent on this type of information as well, e.g. "can I assume this enum will not grow any additional cases?" If the enum comes from the module being compiled, the answer is usually "yes", but not if the code asking the question may be inlined into another binary! (This latter purpose is currently served by ResilienceExpansion down at the SIL level; my goal is to replace ResilienceExpansion with AvailabilityContext. It's a bit heavier but would also allow additional optimization in the future.) This commit does not change any logic; it only wraps existing uses of VersionRange in AvailabilityContext if they're not strictly referring to the OS version.
1 parent b9ef11e commit 5e0f4b7

File tree

10 files changed

+253
-147
lines changed

10 files changed

+253
-147
lines changed

include/swift/AST/Availability.h

Lines changed: 97 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,97 @@ class UnavailabilityReason {
225225
}
226226
};
227227

228+
/// Represents everything that a particular chunk of code may assume about its
229+
/// runtime environment.
230+
///
231+
/// The AvailabilityContext structure forms a [lattice][], which allows it to
232+
/// have meaningful union and intersection operations ("join" and "meet"),
233+
/// which use conservative approximations to prevent availability violations.
234+
/// See #unionWith, #intersectWith, and #constrainWith.
235+
///
236+
/// [lattice]: http://mathworld.wolfram.com/Lattice.html
237+
class AvailabilityContext {
238+
VersionRange OSVersion;
239+
public:
240+
/// Creates a context that requires certain versions of the target OS.
241+
explicit AvailabilityContext(VersionRange OSVersion) : OSVersion(OSVersion) {}
242+
243+
/// Creates a context that imposes no constraints.
244+
///
245+
/// \see isAlwaysAvailable
246+
static AvailabilityContext alwaysAvailable() {
247+
return AvailabilityContext(VersionRange::all());
248+
}
249+
250+
/// Creates a context that can never actually occur.
251+
///
252+
/// \see isKnownUnreachable
253+
static AvailabilityContext neverAvailable() {
254+
return AvailabilityContext(VersionRange::empty());
255+
}
256+
257+
/// Returns the range of possible OS versions required by this context.
258+
VersionRange getOSVersion() const { return OSVersion; }
259+
260+
/// Returns true if \p other makes stronger guarantees than this context.
261+
///
262+
/// That is, `a.isContainedIn(b)` implies `a.union(b) == b`.
263+
bool isContainedIn(AvailabilityContext other) const {
264+
return OSVersion.isContainedIn(other.OSVersion);
265+
}
266+
267+
/// Returns true if this context has constraints that make it impossible to
268+
/// actually occur.
269+
///
270+
/// For example, the else branch of a `#available` check for iOS 8.0 when the
271+
/// containing function already requires iOS 9.
272+
bool isKnownUnreachable() const {
273+
return OSVersion.isEmpty();
274+
}
275+
276+
/// Returns true if there are no constraints on this context; that is,
277+
/// nothing can be assumed.
278+
bool isAlwaysAvailable() const {
279+
return OSVersion.isAll();
280+
}
281+
282+
/// Produces an under-approximation of the intersection of the two
283+
/// availability contexts.
284+
///
285+
/// That is, if the intersection can't be represented exactly, prefer
286+
/// treating some valid deployment environments as unavailable. This is the
287+
/// "meet" operation of the lattice.
288+
///
289+
/// As an example, this is used when figuring out the required availability
290+
/// for a type that references multiple nominal decls.
291+
void intersectWith(AvailabilityContext other) {
292+
OSVersion.intersectWith(other.getOSVersion());
293+
}
294+
295+
/// Produces an over-approximation of the intersection of the two
296+
/// availability contexts.
297+
///
298+
/// That is, if the intersection can't be represented exactly, prefer
299+
/// treating some invalid deployment environments as available.
300+
///
301+
/// As an example, this is used for the true branch of `#available`.
302+
void constrainWith(AvailabilityContext other) {
303+
OSVersion.constrainWith(other.getOSVersion());
304+
}
305+
306+
/// Produces an over-approximation of the union of two availability contexts.
307+
///
308+
/// That is, if the union can't be represented exactly, prefer treating
309+
/// some invalid deployment environments as available. This is the "join"
310+
/// operation of the lattice.
311+
///
312+
/// As an example, this is used for the else branch of a conditional with
313+
/// multiple `#available` checks.
314+
void unionWith(AvailabilityContext other) {
315+
OSVersion.unionWith(other.getOSVersion());
316+
}
317+
};
318+
228319

229320
class AvailabilityInference {
230321
public:
@@ -236,17 +327,17 @@ class AvailabilityInference {
236327
ArrayRef<const Decl *> InferredFromDecls,
237328
ASTContext &Context);
238329

239-
static VersionRange inferForType(Type t);
330+
static AvailabilityContext inferForType(Type t);
240331

241-
/// \brief Returns the version range on which a declaration is available
332+
/// \brief Returns the context where a declaration is available
242333
/// We assume a declaration without an annotation is always available.
243-
static VersionRange availableRange(const Decl *D, ASTContext &C);
334+
static AvailabilityContext availableRange(const Decl *D, ASTContext &C);
244335

245-
/// \brief Returns the version range for which the declaration
336+
/// \brief Returns the context for which the declaration
246337
/// is annotated as available, or None if the declaration
247338
/// has no availability annotation.
248-
static Optional<VersionRange> annotatedAvailableRange(const Decl *D,
249-
ASTContext &C);
339+
static Optional<AvailabilityContext> annotatedAvailableRange(const Decl *D,
340+
ASTContext &C);
250341

251342
};
252343

include/swift/AST/TypeRefinementContext.h

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -153,35 +153,35 @@ class TypeRefinementContext {
153153

154154
SourceRange SrcRange;
155155

156-
VersionRange PotentialVersions;
156+
AvailabilityContext AvailabilityInfo;
157157

158158
std::vector<TypeRefinementContext *> Children;
159159

160160
TypeRefinementContext(ASTContext &Ctx, IntroNode Node,
161161
TypeRefinementContext *Parent, SourceRange SrcRange,
162-
const VersionRange &Versions);
162+
const AvailabilityContext &Info);
163163

164164
public:
165165

166166
/// Create the root refinement context for the given SourceFile.
167167
static TypeRefinementContext *createRoot(SourceFile *SF,
168-
const VersionRange &Versions);
168+
const AvailabilityContext &Info);
169169

170170
/// Create a refinement context for the given declaration.
171171
static TypeRefinementContext *createForDecl(ASTContext &Ctx, Decl *D,
172172
TypeRefinementContext *Parent,
173-
const VersionRange &Versions,
173+
const AvailabilityContext &Info,
174174
SourceRange SrcRange);
175175

176176
/// Create a refinement context for the Then branch of the given IfStmt.
177177
static TypeRefinementContext *
178178
createForIfStmtThen(ASTContext &Ctx, IfStmt *S, TypeRefinementContext *Parent,
179-
const VersionRange &Versions);
179+
const AvailabilityContext &Info);
180180

181181
/// Create a refinement context for the Else branch of the given IfStmt.
182182
static TypeRefinementContext *
183183
createForIfStmtElse(ASTContext &Ctx, IfStmt *S, TypeRefinementContext *Parent,
184-
const VersionRange &Versions);
184+
const AvailabilityContext &Info);
185185

186186
/// Create a refinement context for the true-branch control flow to
187187
/// further StmtConditionElements following a #available() query in
@@ -190,26 +190,26 @@ class TypeRefinementContext {
190190
createForConditionFollowingQuery(ASTContext &Ctx, PoundAvailableInfo *PAI,
191191
const StmtConditionElement &LastElement,
192192
TypeRefinementContext *Parent,
193-
const VersionRange &Versions);
193+
const AvailabilityContext &Info);
194194

195195
/// Create a refinement context for the fallthrough of a GuardStmt.
196196
static TypeRefinementContext *
197197
createForGuardStmtFallthrough(ASTContext &Ctx, GuardStmt *RS,
198198
BraceStmt *ContainingBraceStmt,
199199
TypeRefinementContext *Parent,
200-
const VersionRange &Versions);
200+
const AvailabilityContext &Info);
201201

202202
/// Create a refinement context for the else branch of a GuardStmt.
203203
static TypeRefinementContext *
204204
createForGuardStmtElse(ASTContext &Ctx, GuardStmt *RS,
205205
TypeRefinementContext *Parent,
206-
const VersionRange &Versions);
206+
const AvailabilityContext &Info);
207207

208208
/// Create a refinement context for the body of a WhileStmt.
209209
static TypeRefinementContext *
210210
createForWhileStmtBody(ASTContext &Ctx, WhileStmt *WS,
211211
TypeRefinementContext *Parent,
212-
const VersionRange &Versions);
212+
const AvailabilityContext &Info);
213213

214214
/// Returns the reason this context was introduced.
215215
Reason getReason() const;
@@ -229,9 +229,11 @@ class TypeRefinementContext {
229229
/// Returns the source range on which this context refines types.
230230
SourceRange getSourceRange() const { return SrcRange; }
231231

232-
/// Returns a version range representing the range of operating system
233-
/// versions on which the code contained in this context may run.
234-
const VersionRange &getPotentialVersions() const { return PotentialVersions; }
232+
/// Returns the information on what can be assumed present at run time when
233+
/// running code contained in this context.
234+
const AvailabilityContext &getAvailabilityInfo() const {
235+
return AvailabilityInfo;
236+
}
235237

236238
/// Adds a child refinement context.
237239
void addChild(TypeRefinementContext *Child) {

lib/AST/Availability.cpp

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,9 @@ void AvailabilityInference::applyInferredAvailableAttrs(
121121
}
122122
}
123123

124-
Optional<VersionRange>
124+
Optional<AvailabilityContext>
125125
AvailabilityInference::annotatedAvailableRange(const Decl *D, ASTContext &Ctx) {
126-
Optional<VersionRange> AnnotatedRange;
126+
Optional<AvailabilityContext> AnnotatedRange;
127127

128128
for (auto Attr : D->getAttrs()) {
129129
auto *AvailAttr = dyn_cast<AvailableAttr>(Attr);
@@ -132,8 +132,8 @@ AvailabilityInference::annotatedAvailableRange(const Decl *D, ASTContext &Ctx) {
132132
continue;
133133
}
134134

135-
VersionRange AttrRange =
136-
VersionRange::allGTE(AvailAttr->Introduced.getValue());
135+
AvailabilityContext AttrRange{
136+
VersionRange::allGTE(AvailAttr->Introduced.getValue())};
137137

138138
// If we have multiple introduction versions, we will conservatively
139139
// assume the worst case scenario. We may want to be more precise here
@@ -149,9 +149,10 @@ AvailabilityInference::annotatedAvailableRange(const Decl *D, ASTContext &Ctx) {
149149
return AnnotatedRange;
150150
}
151151

152-
VersionRange AvailabilityInference::availableRange(const Decl *D,
153-
ASTContext &Ctx) {
154-
Optional<VersionRange> AnnotatedRange = annotatedAvailableRange(D, Ctx);
152+
AvailabilityContext AvailabilityInference::availableRange(const Decl *D,
153+
ASTContext &Ctx) {
154+
Optional<AvailabilityContext> AnnotatedRange =
155+
annotatedAvailableRange(D, Ctx);
155156
if (AnnotatedRange.hasValue()) {
156157
return AnnotatedRange.getValue();
157158
}
@@ -173,21 +174,21 @@ VersionRange AvailabilityInference::availableRange(const Decl *D,
173174
}
174175

175176
// Treat unannotated declarations as always available.
176-
return VersionRange::all();
177+
return AvailabilityContext::alwaysAvailable();
177178
}
178179

179180
namespace {
180181
/// Infers the availability required to access a type.
181182
class AvailabilityInferenceTypeWalker : public TypeWalker {
182183
public:
183184
ASTContext &AC;
184-
VersionRange AvailableRange = VersionRange::all();
185+
AvailabilityContext AvailabilityInfo = AvailabilityContext::alwaysAvailable();
185186

186187
AvailabilityInferenceTypeWalker(ASTContext &AC) : AC(AC) {}
187188

188189
virtual Action walkToTypePre(Type ty) {
189190
if (auto *nominalDecl = ty.getCanonicalTypeOrNull().getAnyNominal()) {
190-
AvailableRange.intersectWith(
191+
AvailabilityInfo.intersectWith(
191192
AvailabilityInference::availableRange(nominalDecl, AC));
192193
}
193194

@@ -197,8 +198,8 @@ class AvailabilityInferenceTypeWalker : public TypeWalker {
197198
};
198199

199200

200-
VersionRange AvailabilityInference::inferForType(Type t) {
201+
AvailabilityContext AvailabilityInference::inferForType(Type t) {
201202
AvailabilityInferenceTypeWalker walker(t->getASTContext());
202203
t.walk(walker);
203-
return walker.AvailableRange;
204+
return walker.AvailabilityInfo;
204205
}

0 commit comments

Comments
 (0)