@@ -23,6 +23,7 @@ use smallvec::{smallvec, SmallVec};
2323
2424use std:: borrow:: Cow ;
2525use std:: cell:: Cell ;
26+ use std:: mem;
2627use std:: ops:: Range ;
2728
2829use crate :: clean:: * ;
@@ -65,10 +66,53 @@ enum ResolutionFailure<'a> {
6566 NotResolved { module_id : DefId , partial_res : Option < Res > , unresolved : Cow < ' a , str > } ,
6667 /// should not ever happen
6768 NoParentItem ,
69+ /// This link has malformed generic parameters; e.g., the angle brackets are unbalanced.
70+ MalformedGenerics ( MalformedGenerics ) ,
6871 /// used to communicate that this should be ignored, but shouldn't be reported to the user
6972 Dummy ,
7073}
7174
75+ #[ derive( Debug ) ]
76+ enum MalformedGenerics {
77+ /// This link has unbalanced angle brackets.
78+ ///
79+ /// For example, `Vec<T` should trigger this, as should `Vec<T>>`.
80+ UnbalancedAngleBrackets ,
81+ /// The generics are not attached to a type.
82+ ///
83+ /// For example, `<T>` should trigger this.
84+ ///
85+ /// This is detected by checking if the path is empty after the generics are stripped.
86+ MissingType ,
87+ /// The link uses fully-qualified syntax, which is currently unsupported.
88+ ///
89+ /// For example, `<Vec as IntoIterator>::into_iter` should trigger this.
90+ ///
91+ /// This is detected by checking if ` as ` (the keyword `as` with spaces around it) is inside
92+ /// angle brackets.
93+ HasFullyQualifiedSyntax ,
94+ /// The link has an invalid path separator.
95+ ///
96+ /// For example, `Vec:<T>:new()` should trigger this. Note that `Vec:new()` will **not**
97+ /// trigger this because it has no generics and thus [`strip_generics_from_path`] will not be
98+ /// called.
99+ ///
100+ /// Note that this will also **not** be triggered if the invalid path separator is inside angle
101+ /// brackets because rustdoc mostly ignores what's inside angle brackets (except for
102+ /// [`HasFullyQualifiedSyntax`](MalformedGenerics::HasFullyQualifiedSyntax)).
103+ ///
104+ /// This is detected by checking if there is a colon followed by a non-colon in the link.
105+ InvalidPathSeparator ,
106+ /// The link has too many angle brackets.
107+ ///
108+ /// For example, `Vec<<T>>` should trigger this.
109+ TooManyAngleBrackets ,
110+ /// The link has empty angle brackets.
111+ ///
112+ /// For example, `Vec<>` should trigger this.
113+ EmptyAngleBrackets ,
114+ }
115+
72116impl ResolutionFailure < ' a > {
73117 // This resolved fully (not just partially) but is erroneous for some other reason
74118 fn full_res ( & self ) -> Option < Res > {
@@ -912,6 +956,7 @@ impl LinkCollector<'_, '_> {
912956 let link_text;
913957 let mut path_str;
914958 let disambiguator;
959+ let stripped_path_string;
915960 let ( mut res, mut fragment) = {
916961 path_str = if let Ok ( ( d, path) ) = Disambiguator :: from_str ( & link) {
917962 disambiguator = Some ( d) ;
@@ -922,7 +967,7 @@ impl LinkCollector<'_, '_> {
922967 }
923968 . trim ( ) ;
924969
925- if path_str. contains ( |ch : char | !( ch. is_alphanumeric ( ) || ch == ':' || ch == '_' ) ) {
970+ if path_str. contains ( |ch : char | !( ch. is_alphanumeric ( ) || ":_<>, " . contains ( ch ) ) ) {
926971 return None ;
927972 }
928973
@@ -985,6 +1030,36 @@ impl LinkCollector<'_, '_> {
9851030 module_id = DefId { krate, index : CRATE_DEF_INDEX } ;
9861031 }
9871032
1033+ // Strip generics from the path.
1034+ if path_str. contains ( [ '<' , '>' ] . as_slice ( ) ) {
1035+ stripped_path_string = match strip_generics_from_path ( path_str) {
1036+ Ok ( path) => path,
1037+ Err ( err_kind) => {
1038+ debug ! ( "link has malformed generics: {}" , path_str) ;
1039+ resolution_failure (
1040+ self ,
1041+ & item,
1042+ path_str,
1043+ disambiguator,
1044+ dox,
1045+ link_range,
1046+ smallvec ! [ err_kind] ,
1047+ ) ;
1048+ return None ;
1049+ }
1050+ } ;
1051+ path_str = & stripped_path_string;
1052+ }
1053+
1054+ // Sanity check to make sure we don't have any angle brackets after stripping generics.
1055+ assert ! ( !path_str. contains( [ '<' , '>' ] . as_slice( ) ) ) ;
1056+
1057+ // The link is not an intra-doc link if it still contains commas or spaces after
1058+ // stripping generics.
1059+ if path_str. contains ( [ ',' , ' ' ] . as_slice ( ) ) {
1060+ return None ;
1061+ }
1062+
9881063 match self . resolve_with_disambiguator (
9891064 disambiguator,
9901065 item,
@@ -1718,6 +1793,27 @@ fn resolution_failure(
17181793 diag. level = rustc_errors:: Level :: Bug ;
17191794 "all intra doc links should have a parent item" . to_owned ( )
17201795 }
1796+ ResolutionFailure :: MalformedGenerics ( variant) => match variant {
1797+ MalformedGenerics :: UnbalancedAngleBrackets => {
1798+ String :: from ( "unbalanced angle brackets" )
1799+ }
1800+ MalformedGenerics :: MissingType => {
1801+ String :: from ( "missing type for generic parameters" )
1802+ }
1803+ MalformedGenerics :: HasFullyQualifiedSyntax => {
1804+ diag. note ( "see https://github.com/rust-lang/rust/issues/74563 for more information" ) ;
1805+ String :: from ( "fully-qualified syntax is unsupported" )
1806+ }
1807+ MalformedGenerics :: InvalidPathSeparator => {
1808+ String :: from ( "has invalid path separator" )
1809+ }
1810+ MalformedGenerics :: TooManyAngleBrackets => {
1811+ String :: from ( "too many angle brackets" )
1812+ }
1813+ MalformedGenerics :: EmptyAngleBrackets => {
1814+ String :: from ( "empty angle brackets" )
1815+ }
1816+ } ,
17211817 } ;
17221818 if let Some ( span) = sp {
17231819 diag. span_label ( span, & note) ;
@@ -1908,3 +2004,108 @@ fn is_primitive(path_str: &str, ns: Namespace) -> Option<(&'static str, Res)> {
19082004fn primitive_impl ( cx : & DocContext < ' _ > , path_str : & str ) -> Option < & ' static SmallVec < [ DefId ; 4 ] > > {
19092005 Some ( PrimitiveType :: from_symbol ( Symbol :: intern ( path_str) ) ?. impls ( cx. tcx ) )
19102006}
2007+
2008+ fn strip_generics_from_path ( path_str : & str ) -> Result < String , ResolutionFailure < ' static > > {
2009+ let mut stripped_segments = vec ! [ ] ;
2010+ let mut path = path_str. chars ( ) . peekable ( ) ;
2011+ let mut segment = Vec :: new ( ) ;
2012+
2013+ while let Some ( chr) = path. next ( ) {
2014+ match chr {
2015+ ':' => {
2016+ if path. next_if_eq ( & ':' ) . is_some ( ) {
2017+ let stripped_segment =
2018+ strip_generics_from_path_segment ( mem:: take ( & mut segment) ) ?;
2019+ if !stripped_segment. is_empty ( ) {
2020+ stripped_segments. push ( stripped_segment) ;
2021+ }
2022+ } else {
2023+ return Err ( ResolutionFailure :: MalformedGenerics (
2024+ MalformedGenerics :: InvalidPathSeparator ,
2025+ ) ) ;
2026+ }
2027+ }
2028+ '<' => {
2029+ segment. push ( chr) ;
2030+
2031+ match path. peek ( ) {
2032+ Some ( '<' ) => {
2033+ return Err ( ResolutionFailure :: MalformedGenerics (
2034+ MalformedGenerics :: TooManyAngleBrackets ,
2035+ ) ) ;
2036+ }
2037+ Some ( '>' ) => {
2038+ return Err ( ResolutionFailure :: MalformedGenerics (
2039+ MalformedGenerics :: EmptyAngleBrackets ,
2040+ ) ) ;
2041+ }
2042+ Some ( _) => {
2043+ segment. push ( path. next ( ) . unwrap ( ) ) ;
2044+
2045+ while let Some ( chr) = path. next_if ( |c| * c != '>' ) {
2046+ segment. push ( chr) ;
2047+ }
2048+ }
2049+ None => break ,
2050+ }
2051+ }
2052+ _ => segment. push ( chr) ,
2053+ }
2054+ debug ! ( "raw segment: {:?}" , segment) ;
2055+ }
2056+
2057+ if !segment. is_empty ( ) {
2058+ let stripped_segment = strip_generics_from_path_segment ( segment) ?;
2059+ if !stripped_segment. is_empty ( ) {
2060+ stripped_segments. push ( stripped_segment) ;
2061+ }
2062+ }
2063+
2064+ debug ! ( "path_str: {:?}\n stripped segments: {:?}" , path_str, & stripped_segments) ;
2065+
2066+ let stripped_path = stripped_segments. join ( "::" ) ;
2067+
2068+ if !stripped_path. is_empty ( ) {
2069+ Ok ( stripped_path)
2070+ } else {
2071+ Err ( ResolutionFailure :: MalformedGenerics ( MalformedGenerics :: MissingType ) )
2072+ }
2073+ }
2074+
2075+ fn strip_generics_from_path_segment (
2076+ segment : Vec < char > ,
2077+ ) -> Result < String , ResolutionFailure < ' static > > {
2078+ let mut stripped_segment = String :: new ( ) ;
2079+ let mut param_depth = 0 ;
2080+
2081+ let mut latest_generics_chunk = String :: new ( ) ;
2082+
2083+ for c in segment {
2084+ if c == '<' {
2085+ param_depth += 1 ;
2086+ latest_generics_chunk. clear ( ) ;
2087+ } else if c == '>' {
2088+ param_depth -= 1 ;
2089+ if latest_generics_chunk. contains ( " as " ) {
2090+ // The segment tries to use fully-qualified syntax, which is currently unsupported.
2091+ // Give a helpful error message instead of completely ignoring the angle brackets.
2092+ return Err ( ResolutionFailure :: MalformedGenerics (
2093+ MalformedGenerics :: HasFullyQualifiedSyntax ,
2094+ ) ) ;
2095+ }
2096+ } else {
2097+ if param_depth == 0 {
2098+ stripped_segment. push ( c) ;
2099+ } else {
2100+ latest_generics_chunk. push ( c) ;
2101+ }
2102+ }
2103+ }
2104+
2105+ if param_depth == 0 {
2106+ Ok ( stripped_segment)
2107+ } else {
2108+ // The segment has unbalanced angle brackets, e.g. `Vec<T` or `Vec<T>>`
2109+ Err ( ResolutionFailure :: MalformedGenerics ( MalformedGenerics :: UnbalancedAngleBrackets ) )
2110+ }
2111+ }
0 commit comments