diff --git a/Cargo.lock b/Cargo.lock index 9c6d6c22de4dd..33ca5db1a5d55 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -197,7 +197,7 @@ dependencies = [ "memchr", "proc-macro2", "quote", - "rustc-hash 2.1.1", + "rustc-hash 2.1.2", "serde", "serde_derive", "syn", @@ -218,7 +218,7 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7db09fde9143e7ac4513358fb32ee32847125b63b18ea715afd487956da715da" dependencies = [ - "rustc-hash 2.1.1", + "rustc-hash 2.1.2", "serde", "serde_derive", "unicode-ident", @@ -287,9 +287,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "blake3" @@ -662,7 +662,7 @@ dependencies = [ "declare_clippy_lint", "filetime", "itertools", - "pulldown-cmark", + "pulldown-cmark 0.11.3", "regex", "rustc_tools_util 0.4.2", "serde", @@ -1250,6 +1250,12 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + [[package]] name = "dyn-clone" version = "1.0.20" @@ -1417,7 +1423,7 @@ dependencies = [ "fluent-syntax", "intl-memoizer", "intl_pluralrules", - "rustc-hash 2.1.1", + "rustc-hash 2.1.2", "self_cell", "smallvec", "unic-langid", @@ -2104,7 +2110,7 @@ dependencies = [ "anyhow", "clap", "fs-err", - "rustc-hash 2.1.1", + "rustc-hash 2.1.2", "rustdoc-json-types", "serde", "serde_json", @@ -2361,6 +2367,35 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "math-core" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "938914f21d07488e5c9b2386d951494976b158b9616ee67bbda5afa5c2924eeb" +dependencies = [ + "math-core-renderer-internal", + "memchr", + "phf 0.13.1", + "rustc-hash 2.1.2", + "static_assertions", + "strum", + "strum_macros", +] + +[[package]] +name = "math-core-renderer-internal" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3716dfbc3e092d5b212c0b03e6fedb60ffbceb21ed5df9346a09866f47d07e84" +dependencies = [ + "bitflags", + "dtoa", + "stable-arena", + "static_assertions", + "strum", + "strum_macros", +] + [[package]] name = "md-5" version = "0.10.6" @@ -2899,13 +2934,24 @@ dependencies = [ "phf_shared 0.12.1", ] +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros", + "phf_shared 0.13.1", + "serde", +] + [[package]] name = "phf_codegen" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ - "phf_generator", + "phf_generator 0.11.3", "phf_shared 0.11.3", ] @@ -2919,6 +2965,29 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared 0.13.1", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "phf_shared" version = "0.11.3" @@ -2937,6 +3006,15 @@ dependencies = [ "siphasher", ] +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -3093,6 +3171,18 @@ dependencies = [ "unicase", ] +[[package]] +name = "pulldown-cmark" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c3a14896dfa883796f1cb410461aef38810ea05f2b2c33c5aded3649095fdad" +dependencies = [ + "bitflags", + "memchr", + "pulldown-cmark-escape", + "unicase", +] + [[package]] name = "pulldown-cmark-escape" version = "0.11.0" @@ -3409,9 +3499,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustc-literal-escaper" @@ -3777,7 +3867,7 @@ dependencies = [ "memmap2", "parking_lot", "portable-atomic", - "rustc-hash 2.1.1", + "rustc-hash 2.1.2", "rustc-stable-hash", "rustc_arena", "rustc_graphviz", @@ -4454,7 +4544,7 @@ dependencies = [ name = "rustc_pattern_analysis" version = "0.0.0" dependencies = [ - "rustc-hash 2.1.1", + "rustc-hash 2.1.2", "rustc_abi", "rustc_apfloat", "rustc_arena", @@ -4548,7 +4638,7 @@ version = "0.0.0" dependencies = [ "indexmap", "itertools", - "pulldown-cmark", + "pulldown-cmark 0.13.3", "rustc_arena", "rustc_ast", "rustc_ast_pretty", @@ -4782,7 +4872,7 @@ dependencies = [ "derive-where", "ena", "indexmap", - "rustc-hash 2.1.1", + "rustc-hash 2.1.2", "rustc_abi", "rustc_ast_ir", "rustc_data_structures", @@ -4833,6 +4923,7 @@ dependencies = [ "expect-test", "indexmap", "itertools", + "math-core", "minifier", "proc-macro2", "pulldown-cmark-escape", @@ -4867,7 +4958,7 @@ version = "0.1.0" dependencies = [ "bincode", "rkyv", - "rustc-hash 2.1.1", + "rustc-hash 2.1.2", "serde", "serde_derive", "serde_json", @@ -5250,6 +5341,12 @@ dependencies = [ "color-eyre", ] +[[package]] +name = "stable-arena" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a6572b16faf73db61fab0b1d848ce108e20d026dc20dcb043a3f83c73ad1c09" + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -5294,7 +5391,7 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" dependencies = [ - "phf_generator", + "phf_generator 0.11.3", "phf_shared 0.11.3", "proc-macro2", "quote", @@ -5315,6 +5412,24 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" + +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "syn" version = "2.0.110" @@ -5526,7 +5641,7 @@ dependencies = [ "ignore", "miropt-test-tools", "regex", - "rustc-hash 2.1.1", + "rustc-hash 2.1.2", "semver", "serde", "similar", @@ -5825,7 +5940,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" dependencies = [ - "rustc-hash 2.1.1", + "rustc-hash 2.1.2", ] [[package]] diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs index 10232a7c55a5f..7c945102caef8 100644 --- a/compiler/rustc_attr_parsing/src/attributes/doc.rs +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -3,7 +3,8 @@ use rustc_errors::{Applicability, msg}; use rustc_feature::template; use rustc_hir::Target; use rustc_hir::attrs::{ - AttributeKind, CfgEntry, CfgHideShow, CfgInfo, DocAttribute, DocInline, HideOrShow, + AttributeKind, CfgEntry, CfgHideShow, CfgInfo, DocAttribute, DocAttributeSyntax, DocInline, + HideOrShow, }; use rustc_session::errors::feature_err; use rustc_span::{Span, Symbol, edition, sym}; @@ -574,6 +575,42 @@ impl DocParser { } } } + Some(sym::syntax) => { + if !cx.features().rustdoc_texmath() { + feature_err( + cx.sess(), + sym::rustdoc_texmath, + path.span(), + msg!("the `#[doc(syntax)]` attribute is unstable"), + ) + .emit(); + } + let span = args.span().unwrap_or(path.span()); + let tex_math_dollars = if let Some(v) = args.as_name_value() + && let Some(syntax) = v.value_as_str() + { + match syntax.as_str() { + "+tex_math_dollars" => true, + "-tex_math_dollars" => false, + _ => { + cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + MalformedDoc, + span, + ); + false + } + } + } else { + cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + MalformedDoc, + span, + ); + false + }; + self.attribute.syntax = Some(DocAttributeSyntax { tex_math_dollars, span }); + } Some(sym::spotlight) => { let span = path.span(); cx.emit_lint( diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 0d44d3c0d4d79..4d4fe4ed00b59 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -340,6 +340,8 @@ declare_features! ( (internal, rustdoc_internals, "1.58.0", Some(90418)), /// Allows using the `rustdoc::missing_doc_code_examples` lint (unstable, rustdoc_missing_doc_code_examples, "1.31.0", Some(101730)), + /// Allows using the `#[doc(math_syntax)]` and `#[doc(no_math_syntax)]` crate attributes + (unstable, rustdoc_texmath, "CURRENT_RUSTC_VERSION", None), /// Introduces a hierarchy of `Sized` traits (RFC 3729). (unstable, sized_hierarchy, "1.89.0", Some(144404)), /// Allows using `#[structural_match]` which indicates that a type is structurally matchable. diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 2cdcf75d00be7..24623570cf40c 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -558,6 +558,34 @@ pub struct DocAttribute { // #[doc(test(...))] pub test_attrs: ThinVec, pub no_crate_inject: Option, + + // #[doc(syntax="-tex_math_dollars")] // disable + // #[doc(syntax="+tex_math_dollars")] // enable + pub syntax: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, StableHash, Decodable, Encodable)] +pub struct DocAttributeSyntax { + pub tex_math_dollars: bool, + pub span: Span, +} + +impl PrintAttribute for DocAttributeSyntax { + fn should_render(&self) -> bool { + true + } + fn print_attribute(&self, p: &mut rustc_ast_pretty::pp::Printer) { + match self { + DocAttributeSyntax { tex_math_dollars, span: _ } => { + p.word("syntax"); + p.word("="); + p.word(format!( + r#""{}tex_math_dollars""#, + if *tex_math_dollars { "+" } else { "-" } + )); + } + } + } } impl rustc_serialize::Encodable for DocAttribute { @@ -585,6 +613,7 @@ impl rustc_serialize::Encodable for DocAttribute rust_logo, test_attrs, no_crate_inject, + syntax, } = self; rustc_serialize::Encodable::::encode(first_span, encoder); rustc_serialize::Encodable::::encode(aliases, encoder); @@ -615,6 +644,7 @@ impl rustc_serialize::Encodable for DocAttribute rustc_serialize::Encodable::::encode(rust_logo, encoder); rustc_serialize::Encodable::::encode(test_attrs, encoder); rustc_serialize::Encodable::::encode(no_crate_inject, encoder); + rustc_serialize::Encodable::::encode(syntax, encoder); } } diff --git a/compiler/rustc_middle/src/queries.rs b/compiler/rustc_middle/src/queries.rs index a9fc5dcac8f29..2877d2e41d76e 100644 --- a/compiler/rustc_middle/src/queries.rs +++ b/compiler/rustc_middle/src/queries.rs @@ -62,7 +62,7 @@ use rustc_data_structures::svh::Svh; use rustc_data_structures::unord::{UnordMap, UnordSet}; use rustc_errors::{ErrorGuaranteed, catch_fatal_errors}; use rustc_hir as hir; -use rustc_hir::attrs::{EiiDecl, EiiImpl, StrippedCfgItem}; +use rustc_hir::attrs::{DocAttributeSyntax, EiiDecl, EiiImpl, StrippedCfgItem}; use rustc_hir::def::{DefKind, DocLinkResMap}; use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, LocalDefId, LocalDefIdSet, LocalModDefId}; use rustc_hir::lang_items::{LangItem, LanguageItems}; @@ -1513,6 +1513,11 @@ rustc_queries! { desc { "checking whether `{}` is `doc(notable_trait)`", tcx.def_path_str(def_id) } } + /// Return the math syntax configuration for this crates' doc attribute, or `Unspecified` if there is none. + query doc_attribute_syntax(def_id: DefId) -> Option<&'tcx DocAttributeSyntax> { + desc { "looking up math syntax for crate {}", tcx.def_path_str(def_id) } + } + /// Returns the attributes on the item at `def_id`. /// /// Do not use this directly, use `tcx.get_attrs` instead. diff --git a/compiler/rustc_middle/src/ty/util.rs b/compiler/rustc_middle/src/ty/util.rs index 2cdbe2bee3b82..6b9f6974ccad8 100644 --- a/compiler/rustc_middle/src/ty/util.rs +++ b/compiler/rustc_middle/src/ty/util.rs @@ -9,6 +9,7 @@ use rustc_data_structures::stable_hash::{StableHash, StableHasher}; use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_errors::ErrorGuaranteed; use rustc_hashes::Hash128; +use rustc_hir::attrs::DocAttributeSyntax; use rustc_hir::def::{CtorOf, DefKind, Res}; use rustc_hir::def_id::{CrateNum, DefId, LocalDefId}; use rustc_hir::limit::Limit; @@ -1689,6 +1690,21 @@ pub fn is_doc_notable_trait(tcx: TyCtxt<'_>, def_id: DefId) -> bool { find_attr!(tcx, def_id, Doc(doc) if doc.notable_trait.is_some()) } +/// Get doc attribute math syntax declaration, if any. +pub fn doc_attribute_syntax(tcx: TyCtxt<'_>, mut def_id: DefId) -> Option<&'_ DocAttributeSyntax> { + loop { + let attr = find_attr!( + tcx, + def_id, + Doc(doc) if doc.syntax.is_some() => doc.syntax.as_ref().unwrap() + ); + if attr.is_some() { + return attr; + } + def_id = tcx.opt_parent(def_id)?; + } +} + /// Determines whether an item is an intrinsic (which may be via Abi or via the `rustc_intrinsic` attribute). /// /// We double check the feature gate here because whether a function may be defined as an intrinsic causes @@ -1717,6 +1733,7 @@ pub fn provide(providers: &mut Providers) { reveal_opaque_types_in_bounds, is_doc_hidden, is_doc_notable_trait, + doc_attribute_syntax, intrinsic_raw, ..*providers } diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index f1596a0685768..d8461112b4df3 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -1062,6 +1062,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { // already checked in attr_parsing no_crate_inject: _, attribute, + syntax: _, } = attr; for (alias, span) in aliases { diff --git a/compiler/rustc_resolve/Cargo.toml b/compiler/rustc_resolve/Cargo.toml index 2fc251e2b525e..ed2e5f8cceef6 100644 --- a/compiler/rustc_resolve/Cargo.toml +++ b/compiler/rustc_resolve/Cargo.toml @@ -7,7 +7,7 @@ edition = "2024" # tidy-alphabetical-start indexmap = "2.4.0" itertools = "0.12" -pulldown-cmark = { version = "0.11", features = [ +pulldown-cmark = { version = "0.13", features = [ "html", ], default-features = false } rustc_arena = { path = "../rustc_arena" } diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 3337a4626b040..4fc8bf65f3d7e 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -5417,7 +5417,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { } let mut need_traits_in_scope = false; - for path_str in rustdoc::attrs_to_preprocessed_links(attrs) { + for path_str in rustdoc::attrs_to_preprocessed_links(self.r.tcx, attrs) { // Resolve all namespaces due to no disambiguator or for diagnostics. let mut any_resolved = false; let mut need_assoc = false; diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index f26405cb223c0..00a8ebf56dcdf 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -14,6 +14,7 @@ use rustc_ast::token::DocFragmentKind; use rustc_ast::util::comments::beautify_doc_string; use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::unord::UnordSet; +use rustc_hir::attrs::DocAttributeSyntax; use rustc_middle::ty::TyCtxt; use rustc_span::def_id::DefId; use rustc_span::source_map::SourceMap; @@ -248,12 +249,17 @@ pub fn prepare_to_doc_link_resolution( } /// Options for rendering Markdown in the main body of documentation. -pub fn main_body_opts() -> Options { +pub fn main_body_opts(doc_syntax: Option<&DocAttributeSyntax>) -> Options { Options::ENABLE_TABLES | Options::ENABLE_FOOTNOTES | Options::ENABLE_STRIKETHROUGH | Options::ENABLE_TASKLISTS | Options::ENABLE_SMART_PUNCTUATION + | (if let Some(DocAttributeSyntax { tex_math_dollars: true, .. }) = doc_syntax { + Options::ENABLE_MATH + } else { + Options::empty() + }) } fn strip_generics_from_path_segment(segment: Vec) -> Result { @@ -404,16 +410,20 @@ pub fn may_be_doc_link(link_type: LinkType) -> bool { | LinkType::Shortcut | LinkType::ShortcutUnknown => true, LinkType::Autolink | LinkType::Email => false, + LinkType::WikiLink { has_pothole: _ } => unreachable!("wikilinks aren't used in rustdoc"), } } /// Simplified version of `preprocessed_markdown_links` from rustdoc. /// Must return at least the same links as it, but may add some more links on top of that. -pub(crate) fn attrs_to_preprocessed_links(attrs: &[ast::Attribute]) -> Vec> { +pub(crate) fn attrs_to_preprocessed_links( + tcx: TyCtxt<'_>, + attrs: &[ast::Attribute], +) -> Vec> { let (doc_fragments, other_attrs) = attrs_to_doc_fragments(attrs.iter().map(|attr| (attr, None)), false); - let mut doc = - prepare_to_doc_link_resolution(&doc_fragments).into_values().next().unwrap_or_default(); + let (def_id, mut doc) = + prepare_to_doc_link_resolution(&doc_fragments).into_iter().next().unwrap_or_default(); for attr in other_attrs { if let Some(note) = attr.deprecation_note() { @@ -422,16 +432,16 @@ pub(crate) fn attrs_to_preprocessed_links(attrs: &[ast::Attribute]) -> Vec(doc: &'md str) -> Vec> { +fn parse_links<'md>(doc: &'md str, doc_syntax: Option<&DocAttributeSyntax>) -> Vec> { let mut broken_link_callback = |link: BrokenLink<'md>| Some((link.reference, "".into())); let mut event_iter = Parser::new_with_broken_link_callback( doc, - main_body_opts(), + main_body_opts(doc_syntax), Some(&mut broken_link_callback), ); let mut links = Vec::new(); diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index a97a0c82cfa97..15a9b88914346 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1812,6 +1812,7 @@ symbols! { rustdoc, rustdoc_internals, rustdoc_missing_doc_code_examples, + rustdoc_texmath, rustfmt, rvalue_static_promotion, rwpi, @@ -2007,6 +2008,7 @@ symbols! { sve_tuple_set, sym, sync, + syntax, synthetic, t32, target, diff --git a/src/bootstrap/src/utils/proc_macro_deps.rs b/src/bootstrap/src/utils/proc_macro_deps.rs index 94f8243ae1f68..0d5e55c20ae4d 100644 --- a/src/bootstrap/src/utils/proc_macro_deps.rs +++ b/src/bootstrap/src/utils/proc_macro_deps.rs @@ -16,6 +16,7 @@ pub static CRATES: &[&str] = &[ "derive_builder_core", "digest", "equivalent", + "fastrand", "fluent-bundle", "fluent-langneg", "fluent-syntax", @@ -36,6 +37,8 @@ pub static CRATES: &[&str] = &[ "pest", "pest_generator", "pest_meta", + "phf_generator", + "phf_shared", "proc-macro2", "quote", "rustc-hash", @@ -44,6 +47,7 @@ pub static CRATES: &[&str] = &[ "serde_core", "serde_derive_internals", "sha2", + "siphasher", "smallvec", "stable_deref_trait", "strsim", diff --git a/src/librustdoc/Cargo.toml b/src/librustdoc/Cargo.toml index c06b4857e4903..a2337885019a2 100644 --- a/src/librustdoc/Cargo.toml +++ b/src/librustdoc/Cargo.toml @@ -14,6 +14,7 @@ askama = { version = "0.16.0", default-features = false, features = ["alloc", "c base64 = "0.21.7" indexmap = { version = "2", features = ["serde"] } itertools = "0.12" +math-core = "0.6.1" minifier = { version = "0.3.5", default-features = false } proc-macro2 = "1.0.103" pulldown-cmark-escape = { version = "0.11.0", features = ["simd"] } diff --git a/src/librustdoc/build.rs b/src/librustdoc/build.rs index 94bb0e30fcdbc..f4586e4caa6d0 100644 --- a/src/librustdoc/build.rs +++ b/src/librustdoc/build.rs @@ -8,6 +8,7 @@ fn main() { "static/css/rustdoc.css", "static/css/noscript.css", "static/css/normalize.css", + "static/css/mathml.css", "static/js/main.js", "static/js/search.js", "static/js/stringdex.js", @@ -28,6 +29,8 @@ fn main() { "static/fonts/FiraMono-Regular.woff2", "static/fonts/FiraMono-Medium.woff2", "static/fonts/Fira-LICENSE.txt", + "static/fonts/ModifiedNotoSansMath-Regular.woff2", + "static/fonts/ModifiedNotoSansMath-LICENSE.txt", "static/fonts/SourceSerif4-Regular.ttf.woff2", "static/fonts/SourceSerif4-Semibold.ttf.woff2", "static/fonts/SourceSerif4-Bold.ttf.woff2", diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index f020a26a23bc2..e1eccb869a585 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -2811,6 +2811,7 @@ fn add_without_unwanted_attributes<'hir>( rust_logo: _, test_attrs: _, no_crate_inject: _, + syntax: _, } = d; let mut attr = DocAttribute::default(); if is_inline { diff --git a/src/librustdoc/externalfiles.rs b/src/librustdoc/externalfiles.rs index 42ade5b90048b..7cc26ffd9d455 100644 --- a/src/librustdoc/externalfiles.rs +++ b/src/librustdoc/externalfiles.rs @@ -1,3 +1,4 @@ +use std::cell::Cell; use std::path::{Path, PathBuf}; use std::{fs, str}; @@ -43,6 +44,8 @@ impl ExternalHtml { content: &m_bc, links: &[], ids: id_map, + contains_mathml: &Cell::new(false), + doc_syntax: None, error_codes: codes, edition, playground, @@ -59,6 +62,8 @@ impl ExternalHtml { content: &m_ac, links: &[], ids: id_map, + contains_mathml: &Cell::new(false), + doc_syntax: None, error_codes: codes, edition, playground, diff --git a/src/librustdoc/html/layout.rs b/src/librustdoc/html/layout.rs index 5db742bdebf70..3c72369edff13 100644 --- a/src/librustdoc/html/layout.rs +++ b/src/librustdoc/html/layout.rs @@ -1,3 +1,4 @@ +use std::cell::Cell; use std::fmt::Display; use std::path::PathBuf; @@ -70,6 +71,7 @@ struct PageLayout<'a> { display_krate_with_trailing_slash: String, display_krate_version_number: &'a str, display_krate_version_extra: &'a str, + contains_mathml: bool, } impl PageLayout<'_> { @@ -87,6 +89,7 @@ pub(crate) fn render( sidebar: S, t: T, style_files: &[StylePath], + contains_mathml: &Cell, ) -> String { let rustdoc_version = rustc_interface::util::version_str!().unwrap_or("unknown version"); @@ -110,6 +113,7 @@ pub(crate) fn render( let content = t.to_string(); // Note: This must happen before making the sidebar. let sidebar = sidebar.to_string(); + let contains_mathml = contains_mathml.get(); PageLayout { static_root_path, page, @@ -124,6 +128,7 @@ pub(crate) fn render( display_krate_version_extra, rust_channel: *crate::clean::utils::RUSTDOC_VERSION, rustdoc_version, + contains_mathml, } .render() .unwrap() diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 2034abdfd1566..ed6892cf8d8a0 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -5,8 +5,11 @@ //! ``` //! #![feature(rustc_private)] //! +//! extern crate rustc_hir; //! extern crate rustc_span; //! +//! use std::cell::Cell; +//! use rustc_hir::attrs::DocAttributeSyntax; //! use rustc_span::edition::Edition; //! use rustdoc::html::markdown::{HeadingOffset, IdMap, Markdown, ErrorCodes}; //! @@ -16,6 +19,8 @@ //! content: s, //! links: &[], //! ids: &mut id_map, +//! contains_mathml: &Cell::new(false), +//! doc_syntax: None, //! error_codes: ErrorCodes::Yes, //! edition: Edition::Edition2015, //! playground: &None, @@ -27,6 +32,7 @@ //! ``` use std::borrow::Cow; +use std::cell::Cell; use std::collections::VecDeque; use std::fmt::{self, Write}; use std::iter::Peekable; @@ -38,6 +44,7 @@ use std::sync::{Arc, Weak}; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_errors::{Diag, DiagMessage}; +use rustc_hir::attrs::DocAttributeSyntax; use rustc_hir::def_id::LocalDefId; use rustc_middle::ty::TyCtxt; pub(crate) use rustc_resolve::rustdoc::main_body_opts; @@ -45,6 +52,7 @@ use rustc_resolve::rustdoc::may_be_doc_link; use rustc_resolve::rustdoc::pulldown_cmark::{ self, BrokenLink, CodeBlockKind, CowStr, Event, LinkType, Options, Parser, Tag, TagEnd, html, }; +use rustc_span::def_id::LOCAL_CRATE; use rustc_span::edition::Edition; use rustc_span::{Span, Symbol}; use tracing::{debug, trace}; @@ -65,12 +73,17 @@ mod tests; const MAX_HEADER_LEVEL: u32 = 6; /// Options for rendering Markdown in summaries (e.g., in search results). -pub(crate) fn summary_opts() -> Options { +pub(crate) fn summary_opts(doc_syntax: Option<&DocAttributeSyntax>) -> Options { Options::ENABLE_TABLES | Options::ENABLE_FOOTNOTES | Options::ENABLE_STRIKETHROUGH | Options::ENABLE_TASKLISTS | Options::ENABLE_SMART_PUNCTUATION + | (if let Some(DocAttributeSyntax { tex_math_dollars: true, .. }) = doc_syntax { + Options::ENABLE_MATH + } else { + Options::empty() + }) } #[derive(Debug, Clone, Copy)] @@ -91,6 +104,10 @@ pub struct Markdown<'a> { pub links: &'a [RenderedLink], /// The current list of used header IDs. pub ids: &'a mut IdMap, + /// Set to `true` if a `$\TeX$` span is found. + pub contains_mathml: &'a Cell, + /// If `$\TeX$` syntax is enabled, this is set. + pub doc_syntax: Option<&'a DocAttributeSyntax>, /// Whether to allow the use of explicit error codes in doctest lang strings. pub error_codes: ErrorCodes, /// Default edition to use when parsing doctests (to add a `fn main`). @@ -105,6 +122,10 @@ pub(crate) struct MarkdownWithToc<'a> { pub(crate) content: &'a str, pub(crate) links: &'a [RenderedLink], pub(crate) ids: &'a mut IdMap, + /// Set to `true` if a `$\TeX$` span is found. + pub(crate) contains_mathml: &'a Cell, + /// If `$\TeX$` syntax is enabled, this is set. + pub(crate) doc_syntax: Option<&'a DocAttributeSyntax>, pub(crate) error_codes: ErrorCodes, pub(crate) edition: Edition, pub(crate) playground: &'a Option, @@ -116,10 +137,19 @@ pub(crate) struct MarkdownItemInfo<'a> { pub(crate) content: &'a str, pub(crate) links: &'a [RenderedLink], pub(crate) ids: &'a mut IdMap, + /// Set to `true` if a `$\TeX$` span is found. + pub(crate) contains_mathml: &'a Cell, + /// If `$\TeX$` syntax is enabled, this is set. + pub(crate) doc_syntax: Option<&'a DocAttributeSyntax>, } /// A tuple struct like `Markdown` that renders only the first paragraph. -pub(crate) struct MarkdownSummaryLine<'a>(pub &'a str, pub &'a [RenderedLink]); +pub(crate) struct MarkdownSummaryLine<'a> { + pub md: &'a str, + pub links: &'a [RenderedLink], + pub contains_mathml: &'a Cell, + pub doc_syntax: Option<&'a DocAttributeSyntax>, +} #[derive(Copy, Clone, PartialEq, Debug)] pub enum ErrorCodes { @@ -628,7 +658,7 @@ fn check_if_allowed_tag(t: &TagEnd) -> bool { | TagEnd::Strong | TagEnd::Strikethrough | TagEnd::Link - | TagEnd::BlockQuote + | TagEnd::BlockQuote(_) ) } @@ -697,6 +727,44 @@ impl<'a, I: Iterator>> Iterator for SummaryLine<'a, I> { } } +struct MathConverter<'c, I> { + p: I, + contains_mathml: &'c Cell, + parser: Option, +} + +impl<'a, 'c, I: Iterator>> Iterator for MathConverter<'c, I> { + type Item = Event<'a>; + fn next(&mut self) -> Option { + let event = self.p.next(); + let mut convert = |latex: &str, display: math_core::MathDisplay| -> Event<'a> { + let parser = if let Some(parser) = self.parser.as_ref() { + parser + } else if let Ok(parser) = + math_core::LatexToMathML::new(math_core::MathCoreConfig::default()) + { + self.parser.insert(parser) + } else { + return Event::Text(latex.to_owned().into()); + }; + if let Ok(result) = parser.convert_with_local_counter(latex, display) { + Event::Html(result.into()) + } else { + Event::Text(latex.to_owned().into()) + } + }; + if let Some(Event::InlineMath(latex)) = event { + self.contains_mathml.set(true); + Some(convert(&latex, math_core::MathDisplay::Inline)) + } else if let Some(Event::DisplayMath(latex)) = event { + self.contains_mathml.set(true); + Some(convert(&latex, math_core::MathDisplay::Block)) + } else { + event + } + } +} + /// A newtype that represents a relative line number in Markdown. /// /// In other words, this represents an offset from the first line of Markdown @@ -735,7 +803,7 @@ pub(crate) fn find_codes( extra_info: Option<&ExtraInfo<'_>>, include_non_rust: bool, ) { - let mut parser = Parser::new_ext(doc, main_body_opts()).into_offset_iter(); + let mut parser = Parser::new_ext(doc, main_body_opts(None)).into_offset_iter(); let mut prev_offset = 0; let mut nb_lines = 0; let mut register_header = None; @@ -1345,6 +1413,8 @@ impl<'a> Markdown<'a> { content: md, links, ids, + contains_mathml, + doc_syntax, error_codes: codes, edition, playground, @@ -1358,7 +1428,8 @@ impl<'a> Markdown<'a> { .map(|link| (link.href.as_str().into(), link.tooltip.as_str().into())) }; - let p = Parser::new_with_broken_link_callback(md, main_body_opts(), Some(replacer)); + let p = + Parser::new_with_broken_link_callback(md, main_body_opts(doc_syntax), Some(replacer)); let p = p.into_offset_iter(); ids.handle_footnotes(|ids, existing_footnotes| { @@ -1366,6 +1437,7 @@ impl<'a> Markdown<'a> { let p = SpannedLinkReplacer::new(p, links); let p = footnotes::Footnotes::new(p, existing_footnotes); let p = TableWrapper::new(p.map(|(ev, _)| ev)); + let p = MathConverter { p, contains_mathml, parser: None }; CodeBlocks::new(p, codes, edition, playground) }) } @@ -1422,8 +1494,16 @@ impl<'a> Markdown<'a> { impl MarkdownWithToc<'_> { pub(crate) fn into_parts(self) -> (Toc, String) { - let MarkdownWithToc { content: md, links, ids, error_codes: codes, edition, playground } = - self; + let MarkdownWithToc { + content: md, + links, + ids, + error_codes: codes, + edition, + playground, + contains_mathml, + doc_syntax, + } = self; // This is actually common enough to special-case if md.is_empty() { @@ -1436,7 +1516,11 @@ impl MarkdownWithToc<'_> { .map(|link| (link.href.as_str().into(), link.tooltip.as_str().into())) }; - let p = Parser::new_with_broken_link_callback(md, main_body_opts(), Some(&mut replacer)); + let p = Parser::new_with_broken_link_callback( + md, + main_body_opts(doc_syntax), + Some(&mut replacer), + ); let p = p.into_offset_iter(); let mut s = String::with_capacity(md.len() * 3 / 2); @@ -1448,6 +1532,7 @@ impl MarkdownWithToc<'_> { let p = footnotes::Footnotes::new(p, existing_footnotes); let p = TableWrapper::new(p.map(|(ev, _)| ev)); let p = CodeBlocks::new(p, codes, edition, playground); + let p = MathConverter { p, contains_mathml, parser: None }; html::push_html(&mut s, p); }); @@ -1461,12 +1546,18 @@ impl MarkdownWithToc<'_> { } impl<'a> MarkdownItemInfo<'a> { - pub(crate) fn new(content: &'a str, links: &'a [RenderedLink], ids: &'a mut IdMap) -> Self { - Self { content, links, ids } + pub(crate) fn new( + content: &'a str, + links: &'a [RenderedLink], + ids: &'a mut IdMap, + contains_mathml: &'a Cell, + doc_syntax: Option<&'a DocAttributeSyntax>, + ) -> Self { + Self { content, links, ids, contains_mathml, doc_syntax } } pub(crate) fn write_into(self, mut f: impl fmt::Write) -> fmt::Result { - let MarkdownItemInfo { content: md, links, ids } = self; + let MarkdownItemInfo { content: md, links, ids, contains_mathml, doc_syntax } = self; // This is actually common enough to special-case if md.is_empty() { @@ -1480,7 +1571,8 @@ impl<'a> MarkdownItemInfo<'a> { .map(|link| (link.href.as_str().into(), link.tooltip.as_str().into())) }; - let p = Parser::new_with_broken_link_callback(md, main_body_opts(), Some(replacer)); + let p = + Parser::new_with_broken_link_callback(md, main_body_opts(doc_syntax), Some(replacer)); let p = p.into_offset_iter(); // Treat inline HTML as plain text. @@ -1494,6 +1586,7 @@ impl<'a> MarkdownItemInfo<'a> { let p = SpannedLinkReplacer::new(p, links); let p = footnotes::Footnotes::new(p, existing_footnotes); let p = TableWrapper::new(p.map(|(ev, _)| ev)); + let p = MathConverter { p, contains_mathml, parser: None }; // in legacy wrap mode, strip

elements to avoid them inserting newlines html::write_html_fmt(&mut f, p)?; @@ -1504,7 +1597,7 @@ impl<'a> MarkdownItemInfo<'a> { impl MarkdownSummaryLine<'_> { pub(crate) fn into_string_with_has_more_content(self) -> (String, bool) { - let MarkdownSummaryLine(md, links) = self; + let MarkdownSummaryLine { md, links, contains_mathml, doc_syntax } = self; // This is actually common enough to special-case if md.is_empty() { return (String::new(), false); @@ -1517,8 +1610,12 @@ impl MarkdownSummaryLine<'_> { .map(|link| (link.href.as_str().into(), link.tooltip.as_str().into())) }; - let p = Parser::new_with_broken_link_callback(md, summary_opts(), Some(&mut replacer)) - .peekable(); + let p = Parser::new_with_broken_link_callback( + md, + summary_opts(doc_syntax), + Some(&mut replacer), + ) + .peekable(); let mut summary = SummaryLine::new(p); let mut s = String::new(); @@ -1527,7 +1624,10 @@ impl MarkdownSummaryLine<'_> { !matches!(event, Event::Start(Tag::Paragraph) | Event::End(TagEnd::Paragraph)) }); - html::push_html(&mut s, without_paragraphs); + html::push_html( + &mut s, + MathConverter { p: without_paragraphs, contains_mathml, parser: None }, + ); let has_more_content = matches!(summary.inner.peek(), Some(Event::Start(_))) || summary.skipped_tags > 0; @@ -1564,7 +1664,7 @@ fn markdown_summary_with_limit( .map(|link| (link.href.as_str().into(), link.tooltip.as_str().into())) }; - let p = Parser::new_with_broken_link_callback(md, summary_opts(), Some(&mut replacer)); + let p = Parser::new_with_broken_link_callback(md, summary_opts(None), Some(&mut replacer)); let mut p = LinkReplacer::new(p, link_names); let mut buf = HtmlWithLimit::new(length_limit); @@ -1645,7 +1745,7 @@ pub(crate) fn plain_text_summary(md: &str, link_names: &[RenderedLink]) -> Strin .map(|link| (link.href.as_str().into(), link.tooltip.as_str().into())) }; - let p = Parser::new_with_broken_link_callback(md, summary_opts(), Some(&mut replacer)); + let p = Parser::new_with_broken_link_callback(md, summary_opts(None), Some(&mut replacer)); plain_text_from_events(p, &mut s); @@ -1725,6 +1825,7 @@ impl MarkdownLinkRange { pub(crate) fn markdown_links<'md, R>( md: &'md str, + doc_syntax: Option<&DocAttributeSyntax>, preprocess_link: impl Fn(MarkdownLink) -> Option, ) -> Vec { use itertools::Itertools; @@ -1879,7 +1980,7 @@ pub(crate) fn markdown_links<'md, R>( let mut broken_link_callback = |link: BrokenLink<'md>| Some((link.reference, "".into())); let event_iter = Parser::new_with_broken_link_callback( md, - main_body_opts(), + main_body_opts(doc_syntax), Some(&mut broken_link_callback), ) .into_offset_iter(); @@ -1911,7 +2012,9 @@ pub(crate) fn markdown_links<'md, R>( span_for_link(&dest_url, span) } } - LinkType::Autolink | LinkType::Email => unreachable!(), + LinkType::Autolink | LinkType::Email | LinkType::WikiLink { .. } => { + unreachable!() + } }; if let Some(link) = preprocess_link(MarkdownLink { @@ -1961,7 +2064,11 @@ pub(crate) fn rust_code_blocks(md: &str, extra_info: &ExtraInfo<'_>) -> Vec { /// Contains information that needs to be saved and reset after rendering an item which is /// not a module. pub(crate) info: ContextInfo, + /// Set to `true` if a `$\TeX$` span is found. + pub(crate) contains_mathml: Cell, } /// This struct contains the information that needs to be reset between each @@ -272,6 +274,7 @@ impl<'tcx> Context<'tcx> { fmt::from_fn(|f| print_sidebar(self, it, f)), content, &self.shared.style_files, + &self.contains_mathml, ) } else { if let Some(&(ref names, ty)) = self.cache().paths.get(&it.item_id.expect_def_id()) @@ -603,6 +606,7 @@ impl<'tcx> Context<'tcx> { shared: scx, types_with_notable_traits: RefCell::new(FxIndexSet::default()), info: ContextInfo::new(include_sources), + contains_mathml: Cell::new(false), }; if emit_crate { @@ -630,6 +634,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { self.deref_id_map.borrow_mut().clear(); self.id_map.borrow_mut().clear(); self.types_with_notable_traits.borrow_mut().clear(); + self.contains_mathml.set(false); self.info } @@ -676,7 +681,14 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { bar.render_into(&mut sidebar).unwrap(); - let v = layout::render(&shared.layout, &page, sidebar, all.print(), &shared.style_files); + let v = layout::render( + &shared.layout, + &page, + sidebar, + all.print(), + &shared.style_files, + &Cell::new(false), + ); shared.fs.write(final_file, v)?; // if to avoid writing help, settings files to doc root unless we're on the final invocation @@ -730,6 +742,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { Ok(()) }), &shared.style_files, + &Cell::new(false), ); shared.fs.write(settings_file, v)?; @@ -761,6 +774,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { ", ), &shared.style_files, + &Cell::new(false), ); shared.fs.write(help_file, v)?; } @@ -775,6 +789,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { "", scrape_examples_help(shared), &shared.style_files, + &Cell::new(false), ); shared.fs.write(scrape_examples_help_file, v)?; } diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 8600fccdbe62c..d078af1aeb2a4 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -41,6 +41,7 @@ mod type_layout; mod write_shared; use std::borrow::Cow; +use std::cell::Cell; use std::collections::VecDeque; use std::fmt::{self, Display as _, Write}; use std::iter::Peekable; @@ -53,7 +54,7 @@ use itertools::Either; use rustc_ast::join_path_syms; use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_hir as hir; -use rustc_hir::attrs::{AttributeKind, DeprecatedSince, Deprecation}; +use rustc_hir::attrs::{AttributeKind, DeprecatedSince, Deprecation, DocAttributeSyntax}; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, DefIdSet}; use rustc_hir::{ConstStability, Mutability, RustcVersion, StabilityLevel, StableSince}; @@ -672,6 +673,8 @@ fn scrape_examples_help(shared: &SharedContext<'_>) -> String { content: &content, links: &[], ids: &mut IdMap::default(), + contains_mathml: &Cell::new(false), + doc_syntax: None, error_codes: shared.codes, edition: shared.edition(), playground: &shared.playground, @@ -707,6 +710,7 @@ fn render_markdown( md_text: &str, links: Vec, heading_offset: HeadingOffset, + doc_syntax: Option<&DocAttributeSyntax>, ) -> impl fmt::Display { fmt::from_fn(move |f| { f.write_str("

")?; @@ -714,6 +718,8 @@ fn render_markdown( content: md_text, links: &links, ids: &mut cx.id_map.borrow_mut(), + contains_mathml: &cx.contains_mathml, + doc_syntax, error_codes: cx.shared.codes, edition: cx.shared.edition(), playground: &cx.shared.playground, @@ -740,8 +746,13 @@ fn document_short( } let s = item.doc_value(); if !s.is_empty() { - let (mut summary_html, has_more_content) = - MarkdownSummaryLine(&s, &item.links(cx)).into_string_with_has_more_content(); + let (mut summary_html, has_more_content) = MarkdownSummaryLine { + md: &s, + links: &item.links(cx), + doc_syntax: cx.tcx().doc_attribute_syntax(item.item_id.expect_def_id()), + contains_mathml: &cx.contains_mathml, + } + .into_string_with_has_more_content(); let link = if has_more_content { let link = fmt::from_fn(|f| { @@ -801,10 +812,26 @@ fn document_full_inner( \ Expand description\ {}", - render_markdown(cx, &s, item.links(cx), heading_offset) + render_markdown( + cx, + &s, + item.links(cx), + heading_offset, + cx.tcx().doc_attribute_syntax(item.item_id.expect_def_id()) + ) )?; } else { - write!(f, "{}", render_markdown(cx, &s, item.links(cx), heading_offset))?; + write!( + f, + "{}", + render_markdown( + cx, + &s, + item.links(cx), + heading_offset, + cx.tcx().doc_attribute_syntax(item.item_id.expect_def_id()) + ) + )?; } } @@ -903,7 +930,13 @@ fn short_item_info( let note = note.as_str(); let mut id_map = cx.id_map.borrow_mut(); let links = item.links(cx); - let html = MarkdownItemInfo::new(note, &links, &mut id_map); + let html = MarkdownItemInfo::new( + note, + &links, + &mut id_map, + &cx.contains_mathml, + cx.tcx().doc_attribute_syntax(item.item_id.expect_def_id()), + ); message.push_str(": "); html.write_into(&mut message).unwrap(); } @@ -2210,6 +2243,10 @@ fn render_impl( content: &dox, links: &i.impl_item.links(cx), ids: &mut cx.id_map.borrow_mut(), + contains_mathml: &cx.contains_mathml, + doc_syntax: cx + .tcx() + .doc_attribute_syntax(i.impl_item.item_id.expect_def_id()), error_codes: cx.shared.codes, edition: cx.shared.edition(), playground: &cx.shared.playground, diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 48108097864a4..73330bca32789 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -447,8 +447,15 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i }; let visibility_and_hidden = visibility_and_hidden(myitem); - let docs = MarkdownSummaryLine(&myitem.doc_value(), &myitem.links(cx)) - .into_string(); + let docs = MarkdownSummaryLine { + md: &myitem.doc_value(), + links: &myitem.links(cx), + doc_syntax: cx + .tcx() + .doc_attribute_syntax(myitem.item_id.expect_def_id()), + contains_mathml: &cx.contains_mathml, + } + .into_string(); let (docs_before, docs_after) = if docs.is_empty() { ("", "") } else { ("
", "
") }; let deprecation_attr = deprecation_class_attr(myitem.is_deprecated(tcx)); diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs index 360f8bdf642e2..20ed6db05bc87 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -231,6 +231,8 @@ fn docblock_toc<'a>( error_codes: cx.shared.codes, edition: cx.shared.edition(), playground: &cx.shared.playground, + contains_mathml: &cx.contains_mathml, + doc_syntax: cx.tcx().doc_attribute_syntax(it.item_id.expect_def_id()), } .into_parts(); let links: Vec> = toc diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index 8de899ea0eef9..c24773f54f84a 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -13,7 +13,7 @@ //! --resource-suffix flag and are emitted when --emit-type is empty (default) //! or contains "invocation-specific". -use std::cell::RefCell; +use std::cell::{Cell, RefCell}; use std::cmp::Ordering; use std::ffi::{OsStr, OsString}; use std::fs::File; @@ -421,7 +421,7 @@ impl CratesIndexPart {
\
    {DELIMITER}
" ); - let template = layout::render(layout, &page, "", content, style_files); + let template = layout::render(layout, &page, "", content, style_files, &Cell::new(false)); SortedTemplate::from_template(&template, DELIMITER) .expect("Object Replacement Character (U+FFFC) should not appear in the --index-page") } diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs index dda9b7c55351c..2424abbb6386e 100644 --- a/src/librustdoc/html/sources.rs +++ b/src/librustdoc/html/sources.rs @@ -1,4 +1,4 @@ -use std::cell::RefCell; +use std::cell::{Cell, RefCell}; use std::ffi::OsStr; use std::path::{Component, Path, PathBuf}; use std::{fmt, fs}; @@ -257,6 +257,7 @@ impl SourceCollector<'_, '_> { ) }), &shared.style_files, + &Cell::new(false), ); shared.fs.write(cur, v)?; self.emitted_local_sources.insert(p); diff --git a/src/librustdoc/html/static/COPYRIGHT.txt b/src/librustdoc/html/static/COPYRIGHT.txt index 0c295b4b01680..6c065e08ceb38 100644 --- a/src/librustdoc/html/static/COPYRIGHT.txt +++ b/src/librustdoc/html/static/COPYRIGHT.txt @@ -61,6 +61,30 @@ included, and carry their own copyright notices and license terms: Licensed under the SIL Open Font License, Version 1.1. See NanumBarunGothic-LICENSE.txt. +* Modified Noto Sans Math (ModifiedNotoSansMath-Regular.woff2) + + This Font Software is licensed under the SIL Open Font License, Version 1.1. + This license is available with a FAQ at https://scripts.sil.org/OFL + See ModifiedNotoSansMath-LICENSE.txt + + https://github.com/notofonts/math/ + + Modified for use with math-core by tmke8. + + https://github.com/tmke8/math-core-fonts/tree/main/NotoSansMath + +* mathml.css + + MIT License + + Copyright 2020 Hiromu Sugiura + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * Rust logos (rust-logo.svg, favicon.svg, favicon-32x32.png) Copyright 2025 Rust Foundation. diff --git a/src/librustdoc/html/static/css/mathml.css b/src/librustdoc/html/static/css/mathml.css new file mode 100644 index 0000000000000..1721e073129c0 --- /dev/null +++ b/src/librustdoc/html/static/css/mathml.css @@ -0,0 +1,82 @@ +/* See ModifiedNotoSansMath-LICENSE.txt for the Noto Sans license. */ +@font-face { + font-family: 'Modified Noto Sans Math'; + font-style: normal; + font-weight: 400; + src: url("ModifiedNotoSansMath-Regular-3dd429ed.woff2") format("woff2"); + font-display: swap; +} +math { + font-family: 'Modified Noto Sans Math', math; +} +mtext { + font-family: var(--font-family); +} + +/* https://github.com/tmke8/math-core/blob/main/css/mathmlfixes.css */ + +/* Styles for all browsers */ + +mtd { + /* This is the default padding for mtd elements as defined in the MathML Core specification. */ + padding: 0.5ex 0.4em; +} + +mtr > mtd:first-child { + padding-left: 0; +} + +mtr > mtd:last-child { + padding-right: 0; +} + +/* Styles for Chromium only */ +@supports (not (-webkit-backdrop-filter: blur(1px))) and + (not (-moz-appearance: none)) { + menclose { + position: relative; + padding: 0.5ex 0; + } + mrow.menclose-updiagonalstrike, + mrow.menclose-downdiagonalstrike, + mrow.menclose-horizontalstrike { + display: inline-block; + position: absolute; + left: 0.5px; + bottom: 0; + width: 100%; + height: 100%; + background-color: currentcolor; + } + mrow.menclose-updiagonalstrike { + clip-path: polygon( + 0.05em 100%, + 0 calc(100% - 0.05em), + calc(100% - 0.05em) 0, + 100% 0.05em + ); + } + mrow.menclose-downdiagonalstrike { + clip-path: polygon( + 0em 0.05em, + 0.05em 0em, + 100% calc(100% - 0.05em), + calc(100% - 0.05em) 100% + ); + } + mrow.menclose-horizontalstrike { + clip-path: polygon( + 0em calc(55% + 0.0333em), + 0em calc(55% - 0.0333em), + 100% calc(55% - 0.0333em), + 100% calc(55% + 0.0333em) + ); + } +} + +/* Styles for Chromium and Safari */ +@supports (not (-moz-appearance: none)) { + mover[accent="true" i] > mo:nth-child(2) { + margin-bottom: 0.15ex; + } +} diff --git a/src/librustdoc/html/static/fonts/ModifiedNotoSansMath-LICENSE.txt b/src/librustdoc/html/static/fonts/ModifiedNotoSansMath-LICENSE.txt new file mode 100644 index 0000000000000..8d0233915288c --- /dev/null +++ b/src/librustdoc/html/static/fonts/ModifiedNotoSansMath-LICENSE.txt @@ -0,0 +1,93 @@ +Copyright 2022 The Noto Project Authors (https://github.com/notofonts/math) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/src/librustdoc/html/static/fonts/ModifiedNotoSansMath-Regular.woff2 b/src/librustdoc/html/static/fonts/ModifiedNotoSansMath-Regular.woff2 new file mode 100644 index 0000000000000..7753c73ba0ee5 Binary files /dev/null and b/src/librustdoc/html/static/fonts/ModifiedNotoSansMath-Regular.woff2 differ diff --git a/src/librustdoc/html/static/fonts/README.txt b/src/librustdoc/html/static/fonts/README.txt index 0db15996d2ec1..7f7f34cbb38bc 100644 --- a/src/librustdoc/html/static/fonts/README.txt +++ b/src/librustdoc/html/static/fonts/README.txt @@ -10,3 +10,11 @@ The font files were generated with these commands: pyftsubset NanumBarunGothic.ttf \ --unicodes=U+AC00-D7AF:U+1100-11FF,U+3130-318F,U+A960-A97F,U+D7B0-D7FF \ --output-file=NanumBarunGothic.ttf.woff2 --flavor=woff2 + +------------------------------------------------------------------------------- + +The "Modified Noto Sans Math" file comes +from . Some of the glyphs are swapped +and repositioned to work around bugs in browser MathML implementations. +The original version of the font can be found +at . diff --git a/src/librustdoc/html/static_files.rs b/src/librustdoc/html/static_files.rs index 84d0162d990b5..4f719669a686f 100644 --- a/src/librustdoc/html/static_files.rs +++ b/src/librustdoc/html/static_files.rs @@ -78,6 +78,7 @@ static_files! { rustdoc_css => "static/css/rustdoc.css", noscript_css => "static/css/noscript.css", normalize_css => "static/css/normalize.css", + mathml_css => "static/css/mathml.css", main_js => "static/js/main.js", search_js => "static/js/search.js", stringdex_js => "static/js/stringdex.js", @@ -109,6 +110,8 @@ static_files! { source_code_pro_license => "static/fonts/SourceCodePro-LICENSE.txt", nanum_barun_gothic_regular => "static/fonts/NanumBarunGothic.ttf.woff2", nanum_barun_gothic_license => "static/fonts/NanumBarunGothic-LICENSE.txt", + modified_noto_sans_math_regular => "static/fonts/ModifiedNotoSansMath-Regular.woff2", + modified_noto_sans_math_license => "static/fonts/ModifiedNotoSansMath-LICENSE.txt", } pub(crate) static SCRAPE_EXAMPLES_HELP_MD: &str = include_str!("static/scrape-examples-help.md"); diff --git a/src/librustdoc/html/templates/page.html b/src/librustdoc/html/templates/page.html index 427e7b4071a1e..1c4894cb74c2c 100644 --- a/src/librustdoc/html/templates/page.html +++ b/src/librustdoc/html/templates/page.html @@ -7,12 +7,16 @@ {# #} {{page.title}} {# #} {# #} {# #} + {% if contains_mathml %} + + {% endif %} {% if !layout.default_settings.is_empty() %}