Skip to content

Commit 368184c

Browse files
authored
Implement @deprecated in WIT (#1687)
This is intended to correspond with WebAssembly/component-model#377. Closes #1683
1 parent 62444c3 commit 368184c

19 files changed

+248
-29
lines changed

crates/wit-component/src/printing.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -903,19 +903,36 @@ impl WitPrinter {
903903
fn print_stability(&mut self, stability: &Stability) {
904904
match stability {
905905
Stability::Unknown => {}
906-
Stability::Stable { since, feature } => {
906+
Stability::Stable {
907+
since,
908+
feature,
909+
deprecated,
910+
} => {
907911
self.output.push_str("@since(version = ");
908912
self.output.push_str(&since.to_string());
909913
if let Some(feature) = feature {
910914
self.output.push_str(", feature = ");
911915
self.output.push_str(feature);
912916
}
913917
self.output.push_str(")\n");
918+
if let Some(version) = deprecated {
919+
self.output.push_str("@deprecated(version = ");
920+
self.output.push_str(&version.to_string());
921+
self.output.push_str(")\n");
922+
}
914923
}
915-
Stability::Unstable { feature } => {
924+
Stability::Unstable {
925+
feature,
926+
deprecated,
927+
} => {
916928
self.output.push_str("@unstable(feature = ");
917929
self.output.push_str(feature);
918930
self.output.push_str(")\n");
931+
if let Some(version) = deprecated {
932+
self.output.push_str("@deprecated(version = ");
933+
self.output.push_str(&version.to_string());
934+
self.output.push_str(")\n");
935+
}
919936
}
920937
}
921938
}

crates/wit-parser/src/ast.rs

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1550,6 +1550,10 @@ enum Attribute<'a> {
15501550
span: Span,
15511551
feature: Id<'a>,
15521552
},
1553+
Deprecated {
1554+
span: Span,
1555+
version: Version,
1556+
},
15531557
}
15541558

15551559
impl<'a> Attribute<'a> {
@@ -1559,35 +1563,46 @@ impl<'a> Attribute<'a> {
15591563
let id = parse_id(tokens)?;
15601564
let attr = match id.name {
15611565
"since" => {
1562-
tokens.eat(Token::LeftParen)?;
1566+
tokens.expect(Token::LeftParen)?;
15631567
eat_id(tokens, "version")?;
1564-
tokens.eat(Token::Equals)?;
1568+
tokens.expect(Token::Equals)?;
15651569
let (_span, version) = parse_version(tokens)?;
15661570
let feature = if tokens.eat(Token::Comma)? {
15671571
eat_id(tokens, "feature")?;
1568-
tokens.eat(Token::Equals)?;
1572+
tokens.expect(Token::Equals)?;
15691573
Some(parse_id(tokens)?)
15701574
} else {
15711575
None
15721576
};
1573-
tokens.eat(Token::RightParen)?;
1577+
tokens.expect(Token::RightParen)?;
15741578
Attribute::Since {
15751579
span: id.span,
15761580
version,
15771581
feature,
15781582
}
15791583
}
15801584
"unstable" => {
1581-
tokens.eat(Token::LeftParen)?;
1585+
tokens.expect(Token::LeftParen)?;
15821586
eat_id(tokens, "feature")?;
1583-
tokens.eat(Token::Equals)?;
1587+
tokens.expect(Token::Equals)?;
15841588
let feature = parse_id(tokens)?;
1585-
tokens.eat(Token::RightParen)?;
1589+
tokens.expect(Token::RightParen)?;
15861590
Attribute::Unstable {
15871591
span: id.span,
15881592
feature,
15891593
}
15901594
}
1595+
"deprecated" => {
1596+
tokens.expect(Token::LeftParen)?;
1597+
eat_id(tokens, "version")?;
1598+
tokens.expect(Token::Equals)?;
1599+
let (_span, version) = parse_version(tokens)?;
1600+
tokens.expect(Token::RightParen)?;
1601+
Attribute::Deprecated {
1602+
span: id.span,
1603+
version,
1604+
}
1605+
}
15911606
other => {
15921607
bail!(Error::new(id.span, format!("unknown attribute `{other}`"),))
15931608
}
@@ -1599,7 +1614,9 @@ impl<'a> Attribute<'a> {
15991614

16001615
fn span(&self) -> Span {
16011616
match self {
1602-
Attribute::Since { span, .. } | Attribute::Unstable { span, .. } => *span,
1617+
Attribute::Since { span, .. }
1618+
| Attribute::Unstable { span, .. }
1619+
| Attribute::Deprecated { span, .. } => *span,
16031620
}
16041621
}
16051622
}

crates/wit-parser/src/ast/resolve.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1376,19 +1376,54 @@ impl<'a> Resolver<'a> {
13761376
fn stability(&mut self, attrs: &[ast::Attribute<'_>]) -> Result<Stability> {
13771377
match attrs {
13781378
[] => Ok(Stability::Unknown),
1379+
1380+
[ast::Attribute::Since {
1381+
version, feature, ..
1382+
}] => Ok(Stability::Stable {
1383+
since: version.clone(),
1384+
feature: feature.as_ref().map(|s| s.name.to_string()),
1385+
deprecated: None,
1386+
}),
1387+
13791388
[ast::Attribute::Since {
13801389
version, feature, ..
1390+
}, ast::Attribute::Deprecated {
1391+
version: deprecated,
1392+
..
1393+
}]
1394+
| [ast::Attribute::Deprecated {
1395+
version: deprecated,
1396+
..
1397+
}, ast::Attribute::Since {
1398+
version, feature, ..
13811399
}] => Ok(Stability::Stable {
13821400
since: version.clone(),
13831401
feature: feature.as_ref().map(|s| s.name.to_string()),
1402+
deprecated: Some(deprecated.clone()),
13841403
}),
1404+
13851405
[ast::Attribute::Unstable { feature, .. }] => Ok(Stability::Unstable {
13861406
feature: feature.name.to_string(),
1407+
deprecated: None,
13871408
}),
1409+
1410+
[ast::Attribute::Unstable { feature, .. }, ast::Attribute::Deprecated { version, .. }]
1411+
| [ast::Attribute::Deprecated { version, .. }, ast::Attribute::Unstable { feature, .. }] => {
1412+
Ok(Stability::Unstable {
1413+
feature: feature.name.to_string(),
1414+
deprecated: Some(version.clone()),
1415+
})
1416+
}
1417+
[ast::Attribute::Deprecated { span, .. }] => {
1418+
bail!(Error::new(
1419+
*span,
1420+
"must pair @deprecated with either @since or @unstable",
1421+
))
1422+
}
13881423
[_, b, ..] => {
13891424
bail!(Error::new(
13901425
b.span(),
1391-
"only one stability attribute is allowed per-item",
1426+
"unsupported combination of attributes",
13921427
))
13931428
}
13941429
}

crates/wit-parser/src/lib.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -845,13 +845,35 @@ pub enum Stability {
845845
since: Version,
846846
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
847847
feature: Option<String>,
848+
#[cfg_attr(
849+
feature = "serde",
850+
serde(
851+
skip_serializing_if = "Option::is_none",
852+
default,
853+
serialize_with = "serialize_optional_version",
854+
deserialize_with = "deserialize_optional_version"
855+
)
856+
)]
857+
deprecated: Option<Version>,
848858
},
849859

850860
/// `@unstable(feature = foo)`
851861
///
852862
/// This item is explicitly tagged `@unstable`. A feature name is listed and
853863
/// this item is excluded by default in `Resolve` unless explicitly enabled.
854-
Unstable { feature: String },
864+
Unstable {
865+
feature: String,
866+
#[cfg_attr(
867+
feature = "serde",
868+
serde(
869+
skip_serializing_if = "Option::is_none",
870+
default,
871+
serialize_with = "serialize_optional_version",
872+
deserialize_with = "deserialize_optional_version"
873+
)
874+
)]
875+
deprecated: Option<Version>,
876+
},
855877

856878
/// This item does not have either `@since` or `@unstable`.
857879
Unknown,

crates/wit-parser/src/resolve.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1411,7 +1411,9 @@ impl Resolve {
14111411
fn include_stability(&self, stability: &Stability) -> bool {
14121412
match stability {
14131413
Stability::Stable { .. } | Stability::Unknown => true,
1414-
Stability::Unstable { feature } => self.features.contains(feature) || self.all_features,
1414+
Stability::Unstable { feature, .. } => {
1415+
self.features.contains(feature) || self.all_features
1416+
}
14151417
}
14161418
}
14171419
}

crates/wit-parser/src/serde_.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,26 @@ where
122122
let version: String = String::deserialize(deserializer)?;
123123
version.parse().map_err(|e| D::Error::custom(e))
124124
}
125+
126+
pub fn serialize_optional_version<S>(
127+
version: &Option<Version>,
128+
serializer: S,
129+
) -> Result<S::Ok, S::Error>
130+
where
131+
S: Serializer,
132+
{
133+
version
134+
.as_ref()
135+
.map(|s| s.to_string())
136+
.serialize(serializer)
137+
}
138+
139+
pub fn deserialize_optional_version<'de, D>(deserializer: D) -> Result<Option<Version>, D::Error>
140+
where
141+
D: serde::de::Deserializer<'de>,
142+
{
143+
match <Option<String>>::deserialize(deserializer)? {
144+
Some(version) => Ok(Some(version.parse().map_err(|e| D::Error::custom(e))?)),
145+
None => Ok(None),
146+
}
147+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package a:b;
2+
3+
@deprecated
4+
interface foo {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
expected '(', found keyword `interface`
2+
--> tests/ui/parse-fail/bad-deprecated1.wit:4:1
3+
|
4+
4 | interface foo {}
5+
| ^
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package a:b;
2+
3+
@deprecated()
4+
interface foo {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
expected an identifier or string, found ')'
2+
--> tests/ui/parse-fail/bad-deprecated2.wit:3:13
3+
|
4+
3 | @deprecated()
5+
| ^
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package a:b;
2+
3+
@deprecated(since = 1.2.3)
4+
interface foo {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
expected `version`, found `since`
2+
--> tests/ui/parse-fail/bad-deprecated3.wit:3:13
3+
|
4+
3 | @deprecated(since = 1.2.3)
5+
| ^----
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package a:b;
2+
3+
@deprecated(since = 1.2.3)
4+
@deprecated(since = 1.2.3)
5+
interface foo {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
expected `version`, found `since`
2+
--> tests/ui/parse-fail/bad-deprecated4.wit:3:13
3+
|
4+
3 | @deprecated(since = 1.2.3)
5+
| ^----
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
expected an identifier or string, found keyword `interface`
1+
expected '(', found keyword `interface`
22
--> tests/ui/parse-fail/bad-since1.wit:4:1
33
|
44
4 | interface foo1 {}
5-
| ^--------
5+
| ^

crates/wit-parser/tests/ui/since-and-unstable.wit

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,21 @@ world in-a-world {
8787
constructor();
8888
}
8989
}
90+
91+
interface deprecated1 {
92+
@since(version = 1.0.0)
93+
@deprecated(version = 1.0.1)
94+
type t1 = u32;
95+
96+
@deprecated(version = 1.0.1)
97+
@since(version = 1.0.0)
98+
type t2 = u32;
99+
100+
@unstable(feature = foo)
101+
@deprecated(version = 1.0.1)
102+
type t3 = u32;
103+
104+
@deprecated(version = 1.0.1)
105+
@unstable(feature = foo)
106+
type t4 = u32;
107+
}

0 commit comments

Comments
 (0)