Skip to content

Commit f4c679c

Browse files
authored
Merge pull request #2545 from DmT021/wp/diagnostic_groups
Precise Control Flags over Compiler Warnings
2 parents a6fa982 + fefac77 commit f4c679c

File tree

1 file changed

+234
-0
lines changed

1 file changed

+234
-0
lines changed
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
2+
# Precise Control Flags over Compiler Warnings
3+
4+
* Proposal: [SE-0443](NNNN-warning-control-flags.md)
5+
* Authors: [Doug Gregor](https://github.com/douggregor), [Dmitrii Galimzianov](https://github.com/DmT021)
6+
* Review Manager: [John McCall](https://github.com/rjmccall)
7+
* Status: **Active review (August 22nd...September 2nd, 2024)**
8+
* Implementation: [apple/swift#74466](https://github.com/swiftlang/swift/pull/74466)
9+
* Review: ([pitch](https://forums.swift.org/t/warnings-as-errors-exceptions/72925))
10+
11+
## Introduction
12+
13+
This proposal introduces new compiler options that allow fine-grained control over how the compiler emits certain warnings: as warnings, as errors, or not at all.
14+
15+
## Motivation
16+
17+
The current compiler options for controlling how warnings are emitted are very inflexible. Currently, the following options exist:
18+
- `-warnings-as-errors` - upgrades all warnings to errors
19+
- `-no-warnings-as-errors` - cancels the upgrade of warnings to errors
20+
- `-suppress-warnings` - disables the emission of all warnings
21+
22+
This lack of flexibility leads to situations where users who want to use `-warnings-as-errors` find themselves unable to do so, or unable to upgrade to a new version of the compiler or SDK until all newly diagnosed warnings are resolved. The most striking example of this is deprecation warnings for certain APIs, though they are not limited to them.
23+
24+
## Proposed solution
25+
26+
This proposal suggests adding new options that will allow the behavior of warnings to be controlled based on their diagnostic group.
27+
- `-Werror <group>` - upgrades warnings in the specified group to errors
28+
- `-Wsuppress <group>` - disables the emission of warnings in the specified group
29+
- `-Wwarning <group>` - indicates that warnings in the specified group should remain warnings, even if they were previously suppressed or upgraded to errors
30+
31+
The `<group>` parameter is a string identifier of the diagnostic group.
32+
33+
A diagnostic group is a stable identifier for an error or warning. It is an abstraction layer over the diagnostic identifiers used within the compiler. This is necessary because diagnostics within the compiler may change, but we need to provide stable user-facing identifiers for them.
34+
35+
A diagnostic group may include errors, warnings, or other diagnostic groups. For example, the `availability_deprecated` diagnostic group includes warnings related to the use of an API marked with the `@available(..., deprecated: ...)` attribute. The `deprecated` diagnostic group includes the `availability_deprecated` group and other groups related to deprecation.
36+
37+
Diagnostic groups may expand over time, but they can never become narrower. When a new diagnostic is added to the compiler, it is either included in an existing group or a new group is created for it, which in turn can also be included in one of the broader groups, if appropriate.
38+
39+
The order in which these flags are specified when invoking the compiler is important. If two or more options change the behavior of the same warning, we follow the rule "the last one wins."
40+
41+
We also retain the existing compiler options but modify their handling algorithm so that they are considered in the general list with the new options and follow the "last one wins" rule as well.
42+
43+
Thus, for example, you can use the combination `-warnings-as-errors -Wwarning deprecated`, which will upgrade all warnings to errors except for those in the `deprecated` group. However, if these flags are specified in the reverse order(`-Wwarning deprecated -warnings-as-errors`) it will be interpreted as upgrading all warnings to errors, as the `-warnings-as-errors` flag is the last one.
44+
45+
We are also introducing a new compiler flag, `-print-diagnostic-groups`, to display the names of diagnostic groups along with the textual representation of the warnings. When used, the warning message will be followed by the name of the narrowest group that includes that warning, enclosed in square brackets. For example:
46+
```
47+
main.swift:33:1: warning: 'f()' is deprecated [availability_deprecated]
48+
```
49+
50+
## Detailed design
51+
52+
### Diagnostic groups
53+
54+
Diagnostic groups form an acyclic graph with the following properties:
55+
56+
- A warning or error can only be included in one diagnostic group. This artificial restriction is introduced to solve two main problems:
57+
- When using the `-print-diagnostic-groups` flag, it would be inconvenient if a warning corresponded to multiple groups.
58+
- Documentation lookup will also be easier for the user if a diagnostic has only one identifier.
59+
60+
- A diagnostic group may include any number of other diagnostic groups. This will allow organizing groups into sets with similar meanings but different specific diagnostics. For example, the warnings `availability_deprecated` and `unsafe_global_actor_deprecated` are part of the supergroup `deprecated`.
61+
62+
- A diagnostic group can be included in any number of diagnostic groups. This allows expressing the membership of a group in multiple supergroups, where appropriate. For example, the group `unsafe_global_actor_deprecated` is part of both the `deprecated` and `concurrency` groups.
63+
64+
The internal structure of the graph may change to some extent. However, the set of diagnostics included in a diagnostic group (directly or transitively) should not shrink. There are two typical situations where the graph structure may change:
65+
- When adding a new diagnostic to the compiler, we also add a new group corresponding to that diagnostic. This new group can also be included in one or more existing groups if it belongs to them. For example, it is expected that the `deprecated` group will continuously include new subgroups.
66+
- If an existing diagnostic is split into more specific versions, and we want to allow users to use the more specific version in compiler options, a separate group is created for it, which **must** be included in the group of the original diagnostic.
67+
68+
For example, suppose we split the `availability_deprecated` warning into a general version and a specialized version `availability_deprecated_same_module`, which the compiler emits if the deprecated symbol is declared in the same module. In this case, the `availability_deprecated_same_module` group must be added to the `availability_deprecated` group to ensure that the overall composition of the `availability_deprecated` group does not change. The final structure should look like this:
69+
```
70+
availability_deprecated (group)
71+
├─ availability_deprecated (internal diag id)
72+
└─ availability_deprecated_same_module (group)
73+
└─ availability_deprecated_same_module (internal diag id)
74+
```
75+
Thus, invoking the compiler with the `-Werror availability_deprecated` parameter will cover both versions of the warning, and the behavior will remain unchanged. At the same time, the user can control the behavior of the narrower `availability_deprecated_same_module` group if they want to.
76+
77+
### Compiler options evaluation
78+
79+
Each warning in the compiler is assigned one of three behaviors: `warning`, `error`, or `suppressed`.
80+
Compiler options for controlling the behavior of groups are now processed as a single list. These options include:
81+
```
82+
-Werror <group>
83+
-Wsuppress <group>
84+
-Wwarning <group>
85+
-warnings-as-errors
86+
-no-warnings-as-errors
87+
-suppress-warnings
88+
```
89+
When these options are passed to the compiler, we sequentially apply the specified behavior to all warnings within the specified group from left to right. For `-warnings-as-errors`, `-no-warnings-as-errors`, and `-suppress-warnings`, we apply the behavior to all warnings.
90+
91+
The `-no-warnings-as-errors` option should be read as "set the behavior to 'warning' for all warnings". Thus, it overrides all previously set behaviors, including if the `-suppress-warnings` option was applied earlier or if a warning was suppressed by default.
92+
93+
Examples of option combinations:
94+
- `-warnings-as-errors -Wwarning deprecated`
95+
96+
Warnings from the `deprecated` group will be kept as warnings, but all the rest will be upgraded to errors.
97+
98+
- `-warnings-as-errors -Wwarning deprecated -Wsuppress availability_deprecated`
99+
100+
Warnings from the `availability_deprecated` group will be suppressed. Other warnings from the `deprecated` group will remain as warnings. All other warnings will be upgraded to errors.
101+
102+
- `-suppress-warnings -Wwarning deprecated`
103+
104+
Warnings from the `deprecated` group will remain as warnings. All others will be suppressed.
105+
106+
It’s crucial to understand that the order in which these flags are applied can significantly affect the behavior of diagnostics. The rule is "the last one wins", meaning that if multiple flags apply to the same diagnostic group, the last one specified on the command line will determine the final behavior.
107+
108+
It is also important to note that the order matters even if the specified groups are not explicitly related but have a common subgroup.
109+
For example, as mentioned above, the `unsafe_global_actor_deprecated` group is part of both the `deprecated` and `concurrency` groups. So the order in which options for the `deprecated` and `concurrency` groups are applied will change the final behavior of the `unsafe_global_actor_deprecated` group. Specifically:
110+
- `-Wwarning deprecated -Werror concurrency` will make it an error,
111+
- `-Werror concurrency -Wwarning deprecated` will keep it as a warning.
112+
113+
### Usage of `-print-diagnostic-groups` and `-debug-diagnostic-names`
114+
115+
As mentioned earlier, we are adding support for the `-print-diagnostic-groups` compiler option, which outputs the group name in square brackets.
116+
117+
A similar behavior already exists in the compiler and is enabled by the `-debug-diagnostic-names` option, but it prints the internal diagnostic identifiers used in the compiler. For example:
118+
```swift
119+
@available(iOS, deprecated: 10.0, renamed: "newFunction")
120+
func oldFunction() { ... }
121+
122+
oldFunction()
123+
```
124+
When compiled with the `-debug-diagnostic-names` option, the following message will be displayed:
125+
```
126+
'oldFunction()' is deprecated: renamed to 'newFunction' [availability_deprecated_rename]
127+
```
128+
The string `availability_deprecated_rename` is the internal identifier of this warning, not the group. Accordingly, it is not supported by the new compiler options.
129+
130+
When compiling the same code with the `-print-diagnostic-groups` option, the following message will be displayed:
131+
```
132+
'oldFunction()' is deprecated: renamed to 'newFunction' [availability_deprecated]
133+
```
134+
Here, the string `availability_deprecated` is the diagnostic group.
135+
136+
Often, group names and internal diagnostic identifiers coincide, but this is not always the case.
137+
138+
We retain support for `-debug-diagnostic-names` in its current form. However, to avoid confusion between diagnostic IDs and diagnostic groups, we prohibit the simultaneous use of these two options.
139+
140+
## Source compatibility
141+
142+
This proposal has no effect on source compatibility.
143+
144+
## ABI compatibility
145+
146+
This proposal has no effect on ABI compatibility.
147+
148+
## Implications on adoption
149+
150+
The adoption of diagnostic groups and the new compiler options will provide a foundation for flexible and precise control over warning behavior. However, to make this useful to end-users, significant work will be needed to mark existing diagnostics in diagnostic groups. It will also be necessary to develop a process for maintaining the relevance of diagnostic groups when new diagnostics are introduced in the compiler.
151+
152+
## Future directions
153+
154+
### Support in the language
155+
156+
While diagnostic groups are introduced to support the compiler options, it may be possible in the future to standardize the structure of the group graph itself. This could open up the possibility of using these same identifiers in the language, implementing something analogous to `#pragma diagnostic` or `[[attribute]]` in C++. However, such standardization and the design of new constructs in the language go far beyond the scope of this proposal, and we need to gain more experience with diagnostic groups before proceeding with this.
157+
158+
### Support in SwiftPM
159+
160+
If this proposal is accepted, it would make sense to support these parameters in SwiftPM as well, allowing the behavior of warnings to be conveniently specified in SwiftSetting.
161+
162+
## Alternatives considered
163+
164+
### Alternatives to diagnostic groups
165+
#### Status quo
166+
The lack of control over the behavior of specific diagnostics forces users to abandon the `-warnings-as-errors` compiler option and create ad-hoc compiler wrappers that filter its output.
167+
168+
#### Using existing diagnostic identifiers
169+
Warnings and errors in Swift can change as the compiler evolves.
170+
For example, one error might be renamed or split into two that are applied in different situations to improve the clarity of the text message depending on the context. Such a change would result in a new ID for the new error variant.
171+
172+
The example of `availability_deprecated_same_module` illustrates this well. If we used the warning ID, the behavior of the compiler with the `-Wwarning availability_deprecated` option would change when a new version of the warning is introduced, as this warning would no longer be triggered for the specific case of the same module.
173+
174+
Therefore, we need a solution that allows us to modify errors and warnings within the compiler while providing a reliable mechanism for identifying diagnostics that can be used by the user.
175+
176+
#### Flat list instead of a graph
177+
178+
To solve this problem, we could use an additional alias-ID for diagnostics that does not change when the main identifier changes.
179+
180+
Suppose we split the `availability_deprecated` diagnostic into a generic variant and `availability_deprecated_same_module`. To retain the existing name for the new variant, we could describe these two groups as
181+
```
182+
availability_deprecated (alias: availability_deprecated)
183+
availability_deprecated_same_module (alias: availability_deprecated)
184+
```
185+
However, this solution would not allow specifying the narrower `availability_deprecated_same_module` or the broader group `deprecated`.
186+
187+
#### Using multiple alias IDs for diagnostics
188+
To express a diagnostic's membership in multiple groups, we could allow multiple alias-IDs to be listed.
189+
```
190+
availability_deprecated aliases:
191+
availability_deprecated
192+
deprecated
193+
availability_deprecated_same_module aliases:
194+
availability_deprecated_same_module
195+
availability_deprecated
196+
deprecated
197+
```
198+
However, such a declaration lacks structure and makes it difficult to understand which alias-ID is the most specific.
199+
200+
### Alternative names for the compiler options
201+
202+
During the design process, other names for the compiler options were considered, which were formed as the singular form of the existing ones:
203+
| Plural | Singular |
204+
|--------------------------|--------------------------------|
205+
| `-warnings-as-errors` | `-warning-as-error <group>` |
206+
| `-no-warnings-as-errors` | `-no-warning-as-error <group>` |
207+
| `-suppress-warnings` | `-suppress-warning <group>` |
208+
209+
However, with this naming, the combination `-suppress-warning deprecated -no-warning-as-error availability_deprecated` might be misleading.
210+
211+
In Clang, diagnostic behavior is controlled through `-W...` options, but the format suffers from inconsistency. We adopt the `-W` prefix while making the format consistent.
212+
| Clang | Swift |
213+
|-------------------|----------------------|
214+
| `-W<group>` | `-Wwarning <group>` |
215+
| `-Wno-<group>` | `-Wsuppress <group>` |
216+
| `-Werror=<group>` | `-Werror <group>` |
217+
218+
Additionally, the option name `-Wwarning` is much better suited when it comes to enabling suppressed-by-default warnings. Today we have several of them behind dedicated flags like `-driver-warn-unused-options` and `-warn-concurrency`. It might be worth having a common infrastructure for warnings that are suppressed by default.
219+
220+
### Alternative format for `-print-diagnostic-groups`
221+
222+
Theoretically, we could allow the simultaneous use of `-debug-diagnostic-names` and `-print-diagnostic-groups`, but this would require choosing a different format for printing diagnostic groups.
223+
224+
Since `-debug-diagnostic-names` has been available in the compiler for a long time, we proceed from the fact that there are people who rely on this option and its format with square brackets.
225+
226+
To avoid overlap, we would need to use a different format, for example:
227+
```
228+
'foo()' is deprecated [availability_deprecated] [group:availability_deprecated]
229+
```
230+
231+
However, even this does not eliminate the possibility of breaking code that parses the compiler's output.
232+
233+
Moreover, `-print-diagnostic-groups` provides a formalized version of the same functionality using identifiers suitable for user use. And thus it should supersede the usages of `-debug-diagnostic-names`. Therefore, we believe the best solution would be to use the same format for `-print-diagnostic-groups` and prohibit the simultaneous use of these two options.
234+

0 commit comments

Comments
 (0)