├── .gitattributes ├── .github └── workflows │ └── book.yml ├── .gitignore ├── .nojekyll ├── Makefile ├── README.md ├── SUMMARY.md ├── appendix └── readme.md ├── book.toml ├── cover-full.drawio ├── cover-full.png ├── index.md ├── intro └── readme.md ├── js ├── bigPicture.js └── custom.js ├── open-source ├── KCLVM │ └── readme.md └── readme.md ├── preface.md ├── rust-tools ├── cargo │ └── readme.md ├── clippy │ └── readme.md └── readme.md ├── rustc ├── codegen │ └── readme.md ├── general │ ├── errors │ │ ├── images │ │ │ ├── error-01.png │ │ │ ├── error-02.png │ │ │ ├── error-03.png │ │ │ ├── error-04.png │ │ │ ├── error-05.png │ │ │ ├── error-06.png │ │ │ ├── error-07.png │ │ │ ├── error-08.png │ │ │ ├── error-09.png │ │ │ ├── error-10.png │ │ │ ├── error-11.png │ │ │ ├── error-12.png │ │ │ └── error-13.png │ │ └── readme.md │ ├── readme.md │ └── sourcemap-span │ │ └── readme.md ├── invocation │ └── readme.md ├── lexer │ └── readme.md ├── overview │ └── readme.md ├── parser │ ├── ast │ │ ├── ast.md │ │ ├── readme.md │ │ └── visitor.md │ ├── early-lint │ │ └── readme.md │ └── readme.md ├── readme.md └── sema │ ├── hir-lowering │ ├── readme.md │ ├── trait-solving │ │ └── readme.md │ ├── type-checking │ │ └── readme.md │ └── type-inference │ │ └── readme.md │ ├── late-lint │ └── readme.md │ ├── lint │ ├── 1.lint-pass.md │ ├── 2.combinedlintpass.md │ ├── 3.Execution process.md │ ├── images │ │ ├── combinedlintpass.jpg │ │ ├── complication_process.jpg │ │ ├── early_lint_node.jpg │ │ ├── lint_lintpass.jpeg │ │ └── linter.jpg │ └── readme.md │ ├── mir-lowering │ ├── borrow-check │ │ └── readme.md │ ├── mir-optimized │ │ └── readme.md │ └── readme.md │ ├── readme.md │ └── resolver │ └── readme.md ├── stdlib ├── readme.md └── sort │ └── readme.md ├── style.css ├── theme └── index.hbs └── wechat.png /.gitattributes: -------------------------------------------------------------------------------- 1 | *.md linguist-language=rust 2 | *.hbs linguist-detectable=false 3 | *.js linguist-detectable=false 4 | *.css linguist-detectable=false -------------------------------------------------------------------------------- /.github/workflows/book.yml: -------------------------------------------------------------------------------- 1 | name: mdbook 2 | on: 3 | push: 4 | branches: 5 | - main 6 | - master 7 | - "releases/*" 8 | pull_request: 9 | branches: 10 | - main 11 | - master 12 | types: 13 | - closed 14 | permissions: 15 | contents: write 16 | jobs: 17 | deploy: 18 | runs-on: ubuntu-22.04 19 | steps: 20 | - name: Git checkout 21 | uses: actions/checkout@v2 22 | 23 | - name: Setup mdBook 24 | uses: peaceiris/actions-mdbook@v1 25 | with: 26 | mdbook-version: '0.4.10' 27 | 28 | - run: mdbook build 29 | 30 | - name: Deploy 31 | uses: peaceiris/actions-gh-pages@v3 32 | with: 33 | personal_token: ${{ secrets.DEPLOY_KEY }} 34 | publish_dir: ./docs 35 | publish_branch: gh-pages 36 | user_name: 'github-actions[bot]' 37 | user_email: 'github-actions[bot]@users.noreply.github.com' 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-kusion/rust-code-book/7cf5b600b88117f641b38a1ec45a7c8a9c5d7f59/.gitignore -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: 2 | mdbook serve 3 | 4 | build: 5 | -rm -r -f docs 6 | mdbook build 7 | -rm docs/.gitignore 8 | -rm -rf docs/.git 9 | 10 | clean: -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust Source Code Analysis 2 | 3 | [English](https://github.com/awesome-kusion/rust-code-book) [简体中文](https://github.com/awesome-kusion/rust-code-book-zh) 4 | 5 | - *KusionStack(Go): * 6 | - *KCLVM(Rust): * 7 | 8 | ## Preface 9 | 10 | When I was working on KusionStack and KCLVM projects, I started to learn the source code of Rustc with the idea of learning the design of excellent compiler. I took some notes and documents during this process. With the suggestion of [Chai](https://github.com/chai2010), I organized them into an article and posted it. I didn't expect people to be interested in it, so I decided to continue writing articles on Rustc source code analysis. This is the original intention of writing this e-book. 11 | 12 | KCLVM is a compiler we developed with Rust in the project of Kusion, and some parts of this book are applied to KCLVM. If you are interested in the cloud-native ecology and technology, you can learn about the project 👉 [KusionStack](https://github.com/KusionStack/kusion). And if you are interested in Rust, programming languages or compilers, maybe you prefer 👉 [KCLVM]( https://github.com/KusionStack/KCLVM). 13 | 14 | Finally, most of the content in these e-book are my personal understanding when reading the source code, as well as some descriptions in the [rust-dev-guide](https://rustc-dev-guide.rust-lang.org/about-this-guide.html). Owing to the limitation of my knowledge, there must be mistakes and errors in the book. So we welcome all forms of conrtibutions(fix typo/polish english/translation/write article or other) from everyone. 15 | 16 | --- 17 | 18 | ## E-book 19 | 20 | Target: Analysis and learn from the source code of Rust standard library, Rust compiler(Rustc) and open source project written by Rust. 21 | 22 | ![cover](cover-full.png) 23 | 24 | - Github Repo: [https://github.com/awesome-kusion/rust-code-book](https://github.com/awesome-kusion/rust-code-book) 25 | - Read Online: [https://awesome-kusion.github.io/rust-code-book](https://awesome-kusion.github.io/rust-code-book) 26 | 27 | ## Catalog 28 | 29 | [Preface](preface.md) 30 | 31 | - [Intro](intro/readme.md) 32 | - [Standard Library](stdlib/readme.md) 33 | - [Sort: Timsort and pdqsort[WIP: Zh-ver.]](stdlib/sort/readme.md) 🕒 34 | - [Rust Compiler](rustc/readme.md) 35 | - [Overview](rustc/overview/readme.md) 36 | - [Invocation](rustc/invocation/readme.md) 37 | - [Lexer](rustc/lexer/readme.md) 38 | - [Parser](rustc/parser/readme.md) 39 | 43 | - [Sema](rustc/sema/readme.md) 44 | - [Lint](rustc/sema/lint/readme.md) ✅ 45 | 48 | 57 | - [Codegen](rustc/codegen/readme.md) 58 | - [General](rustc/general/readme.md) 59 | - [Errors[WIP: Zh-ver.]](rustc/general/errors/readme.md) 🕒 60 | - [SourceMap & Span[WIP]](rustc/general/sourcemap-span/readme.md) 🕒 61 | 62 | - [Rust Tools](rust-tools/readme.md) 63 | 65 | 66 | - [Rust Open Source Project](open-source/readme.md) 67 | - [KCLVM](open-source/KCLVM/readme.md) ✅ 68 | - [Appendix](appendix/readme.md) 69 | 70 | --- 71 | 72 | [![Star History Chart](https://api.star-history.com/svg?repos=awesome-kusion/rust-code-book&type=Date)](https://star-history.com/#awesome-kusion/rust-code-book&Date) 73 | 74 | 75 | Welcome to contact us in following ways: 76 | - Slack: https://join.slack.com/t/rustcodebook/shared_invite/zt-1htuqzkf9-45i6FO1L~ihwnmbGGh7QEw 77 | - Twitter: [He1pa](https://twitter.com/ZhengZh79945795) 78 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | [Rust Source Code Analysis](index.md) 2 | [Preface](preface.md) 3 | 4 | - [Intro](intro/readme.md) 5 | - [Standard Library](stdlib/readme.md) 6 | - [Sort: Timsort and pdqsort[WIP: Zh-ver.]](stdlib/sort/readme.md) 7 | - [Rust Compiler](rustc/readme.md) 8 | - [Overview](rustc/overview/readme.md) 9 | - [Invocation](rustc/invocation/readme.md) 10 | - [Lexer](rustc/lexer/readme.md) 11 | - [Parser](rustc/parser/readme.md) 12 | 16 | - [Sema](rustc/sema/readme.md) 17 | - [Lint](rustc/sema/lint/readme.md) 18 | 21 | 30 | - [Codegen](rustc/codegen/readme.md) 31 | - [General](rustc/general/readme.md) 32 | - [Errors[WIP: Zh-ver.]](rustc/general/errors/readme.md) 33 | - [SourceMap & Span[WIP]](rustc/general/sourcemap-span/readme.md) 34 | 35 | - [Rust Tools](rust-tools/readme.md) 36 | 38 | 39 | - [Rust Open Source Project](open-source/readme.md) 40 | - [KCLVM](open-source/KCLVM/readme.md) 41 | - [Appendix](appendix/readme.md) 42 | -------------------------------------------------------------------------------- /appendix/readme.md: -------------------------------------------------------------------------------- 1 | # 附录 -------------------------------------------------------------------------------- /book.toml: -------------------------------------------------------------------------------- 1 | # https://giscus.app 2 | # https://github.com/badboy/mdbook-mermaid 3 | 4 | [book] 5 | title = "Rust Source Code Analysis" 6 | authors = ["Rust爱好者"] 7 | description = "通过分析代码学习Rust" 8 | language = "zh" 9 | multilingual = false 10 | src = "." 11 | 12 | [build] 13 | build-dir = "docs" 14 | 15 | [output.html] 16 | additional-css = ["style.css"] 17 | additional-js = ["js/custom.js", "js/bigPicture.js"] 18 | git-repository-url = "https://github.com/awesome-kusion/rust-code-book" 19 | edit-url-template = "https://github.com/awesome-kusion/rust-code-book/edit/main/{path}" 20 | git-repository-icon = "fa-github" 21 | -------------------------------------------------------------------------------- /cover-full.drawio: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /cover-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-kusion/rust-code-book/7cf5b600b88117f641b38a1ec45a7c8a9c5d7f59/cover-full.png -------------------------------------------------------------------------------- /index.md: -------------------------------------------------------------------------------- 1 | # Rust Source Code Analysis 2 | 3 | Target: Analysis and learn from the source code of Rust standard library, Rust compiler(Rustc) and open source project written by Rust. 4 | 5 | ![cover](cover-full.png) 6 | 7 | - Github Repo: [https://github.com/awesome-kusion/rust-code-book](https://github.com/awesome-kusion/rust-code-book) 8 | - Read Online: [https://awesome-kusion.github.io/rust-code-book](https://awesome-kusion.github.io/rust-code-book) 9 | -------------------------------------------------------------------------------- /intro/readme.md: -------------------------------------------------------------------------------- 1 | # 简介 -------------------------------------------------------------------------------- /js/bigPicture.js: -------------------------------------------------------------------------------- 1 | var BigPicture=function(){var t,n,e,o,i,r,a,c,p,s,l,d,u,f,m,b,g,h,x,v,y,w,_,T,k,M,S,L,E,A,H,z,I,C=[],D={},O="appendChild",N="createElement",V="removeChild";function W(){var n=t.getBoundingClientRect();return"transform:translate3D("+(n.left-(e.clientWidth-n.width)/2)+"px, "+(n.top-(e.clientHeight-n.height)/2)+"px, 0) scale3D("+t.clientWidth/o.clientWidth+", "+t.clientHeight/o.clientHeight+", 0)"}function q(t){var n=A.length-1;if(!u){if(t>0&&E===n||t<0&&!E){if(!I.loop)return j(i,""),void setTimeout(j,9,i,"animation:"+(t>0?"bpl":"bpf")+" .3s;transition:transform .35s");E=t>0?-1:n+1}if([(E=Math.max(0,Math.min(E+t,n)))-1,E,E+1].forEach(function(t){if(t=Math.max(0,Math.min(t,n)),!D[t]){var e=A[t].src,o=document[N]("IMG");o.addEventListener("load",F.bind(null,e)),o.src=e,D[t]=o}}),D[E].complete)return B(t);u=1,j(m,"opacity:.4;"),e[O](m),D[E].onload=function(){y&&B(t)},D[E].onerror=function(){A[E]={error:"Error loading image"},y&&B(t)}}}function B(n){u&&(e[V](m),u=0);var r=A[E];if(r.error)alert(r.error);else{var a=e.querySelector("img:last-of-type");j(i=o=D[E],"animation:"+(n>0?"bpfl":"bpfr")+" .35s;transition:transform .35s"),j(a,"animation:"+(n>0?"bpfol":"bpfor")+" .35s both"),e[O](i),r.el&&(t=r.el)}H.innerHTML=E+1+"/"+A.length,X(A[E].caption),M&&M([i,A[E]])}function P(){var t,n,e=.95*window.innerHeight,o=.95*window.innerWidth,i=I.dimensions||[1920,1080],r=i[0],a=i[1],p=a/r;p>e/o?n=(t=Math.min(a,e))/p:t=(n=Math.min(r,o))*p,c.style.cssText+="width:"+n+"px;height:"+t+"px;"}function G(t){~[1,4].indexOf(o.readyState)?(U(),setTimeout(function(){o.play()},99)):o.error?U(t):f=setTimeout(G,35,t)}function R(n){I.noLoader||(n&&j(m,"top:"+t.offsetTop+"px;left:"+t.offsetLeft+"px;height:"+t.clientHeight+"px;width:"+t.clientWidth+"px"),t.parentElement[n?O:V](m),u=n)}function X(t){t&&(g.innerHTML=t),j(b,"opacity:"+(t?"1;pointer-events:auto":"0"))}function F(t){!~C.indexOf(t)&&C.push(t)}function U(t){if(u&&R(),T&&T(),"string"==typeof t)return $(),I.onError?I.onError():alert("Error: The requested "+t+" could not be loaded.");_&&F(s),o.style.cssText+=W(),j(e,"opacity:1;pointer-events:auto"),k=setTimeout(k,410),v=1,y=!!A,setTimeout(function(){o.style.cssText+="transition:transform .35s;transform:none",h&&setTimeout(X,250,h)},60)}function Y(t){var n=t?t.target:e,i=[b,x,r,a,g,L,S,m];n.blur(),w||~i.indexOf(n)||(o.style.cssText+=W(),j(e,"pointer-events:auto"),setTimeout($,350),clearTimeout(k),v=0,w=1)}function $(){if((o===c?p:o).removeAttribute("src"),document.body[V](e),e[V](o),j(e,""),j(o,""),X(0),y){for(var t=e.querySelectorAll("img"),n=0;n',n}function d(t,n){var e=document[N]("button");return e.className="bp-lr",e.innerHTML='',j(e,n),e.onclick=function(n){n.stopPropagation(),q(t)},e}var f=document[N]("STYLE");f.innerHTML="#bp_caption,#bp_container{bottom:0;left:0;right:0;position:fixed;opacity:0}#bp_container>*,#bp_loader{position:absolute;right:0;z-index:10}#bp_container,#bp_caption,#bp_container svg{pointer-events:none}#bp_container{top:0;z-index:9999;background:rgba(0,0,0,.7);opacity:0;transition:opacity .35s}#bp_loader{top:0;left:0;bottom:0;display:flex;align-items:center;cursor:wait;background:0;z-index:9}#bp_loader svg{width:50%;max-width:300px;max-height:50%;margin:auto;animation:bpturn 1s infinite linear}#bp_aud,#bp_container img,#bp_sv,#bp_vid{user-select:none;max-height:96%;max-width:96%;top:0;bottom:0;left:0;margin:auto;box-shadow:0 0 3em rgba(0,0,0,.4);z-index:-1}#bp_sv{background:#111}#bp_sv svg{width:66px}#bp_caption{font-size:.9em;padding:1.3em;background:rgba(15,15,15,.94);color:#fff;text-align:center;transition:opacity .3s}#bp_aud{width:650px;top:calc(50% - 20px);bottom:auto;box-shadow:none}#bp_count{left:0;right:auto;padding:14px;color:rgba(255,255,255,.7);font-size:22px;cursor:default}#bp_container button{position:absolute;border:0;outline:0;background:0;cursor:pointer;transition:all .1s}#bp_container>.bp-x{padding:0;height:41px;width:41px;border-radius:100%;top:8px;right:14px;opacity:.8;line-height:1}#bp_container>.bp-x:focus,#bp_container>.bp-x:hover{background:rgba(255,255,255,.2)}.bp-x svg,.bp-xc svg{height:21px;width:20px;fill:#fff;vertical-align:top;}.bp-xc svg{width:16px}#bp_container .bp-xc{left:2%;bottom:100%;padding:9px 20px 7px;background:#d04444;border-radius:2px 2px 0 0;opacity:.85}#bp_container .bp-xc:focus,#bp_container .bp-xc:hover{opacity:1}.bp-lr{top:50%;top:calc(50% - 130px);padding:99px 0;width:6%;background:0;border:0;opacity:.4;transition:opacity .1s}.bp-lr:focus,.bp-lr:hover{opacity:.8}@keyframes bpf{50%{transform:translatex(15px)}100%{transform:none}}@keyframes bpl{50%{transform:translatex(-15px)}100%{transform:none}}@keyframes bpfl{0%{opacity:0;transform:translatex(70px)}100%{opacity:1;transform:none}}@keyframes bpfr{0%{opacity:0;transform:translatex(-70px)}100%{opacity:1;transform:none}}@keyframes bpfol{0%{opacity:1;transform:none}100%{opacity:0;transform:translatex(-70px)}}@keyframes bpfor{0%{opacity:1;transform:none}100%{opacity:0;transform:translatex(70px)}}@keyframes bpturn{0%{transform:none}100%{transform:rotate(360deg)}}@media (max-width:600px){.bp-lr{font-size:15vw}}",document.head[O](f),(e=document[N]("DIV")).id="bp_container",e.onclick=Y,l=s("bp-x"),e[O](l),"ontouchstart"in window&&(z=1,e.ontouchstart=function(n){var e=n.changedTouches;t=e[0].pageX},e.ontouchmove=function(t){t.preventDefault()},e.ontouchend=function(n){var e=n.changedTouches;if(y){var o=e[0].pageX-t;o<-30&&q(1),o>30&&q(-1)}}),i=document[N]("IMG"),(r=document[N]("VIDEO")).id="bp_vid",r.setAttribute("playsinline",1),r.controls=1,r.loop=1,(a=document[N]("audio")).id="bp_aud",a.controls=1,a.loop=1,(H=document[N]("span")).id="bp_count",(b=document[N]("DIV")).id="bp_caption",(x=s("bp-xc")).onclick=X.bind(null,0),b[O](x),g=document[N]("SPAN"),b[O](g),e[O](b),S=d(1,"transform:scalex(-1)"),L=d(-1,"left:0;right:auto"),(m=document[N]("DIV")).id="bp_loader",m.innerHTML='',(c=document[N]("DIV")).id="bp_sv",(p=document[N]("IFRAME")).setAttribute("allowfullscreen",1),p.allow="autoplay; fullscreen",p.onload=function(){return c[V](m)},j(p,"border:0;position:absolute;height:100%;width:100%;left:0;top:0"),c[O](p),i.onload=U,i.onerror=U.bind(null,"image"),window.addEventListener("resize",function(){y||u&&R(1),o===c&&P()}),document.addEventListener("keyup",function(t){var n=t.keyCode;27===n&&v&&Y(),y&&(39===n&&q(1),37===n&&q(-1),38===n&&q(10),40===n&&q(-10))}),document.addEventListener("keydown",function(t){y&&~[37,38,39,40].indexOf(t.keyCode)&&t.preventDefault()}),document.addEventListener("focus",function(t){v&&!e.contains(t.target)&&(t.stopPropagation(),l.focus())},1),n=1}(),u&&(clearTimeout(f),$()),I=w,d=w.ytSrc||w.vimeoSrc,T=w.animationStart,k=w.animationEnd,M=w.onChangeImage,_=0,h=(t=w.el).getAttribute("data-caption"),w.gallery?function(n,r){var a=I.galleryAttribute||"data-bp";if(Array.isArray(n))A=n,h=n[E=r||0].caption;else{var c=(A=[].slice.call("string"==typeof n?document.querySelectorAll(n+" ["+a+"]"):n)).indexOf(t);E=0===r||r?r:-1!==c?c:0,A=A.map(function(t){return{el:t,src:t.getAttribute(a),caption:t.getAttribute("data-caption")}})}_=1,!~C.indexOf(s=A[E].src)&&R(1),A.length>1?(e[O](H),H.innerHTML=E+1+"/"+A.length,z||(e[O](S),e[O](L))):A=0,(o=i).src=s}(w.gallery,w.position):d||w.iframeSrc?(o=c,I.ytSrc?W="https://www.youtube.com/embed/"+d+"?html5=1&rel=0&playsinline=1&autoplay=1":I.vimeoSrc?W="https://player.vimeo.com/video/"+d+"?autoplay=1":I.iframeSrc&&(W=I.iframeSrc),j(m,""),c[O](m),p.src=W,P(),setTimeout(U,9)):w.imgSrc?(_=1,!~C.indexOf(s=w.imgSrc)&&R(1),(o=i).src=s):w.audio?(R(1),(o=a).src=w.audio,G("audio file")):w.vidSrc?(R(1),w.dimensions&&j(r,"width:"+w.dimensions[0]+"px"),D=w.vidSrc,Array.isArray(D)?(o=r.cloneNode(),D.forEach(function(t){var n=document[N]("SOURCE");n.src=t,n.type="video/"+t.match(/.(\w+)$/)[1],o[O](n)})):(o=r).src=D,G("video")):(o=i).src="IMG"===t.tagName?t.src:window.getComputedStyle(t).backgroundImage.replace(/^url|[(|)|'|"]/g,""),e[O](o),document.body[O](e),{close:Y,next:function(){return q(1)},prev:function(){return q(-1)}};var W}}(); 2 | -------------------------------------------------------------------------------- /js/custom.js: -------------------------------------------------------------------------------- 1 | // https://giscus.app 2 | 3 | const data_repo = "awesome-kusion/rust-code-book"; 4 | const data_repo_id = "R_kgDOH0lTxA"; 5 | const data_category = "General"; 6 | const data_category_id = "DIC_kwDOH0lTxM4CQ1T0"; 7 | 8 | var initAll = function () { 9 | var path = window.location.pathname; 10 | if (path.endsWith("/print.html")) { 11 | return; 12 | } 13 | 14 | var images = document.querySelectorAll("main img") 15 | Array.prototype.forEach.call(images, function (img) { 16 | img.addEventListener("click", function () { 17 | BigPicture({ 18 | el: img, 19 | }); 20 | }); 21 | }); 22 | 23 | // Un-active everything when you click it 24 | Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function (el) { 25 | el.addEventHandler("click", function () { 26 | Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function (el) { 27 | el.classList.remove("active"); 28 | }); 29 | el.classList.add("active"); 30 | }); 31 | }); 32 | 33 | var updateFunction = function () { 34 | var id = null; 35 | var elements = document.getElementsByClassName("header"); 36 | Array.prototype.forEach.call(elements, function (el) { 37 | if (window.pageYOffset >= el.offsetTop) { 38 | id = el; 39 | } 40 | }); 41 | 42 | Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function (el) { 43 | el.classList.remove("active"); 44 | }); 45 | 46 | Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function (el) { 47 | if (id == null) { 48 | return; 49 | } 50 | if (id.href.localeCompare(el.href) == 0) { 51 | el.classList.add("active"); 52 | } 53 | }); 54 | }; 55 | 56 | var pagetoc = document.getElementsByClassName("pagetoc")[0]; 57 | var elements = document.getElementsByClassName("header"); 58 | Array.prototype.forEach.call(elements, function (el) { 59 | var link = document.createElement("a"); 60 | 61 | // Indent shows hierarchy 62 | var indent = ""; 63 | switch (el.parentElement.tagName) { 64 | case "H1": 65 | return; 66 | case "H3": 67 | indent = "20px"; 68 | break; 69 | case "H4": 70 | indent = "40px"; 71 | break; 72 | default: 73 | break; 74 | } 75 | 76 | link.appendChild(document.createTextNode(el.text)); 77 | link.style.paddingLeft = indent; 78 | link.href = el.href; 79 | pagetoc.appendChild(link); 80 | }); 81 | updateFunction.call(); 82 | 83 | // Handle active elements on scroll 84 | window.addEventListener("scroll", updateFunction); 85 | 86 | document.getElementById("theme-list").addEventListener("click", function (e) { 87 | var iframe = document.querySelector('.giscus-frame'); 88 | if (!iframe) return; 89 | var theme; 90 | if (e.target.className === "theme") { 91 | theme = e.target.id; 92 | } else { 93 | return; 94 | } 95 | 96 | // 若当前 mdbook 主题不是 Light 或 Rust ,则将 giscuz 主题设置为 transparent_dark 97 | var giscusTheme = "light" 98 | if (theme != "light" && theme != "rust") { 99 | giscusTheme = "transparent_dark"; 100 | } 101 | 102 | var msg = { 103 | setConfig: { 104 | theme: giscusTheme 105 | } 106 | }; 107 | iframe.contentWindow.postMessage({ giscus: msg }, 'https://giscus.app'); 108 | }); 109 | 110 | pagePath = pagePath.replace("index.md", ""); 111 | pagePath = pagePath.replace(".md", ""); 112 | if (pagePath.length > 0) { 113 | if (pagePath.charAt(pagePath.length-1) == "/"){ 114 | pagePath = pagePath.substring(0, pagePath.length-1); 115 | } 116 | }else { 117 | pagePath = "index"; 118 | } 119 | 120 | var giscusTheme = "light"; 121 | const themeClass = document.getElementsByTagName("html")[0].className; 122 | if (themeClass.indexOf("light") == -1 && themeClass.indexOf("rust") == -1) { 123 | giscusTheme = "transparent_dark"; 124 | } 125 | 126 | var script = document.createElement("script"); 127 | script.type = "text/javascript"; 128 | script.src = "https://giscus.app/client.js"; 129 | script.async = true; 130 | script.crossOrigin = "anonymous"; 131 | script.setAttribute("data-repo", data_repo); 132 | script.setAttribute("data-repo-id", data_repo_id); 133 | script.setAttribute("data-category", data_category); 134 | script.setAttribute("data-category-id", data_category_id); 135 | script.setAttribute("data-mapping", "specific"); 136 | script.setAttribute("data-term", pagePath); 137 | script.setAttribute("data-reactions-enabled", "1"); 138 | script.setAttribute("data-emit-metadata", "0"); 139 | script.setAttribute("data-input-position", "top"); 140 | script.setAttribute("data-theme", giscusTheme); 141 | script.setAttribute("data-lang", "zh-CN"); 142 | script.setAttribute("data-loading", "lazy"); 143 | document.getElementById("giscus-container").appendChild(script); 144 | }; 145 | 146 | window.addEventListener('load', initAll); 147 | -------------------------------------------------------------------------------- /open-source/KCLVM/readme.md: -------------------------------------------------------------------------------- 1 | # KCL 2 | 3 | ![license](https://img.shields.io/badge/license-Apache--2.0-green.svg) 4 | 5 | Kusion Configuration Language (KCL) is an open source constraint-based record & functional language mainly used in [Kusion Stack](https://kusionstack.io). KCL improves the writing of a large number of complicated configuration data and logic through mature programming language theory and practice, and simplifies and verifies the development and operation of configuration through declarative syntax combined with technical features such as static typing. 6 | 7 | ## Features 8 | 9 | + **Well-designed**: Independently designed syntax, semantics, runtime and system modules, providing core language elements such as configuration, schema, lambda and rule. 10 | + **Modeling**: Schema-centric modeling abstraction. 11 | + **Easy to use**: the language itself covers most configuration and policy functions. 12 | + **Stability**: Static type system and custom rule constraints. 13 | + **Scalability**: Configuration block definition ability and rich configuration merge coverage ability. 14 | + **Automation capabilities**: Rich language-level CRUD API and multi-language API. 15 | + **High performance**: The language compiler is implemented in Rust and C mainly with LLVM optimizer, supports compilation to native and WASM targets and executes efficiently. 16 | + **Cloud Native Affinity**: Native support for [OpenAPI](https://github.com/KusionStack/kcl-openapi) and Kubernetes CRD Specs to KCL conversion, support for Kubernetes YAML specification. 17 | + **Development friendly**: Rich language tools (Lint, Test, Vet, Doc, etc.), [IDE Plugins](https://github.com/KusionStack/vscode-kcl) and [language plugins](https://github.com/KusionStack/kcl-plugin). 18 | 19 | ## What is it for? 20 | 21 | You can use KCL to 22 | 23 | + generate low-level configuration data like JSON, YAML, etc. 24 | + reduce boilerplate in configuration data with the schema modeling. 25 | + define schemas with rule constraints for configuration data and validate them automatically. 26 | + write configuration data separately and merge them using different strategies. 27 | + organize, simplify, unify and manage large configurations without side effects. 28 | + define your application delivery and operation ecosystem with [Kusion Stack](https://kusionstack.io). 29 | 30 | ## Installation 31 | 32 | [Download](https://github.com/KusionStack/KCLVM/releases) the latest release from GitHub and add `{install-location}/kclvm/bin` to the environment `PATH`. 33 | 34 | ## Quick Showcase 35 | 36 | `./samples/fib.k` is an example of calculating the Fibonacci sequence. 37 | 38 | ```kcl 39 | schema Fib: 40 | n1: int = n - 1 41 | n2: int = n1 - 1 42 | n: int 43 | value: int 44 | 45 | if n <= 1: 46 | value = 1 47 | elif n == 2: 48 | value = 1 49 | else: 50 | value = Fib {n: n1}.value + Fib {n: n2}.value 51 | 52 | fib8 = Fib {n: 8}.value 53 | ``` 54 | 55 | We can execute the following command to get a YAML output. 56 | 57 | ``` 58 | kcl ./samples/fib.k 59 | ``` 60 | 61 | YAML output 62 | 63 | ```yaml 64 | fib8: 21 65 | ``` 66 | 67 | ## Documentation 68 | 69 | Detailed documentation is available at https://kusionstack.io 70 | 71 | ## Contributing 72 | 73 | See [Developing Guide](./docs/dev_guide/1.about_this_guide.md). 74 | 75 | ## Roadmap 76 | 77 | See [KCLVM Roadmap](https://kusionstack.io/docs/governance/intro/roadmap#kclvm-%E8%B7%AF%E7%BA%BF%E8%A7%84%E5%88%92). 78 | 79 | ## License 80 | 81 | [Apache License Version 2.0] 82 | -------------------------------------------------------------------------------- /open-source/readme.md: -------------------------------------------------------------------------------- 1 | # Rust Open Source Project 2 | 3 | - [KCLVM](https://awesome-kusion.github.io/rust-code-book/open-source/KCLVM/index.html) 4 | -------------------------------------------------------------------------------- /preface.md: -------------------------------------------------------------------------------- 1 | # Preface 2 | 3 | When I was working on KusionStack and KCLVM projects, I started to learn the source code of Rustc with the idea of learning the design of excellent compiler. I took some notes and documents during this process. With the suggestion of [Chai](https://github.com/chai2010), I organized them into an article and posted it. I didn't expect people to be interested in it, so I decided to continue writing articles on Rustc source code analysis. This is the original intention of writing this e-book. 4 | 5 | KCLVM is a compiler we developed with Rust in the project of Kusion, and some parts of this book are applied to KCLVM. If you are interested in the cloud-native ecology and technology, you can learn about the project 👉 [KusionStack](https://github.com/KusionStack/kusion). And if you are interested in Rust, programming languages or compilers, maybe you prefer 👉 [KCLVM]( https://github.com/KusionStack/KCLVM). 6 | 7 | Finally, most of the content in these e-book are my personal understanding when reading the source code, as well as some descriptions in the [rust-dev-guide](https://rustc-dev-guide.rust-lang.org/about-this-guide.html). Owing to the limitation of my knowledge, there must be mistakes and errors in the book. So we welcome all forms of conrtibutions(fix typo/polish english/translation/write article or other) from everyone. 8 | -------------------------------------------------------------------------------- /rust-tools/cargo/readme.md: -------------------------------------------------------------------------------- 1 | # Cargo 2 | -------------------------------------------------------------------------------- /rust-tools/clippy/readme.md: -------------------------------------------------------------------------------- 1 | # Clippy -------------------------------------------------------------------------------- /rust-tools/readme.md: -------------------------------------------------------------------------------- 1 | # Rust外围工具 2 | -------------------------------------------------------------------------------- /rustc/codegen/readme.md: -------------------------------------------------------------------------------- 1 | # Codegen 2 | -------------------------------------------------------------------------------- /rustc/general/errors/images/error-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-kusion/rust-code-book/7cf5b600b88117f641b38a1ec45a7c8a9c5d7f59/rustc/general/errors/images/error-01.png -------------------------------------------------------------------------------- /rustc/general/errors/images/error-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-kusion/rust-code-book/7cf5b600b88117f641b38a1ec45a7c8a9c5d7f59/rustc/general/errors/images/error-02.png -------------------------------------------------------------------------------- /rustc/general/errors/images/error-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-kusion/rust-code-book/7cf5b600b88117f641b38a1ec45a7c8a9c5d7f59/rustc/general/errors/images/error-03.png -------------------------------------------------------------------------------- /rustc/general/errors/images/error-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-kusion/rust-code-book/7cf5b600b88117f641b38a1ec45a7c8a9c5d7f59/rustc/general/errors/images/error-04.png -------------------------------------------------------------------------------- /rustc/general/errors/images/error-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-kusion/rust-code-book/7cf5b600b88117f641b38a1ec45a7c8a9c5d7f59/rustc/general/errors/images/error-05.png -------------------------------------------------------------------------------- /rustc/general/errors/images/error-06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-kusion/rust-code-book/7cf5b600b88117f641b38a1ec45a7c8a9c5d7f59/rustc/general/errors/images/error-06.png -------------------------------------------------------------------------------- /rustc/general/errors/images/error-07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-kusion/rust-code-book/7cf5b600b88117f641b38a1ec45a7c8a9c5d7f59/rustc/general/errors/images/error-07.png -------------------------------------------------------------------------------- /rustc/general/errors/images/error-08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-kusion/rust-code-book/7cf5b600b88117f641b38a1ec45a7c8a9c5d7f59/rustc/general/errors/images/error-08.png -------------------------------------------------------------------------------- /rustc/general/errors/images/error-09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-kusion/rust-code-book/7cf5b600b88117f641b38a1ec45a7c8a9c5d7f59/rustc/general/errors/images/error-09.png -------------------------------------------------------------------------------- /rustc/general/errors/images/error-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-kusion/rust-code-book/7cf5b600b88117f641b38a1ec45a7c8a9c5d7f59/rustc/general/errors/images/error-10.png -------------------------------------------------------------------------------- /rustc/general/errors/images/error-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-kusion/rust-code-book/7cf5b600b88117f641b38a1ec45a7c8a9c5d7f59/rustc/general/errors/images/error-11.png -------------------------------------------------------------------------------- /rustc/general/errors/images/error-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-kusion/rust-code-book/7cf5b600b88117f641b38a1ec45a7c8a9c5d7f59/rustc/general/errors/images/error-12.png -------------------------------------------------------------------------------- /rustc/general/errors/images/error-13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-kusion/rust-code-book/7cf5b600b88117f641b38a1ec45a7c8a9c5d7f59/rustc/general/errors/images/error-13.png -------------------------------------------------------------------------------- /rustc/general/errors/readme.md: -------------------------------------------------------------------------------- 1 | # Rust 的错误信息输出原理概述 2 | 3 | ## 1. 背景 4 | 5 | 最近在参与 [KusionStack](https://link.zhihu.com/?target=https%3A//github.com/KusionStack/kusion) 内置的领域语言 —— [KCL 配置语言编译器](https://link.zhihu.com/?target=https%3A//github.com/KusionStack/kclvm) 的开发,需要开发编译器的错误处理模块,由于 KCL 使用 Rust 开发的,因此打算来学学 Rust 语言的错误处理模块是怎么做的。 6 | 7 | ## 2. 介绍 8 | 9 | 单纯从 Rustc 源码的目录结构中看,Rustc 中关于错误处理的部分主要集中在 rustc_errors 、rustc_error_codes 和 rustc_error_message 三个目录下,但是在看源码的过程中我发现由于 Rustc 代码量大,并且错误处理模块涉及到很多其他的模块,单纯的看这三个目录下的代码很容易看晕,剖析起来也比较困难。因此,我打算将这部分的的源码剖析拆分成几个部分,这篇文章主要结合[ Rustc 的官方文档](https://rustc-dev-guide.rust-lang.org/)和[ Rustc 源码](https://github.com/rust-lang/rust)进行结构的梳理。 10 | 11 | 因此本文的核心思路只是对错误处理部分的结构进行梳理,目标就是梳理一下在 Rustc 对 Rust 程序进行解析的过程中,错误是如何从分析过程一步一步传递到终端输出成诊断信息的。对于一些复杂且与错误诊断信息输出无关的内容,我们先暂且略过不做深究。留个坑后面再填,先把结构梳理清楚,也有助于我们后续一步一步的对源码进行更加深入清晰的剖析,防止我们在 Rustc 大量的源码中迷路。并且为了能更加清晰的看代码的结构,本文对使用的代码片段做了处理,去掉了生命周期等与代码执行逻辑无关的部分。 12 | 13 | ## 3. 诊断信息长啥样? 14 | 15 | 首先,看源码之前,先看看 Rust 的诊断信息的格式。如下图所示: 16 | 17 | ![](./images/error-01.png) 18 | 19 | 根据 Rustc 文档中的描述,上述信息可以分为下面5个部分, 20 | 21 | - Level 等级 (错误,警告等等),这部分主要用来说明当前消息的严重程度。 22 | 23 | - Code 代码或者翻译成编号更好一些 (例如:对于“错误的类型”这种诊断信息,它对应的编号是E0308),这个编号是一个索引,用户可以通过这个索引找到当前错误更加完整的描述信息。通过 lint 创建的诊断信息没有这个编号。 24 | **注:我后面又查了一下,rustc 官方把 Code 称作 [Rust Compiler Error Index](https://doc.rust-lang.org/error-index.html)。** 25 | 26 | - Message 消息,描述当前发生的问题的主要内容,这个消息的内容应该是通用的独立的,即使没有其他内容只看这一条信息的话,也能有所帮助。 27 | 28 | - Diagnostic Window 诊断窗口,主要负责展示出现问题的代码上下文相关的信息。 29 | 30 | - Sub-diagnostic 子诊断信息,任何错误都有很多的子诊断信息并且他们看起来都和诊断信息的主部分相似。 31 | 32 | ## 4. 诊断信息从哪来? 33 | 34 | 在了解了 Rustc 诊断信息之后,我们看下 Rustc 是如何构造这样的诊断信息的。在这部分 Rustc 官方提供了两种方式, 35 | 36 | 1. 实现 rustc_sessions 提供的 trait。 37 | 2. 用 rustc_macros 中为输出诊断信息准备的属性宏,自动实现 rustc_sessions 提供的 trait。 38 | 39 | 直接看上面这两点不太好理解,主要的流程可以参考下面这张图, 40 | 41 | ![](./images/error-02.png) 42 | 43 | 其中,黄色部分表示在 Rustc 的不同模块中,定义各自的错误/警告等异常类型的结构体 Struct (**注:枚举也可以,本文是一个概述,为了方便描述所以下面就只列Struct了**)。绿色部分表示在Rustc的错误处理模块提供了一个 trait SessionDiagnostic。不同模块内部定义的 Struct 实现这个 trait SessionDiagnostic。trait SessionDiagnostic 的具体实现就是将 Struct 中输出诊断信息需要的内容抽取出来封装好,返回给 Rustc 的错误处理模块用来输出。 44 | 45 | 这就是上面提到的实现错误模块提供的 trait。这个 trait SessionDiagnostic 的源码如下: 46 | 47 | ```rust 48 | // rustc/compiler/rustc_session/src/session.rs 49 | pub trait SessionDiagnostic 50 | 51 | { 52 | fn into_diagnostic( 53 | self, 54 | sess: & ParseSess 55 | ) -> DiagnosticBuilder; 56 | } 57 | ``` 58 | 59 | 以 Rustc 文档中给出的错误结构为例: 60 | 61 | ```rust 62 | pub struct FieldAlreadyDeclared { 63 | pub field_name: Ident, 64 | pub span: Span, 65 | pub prev_span: Span, 66 | } 67 | ``` 68 | 69 | 按照 Rustc 的官方描述,要想输出 struct FieldAlreadyDeclared 对应的错误信息,就要实现 trait SessionDiagnostic。Rustc 的源码内部定义的错误结构目前完全采用第二种方式。 70 | 71 | 在 Rustc 提供的官方文档上,提供了 trait SessionDiagnostic 的具体实现。 72 | 73 | ```rust 74 | impl SessionDiagnostic for FieldAlreadyDeclared { 75 | fn into_diagnostic(self, sess: Session) -> DiagnosticBuilder { 76 | let mut diag = sess.struct_err(...); 77 | diag.set_span(self.span); 78 | diag.span_label(...); 79 | ... 80 | diag 81 | } 82 | } 83 | ``` 84 | 85 | 上面代码展示了如何为 Struct FieldAlreadyDeclared 实现 trait SessionDiagnostic,具体的代码细节看不懂也不用急,这里只做一个展示,代码的细节不是我们本文的主题,过早的深入代码细节容易迷路,只要知道这部分代码从 Struct FieldAlreadyDeclared 抽取出了输出诊断信息需要的内容,并封装到了 DiagnosticBuilder 中返回。 86 | 87 | 那么怎么理解第二种方式呢?以上面的代码为例,实现 trait SessionDiagnostic 主要是将 Struct FieldAlreadyDeclared 中需要输出到诊断信息中的内容,抽取出来,填充到 DiagnosticBuilder 中,这个过程其实就是在搬砖,将组成诊断信息的砖块从 Struct FieldAlreadyDeclared 搬运到 DiagnosticBuilder 中,因此,这个过程可以自动化,当我们定义一个新的错误 Struct 的时候,砖块不需要我们自己搬,我们可以写一个程序帮我们搬,我们只需要在定义 Struct 的时候标注出来哪些砖需要搬就可以了。 88 | 89 | 所以,Rustc 内部通过属性宏的方式写好了搬砖的程序,这个搬砖程序为我们提供了一些注解,在定义新的错误 Struct 时,只需要通过注解标注出哪些砖要搬,Rustc 内部的属性宏就会为我们自动实现 trait SessionDiagnostic。同样是 Struct FieldAlreadyDeclared,使用第二种方式的代码如下: 90 | 91 | ```rust 92 | #[derive(SessionDiagnostic)] 93 | #[diag(typeck::field_already_declared, code = "E0124")] 94 | pub struct FieldAlreadyDeclared { 95 | pub field_name: Ident, 96 | #[primary_span] 97 | #[label] 98 | pub span: Span, 99 | #[label(typeck::previous_decl_label)] 100 | pub prev_span: Span, 101 | } 102 | ``` 103 | 104 | 其中,通过注解 #[derive(SessionDiagnostic)] 使用 rustc_sessions 内部实现的属性宏,通过注解[diag(typeck::field_already_declared, code = "E0124")] 说明当前诊断信息输出的文本信息与前面提到的当前诊断信息的编号,最后通过注解 #[primary_span], #[label] 和 #[label(typeck::previous_decl_label)] 注解标注了出现问题的代码上下文相关的信息。 105 | 106 | 定义了带有注解的 Struct 或者为 Struct 实现了 trait SessionDiagnostic 后,接下来要做什么?Rustc 文档是这么说的。 107 | 108 | **Now that we've defined our diagnostic, how do we use it? It's quite straightforward, just create an instance of the struct and pass it to emit_err (or emit_warning).** 109 | 110 | 现在,我们已经定义了我们的诊断信息,那我们如何使用它呢?这非常简单,我们只需要创建一个结构体的实例,并将它传递给 emit_err() 或者 emit_warning() 方法就可以了。 111 | 112 | ```rust 113 | tcx.sess.emit_err(FieldAlreadyDeclared { 114 | field_name: f.ident, 115 | span: f.span, 116 | prev_span, 117 | }); 118 | ``` 119 | 120 | 不太明白,但是得到了一个关键方法 emit_err() ,通过这个方法将错误的诊断信息输出到终端,那就在源码里全局搜索一下这个方法: 121 | 122 | ![](./images/error-03.png) 123 | 124 | 找到了这个方法的定义如下: 125 | 126 | ```rust 127 | // 这个方法在 Struct Session 中。 128 | impl Session{ 129 | pub fn emit_err( 130 | &self, 131 | err: impl SessionDiagnostic 132 | ) -> ErrorGuaranteed { 133 | self.parse_sess.emit_err(err) 134 | } 135 | } 136 | ``` 137 | 138 | 我们顺着方法的调用链路连续点进去看看, 139 | 140 | ```rust 141 | // self.parse_sess.emit_err(err) 142 | impl ParseSess{ 143 | pub fn emit_err( 144 | &self, 145 | err: impl SessionDiagnostic 146 | ) -> ErrorGuaranteed { 147 | self.create_err(err).emit() 148 | } 149 | } 150 | 151 | // self.create_err(err) 152 | impl ParseSess{ 153 | pub fn create_err( 154 | &'a self, 155 | err: impl SessionDiagnostic, 156 | ) -> DiagnosticBuilder { 157 | err.into_diagnostic(self) 158 | } 159 | } 160 | 161 | // self.create_err(err).emit() 162 | impl DiagnosticBuilder { 163 | pub fn emit(&mut self) -> G { 164 | ...... 165 | } 166 | } 167 | ``` 168 | 169 | 看代码好像明白了,把上面错误处理过程的图细化一下: 170 | 171 | ![](./images/error-05.png) 172 | 173 | 如图所示,我在图的右面增加了一些东西,黄色的部分没有太大的变化,Rustc 其他的模块定义错误的 Struct,绿色的部分增加了一些内容,细化了 trait SessionDiagnostic 的主要实现,根据黄色的 Struct 提供的内容生成蓝色的 DiagnosticBuilder。生成的 DiagnosticBuilder 中,内置 emit() 方法用来将诊断信息输出到终端,这个 emit() 方法最后会在 Session 中被调用。 174 | 175 | 在 rustc 中通过 Struct Session 调用生成的 DiagnosticBuilder 来输出诊断信息,具体的调用过程如上图右侧所示,Struct Session 内置了 Struct ParseSess ,这里包了两层 emit_err() 方法,并且在方法 ParseSess.emit_err() 中,调用了 ParseSess.create_err() 方法,这个方法接受 trait SessionDiagnostic 的实现,并调用 trait SessionDiagnostic 提供的 into_diagnostic() 方法获取 DiagnosticBuilder 实例,随后调用 DiagnosticBuilder 内置的 emit() 方法将诊断信息输出到终端。 176 | 177 | 看到这里,问题又来了,Rustc 通过 Session 接收 DiagnosticBuilder 输出诊断信息,这个 Session 是什么?这个 Session 是如何与 Rustc 其他模块联动的呢?或者说这个 Session 是怎么被调用的呢? 178 | 179 | 关于 Session 是什么,这不是本文的重点,为了防止迷路,这里先刨个坑,后续的文章中看看 Session 是什么,接下来,我们先来看看 Session 是怎么被调用来处理错误的。我们在全局搜索一下 sess.emit_err() 这个关键字,看看 rustc 是如何通过 Session 输出诊断信息的。 180 | 181 | 可以看到,在Rustc中很多地方都通过 Session 输出错误信息。 182 | 183 | ![](./images/error-04.png) 184 | 185 | 我看了一下,挑了几个其中比较典型,见名知意的地方。首先是在 Ructc 的语法解析器 rustc_parse 中,在进行语法分析的过程中遇到错误,会通过 sess.emit_err() 方法输出错误的诊断信息。 186 | 187 | ![](./images/error-06.png) 188 | 189 | 然后,在 rustc 的类型检查器 TypeChecker 中,所有权借用检查 rustc_borrowck 部分和类型检查部分 rustc_typeck 在检查到错误时会通过 sess.emit_err() 方法输出错误的诊断信息。与 rustc_parse 不同的是 TypeChecker 并不直接将 Session 实例作为结构体成员而是通过一个获取上下文的方法 tcx() 获取 Session 实例。 190 | 191 | ![](./images/error-07.png) 192 | ![](./images/error-08.png) 193 | 194 | 这个上下文方法 tcx() 的细节以及上下文的结构也是暂不深究,目前我们只需要知道 TypeChecker 也是通过 Session 输出诊断信息的就够了。然后,我们来浅看一下他们是如何借助 Session 输出错误的信息的。 195 | 196 | 首先,看看 rustc_parse 中关于 Session 的部分: 197 | 198 | ```rust 199 | pub struct Parser { 200 | pub sess: & ParseSess, 201 | ...... 202 | } 203 | 204 | // 在 Parser 解析 Rust 语言的时候,会调用emit_err方法输出诊断信息。 205 | self.sess.emit_err(...) 206 | ``` 207 | 208 | 见名知意给我带来了一点误判, Parser 内置的是 ParseSess 而不是 Session。所以,可以借助上面那个图的结构,给 Parser 错误处理的局部也单独画一张图。 209 | 210 | ![](./images/error-09.png) 211 | 212 | 之前的图中已经展示了内部的细节,这里就不展示了,这里只展示 trait SessionDiagnostic 和 Parser 之间的关系,(**注:上图中的 Parse() 方法是我起的名字,指的是 Rustc中 对 Rust 程序语法分析的过程,在 Rustc 源程序中这个方法并不一定存在,具体用的是什么方法不是本文的重点,但是只要是编译器就一定有 parse 过程,在不同的编译器中 parse 过程的名字可能不同。**) 213 | 214 | 如图所示,在对 Rust 程序进行语法分析的过程中,如果出现错误,就实例化一个实现了 trait SessionDiagnostic 的错误 Struct 结构,并把它抛给 Parser 内置的 ParseSess 中的 emit_err() 方法将诊断信息输出。 215 | 216 | 然后,再看看 rustc_borrowck 和 rustc_typeck,从调用方式来看,他们不是直接内置 Session 的,他们应该是内置了一个上下文相关的结构,然后那个上下文相关的结构中包含 Session 。 217 | 218 | ```rust 219 | self.tcx().sess.emit_err(MoveUnsized { ty, span }); 220 | ``` 221 | 222 | 点进 self 看一下,可以看到这是一个类型检查器 TypeChecker ,找到上下文结构并点进去深度优先的搜索 Session 或者 ParseSess 结构,为了防止大家看的时候迷路,搜索过程就不写了,这里直接展示搜索结果。 223 | 224 | ```rust 225 | struct TypeChecker { 226 | infcx: & InferCtxt, 227 | ...... 228 | } 229 | 230 | pub struct InferCtxt { 231 | pub tcx: TyCtxt, 232 | ...... 233 | } 234 | 235 | pub struct TyCtxt { 236 | gcx: & GlobalCtxt, 237 | } 238 | 239 | 240 | pub struct GlobalCtxt { 241 | pub sess: & Session, // Session 在这里 242 | .... 243 | } 244 | ``` 245 | 246 | 藏的够深的,不过好在我们还是把它挖了出来,目前聚焦于错误处理,所以暂时不用关心这些上下文结构 (XXXCtxt) 都是什么意思。 247 | 248 | ![](./images/error-10.png) 249 | 250 | 如上图所示,与 Parser 的部分同理,ty_check() 是我自己写的方法,代指 TypeChecker 对 Rust 程序进行类型检查的过程,目前聚焦于错误处理,所以 InferCtxt,TyCtxt 和 GlobalCtxt 等上下文结构我就缩写为 XXXCtx 了,可以看到,这个过程和 Parser 错误处理的过程是一样的,在类型检查的过程中出现错误,就实例化一个实现了 trait SessionDiagnostic 的结构,并把它抛给 TypeChecker 内置的各种上下文中内置的 Session 中的 emit_err() 方法将诊断信息输出。 251 | 252 | 看到这里,压力来到了 Session 和 ParseSess 这边,既然大家都把错误抛给他,那就来看看它里面干了啥。 253 | 254 | ```rust 255 | pub struct Session { 256 | pub parse_sess: ParseSess, 257 | ...... 258 | } 259 | 260 | pub struct ParseSess { 261 | pub span_diagnostic: Handler, 262 | ...... 263 | } 264 | ``` 265 | 266 | 看不太明白,再把之前的代码拿来看看 267 | 268 | ```rust 269 | // self.parse_sess.emit_err(err) 270 | impl ParseSess{ 271 | pub fn emit_err( 272 | & self, 273 | err: impl SessionDiagnostic 274 | ) -> ErrorGuaranteed { 275 | self.create_err(err).emit() 276 | } 277 | } 278 | 279 | // 这个方法是 self.create_err(err) 280 | impl ParseSess{ 281 | pub fn create_err( 282 | & self, 283 | err: impl SessionDiagnostic, 284 | ) -> DiagnosticBuilder { 285 | err.into_diagnostic(self) 286 | } 287 | } 288 | 289 | // 这个方法是 self.create_err(err).emit() 290 | impl DiagnosticBuilder { 291 | pub fn emit(&mut self) -> G { 292 | ...... /// 看来,是时候把这里省略的代码展开了... 293 | } 294 | } 295 | ``` 296 | 297 | 展开上述第21行的代码,看到这是一个 trait 的抽象接口: 298 | 299 | ```rust 300 | impl DiagnosticBuilder { 301 | pub fn emit(&mut self) -> G { 302 | // 省略的代码 303 | G::diagnostic_builder_emit_producing_guarantee(self) 304 | } 305 | 306 | // 省略的代码是一个trait的抽象接口。 307 | pub trait EmissionGuarantee: Sized { 308 | fn diagnostic_builder_emit_producing_guarantee( 309 | db: &mut DiagnosticBuilder 310 | ) -> Self; 311 | ... 312 | } 313 | ``` 314 | 315 | 为了防止迷路,先不深究 EmissionGuarantee 是做什么的,只关注他提供的输出诊断信息到终端的功能就好了。 316 | 然后,我们在全局搜索 EmissionGuarantee,找一个 EmissionGuarantee 的实现,看看他是如何输出信息的。 317 | 318 | ```rust 319 | impl EmissionGuarantee for ErrorGuaranteed { 320 | fn diagnostic_builder_emit_producing_guarantee( 321 | db: &mut DiagnosticBuilder 322 | ) -> Self { 323 | match db.inner.state { 324 | DiagnosticBuilderState::Emittable(handler) => { 325 | ... 326 | let guar = handler.emit_diagnostic(&mut db.inner.diagnostic); 327 | ... 328 | } 329 | DiagnosticBuilderState::AlreadyEmittedOrDuringCancellation => { 330 | ...... 331 | } 332 | } 333 | } 334 | } 335 | ``` 336 | 337 | 看到上面的代码,我觉得压力来到了 DiagnosticBuilder 这边,来都来了,得看看。 338 | 339 | ```rust 340 | // match db.inner.state 341 | 342 | pub struct DiagnosticBuilder { 343 | inner: DiagnosticBuilderInner, 344 | ... 345 | } 346 | 347 | struct DiagnosticBuilderInner { 348 | state: DiagnosticBuilderState, 349 | diagnostic: Box, 350 | } 351 | 352 | // match db.inner.state 353 | enum DiagnosticBuilderState { 354 | Emittable(& Handler), 355 | AlreadyEmittedOrDuringCancellation, 356 | } 357 | ``` 358 | 359 | 可以看到,最后是通过 DiagnosticBuilderState 中的 Handler 输出得诊断信息。 360 | 361 | ```rust 362 | /// A handler deals with errors and other compiler output. 363 | /// Certain errors (fatal, bug, unimpl) may cause immediate exit, 364 | /// others log errors for later reporting. 365 | pub struct Handler { 366 | flags: HandlerFlags, 367 | inner: Lock, 368 | } 369 | ``` 370 | 371 | 到 Handler 这里,看看注释,我觉得可以了,我们知道了所有错误的诊断信息,最后都通过 Handler 输出到终端,到这里,可以再把上面的图细化一下: 372 | 373 | ![](./images/error-11.png) 374 | 375 | 如图所示,我们在图中将 DiagnosticBuilder 内部的一点点细节画进去了,先不考虑 EmissionGuarantee。 DiagnosticBuilder 中包含输出诊断信息的 Handler 和保存诊断信息内容的 Diagnostic ,在 Session 和 ParseSess 中,会先调用 SessionDiagnostic 的 into_diagnostic() 方法,获得 DiagnosticBuilder,然后调用 DiagnoaticBuilder 的 emit() 方法输出诊断信息,在 emit() 方法中,会调用 DiagnoaticBuilder 内置的 Handler 并将 DiagnoaticBuilder 中的 Diagnostic 输出到终端。 376 | 377 | ## 总结 378 | 在本文中我们只涉猎了 Rustc 中错误处理模块很小的一部分,通过这一部分的浅看,我们大概的了解了一下 Rustc 中错误从出现到变成诊断信息输出到终端的整个流程。最后以上文中提到的 rustc_parser 和 rustc_type_checker 为例,一张图收尾。 379 | 380 | ![](./images/error-12.png) 381 | 382 | Rustc 错误处理模块的三部分: 383 | 384 | - 编译器的各个部分自定义错误的结构,保存错误信息。 385 | - SessionDiagnostic 负责将各部分自定义错误的结构转换为 DiagnosticBuilder。 386 | - Session/ParseSess 负责调用 SessionDiagnostic 提供的接口获得 DiagnosticBuilder ,并调用 DiagnosticBuilder 内置的方法输出诊断信息。 387 | 388 | 如果还是有一点绕晕了,在上面这个图上再加一笔,通过红色的尖头我们可以看到 Rust 中的一个异常包含的信息的从发生错误的地方到开发者面前的主要流向: 389 | 390 | ![](./images/error-13.png) 391 | 392 | 从上图右面的部分可以看到,错误信息并不是直接从 DiagnosticBuilder 中发送到开发者面前的,而是先从 Session 兜了个圈子,那为什么要这么做呢?这里先刨个坑,后续我们将进一步深入到 Rustc 的源码当中去,详细剖析解读一下各部分的源码结构并且理解一下 Rustc 的开发者增加各个部分的动机。 393 | 394 | ## 本期挖坑 395 | 396 | - Session 和 ParseSess 到底是什么 ? 397 | - 为什么搜索 emit_err() 没有涉及到词法分析 Lexer 和代码生成 CodeGen 的部分,这两个部分的错误是怎么处理的 ? 398 | - EmissionGuarantee 这个结构在错误处理的过程中是做什么的 ? 399 | 400 | ## 参考 401 | 402 | - KusionStack: [https://github.com/KusionStack/kusion](https://github.com/KusionStack/kusion) 403 | - KCL 配置语言编译器: [https://github.com/KusionStack/KCLVM](https://github.com/KusionStack/KCLVM) 404 | - Rustc 官方文档: [https://rustc-dev-guide.rust-lang.org/](https://rustc-dev-guide.rust-lang.org/) 405 | - Rustc 源码: [https://github.com/rust-lang/rust](https://github.com/rust-lang/rust) 406 | - Rust Compiler Error Index: [https://doc.rust-lang.org/error-index.html](https://doc.rust-lang.org/error-index.html) 407 | -------------------------------------------------------------------------------- /rustc/general/readme.md: -------------------------------------------------------------------------------- 1 | # General 2 | -------------------------------------------------------------------------------- /rustc/general/sourcemap-span/readme.md: -------------------------------------------------------------------------------- 1 | # SourceMap & Span 2 | -------------------------------------------------------------------------------- /rustc/invocation/readme.md: -------------------------------------------------------------------------------- 1 | # Invocation -------------------------------------------------------------------------------- /rustc/lexer/readme.md: -------------------------------------------------------------------------------- 1 | # Lexer 2 | -------------------------------------------------------------------------------- /rustc/overview/readme.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | -------------------------------------------------------------------------------- /rustc/parser/ast/ast.md: -------------------------------------------------------------------------------- 1 | # AST -------------------------------------------------------------------------------- /rustc/parser/ast/readme.md: -------------------------------------------------------------------------------- 1 | # AST -------------------------------------------------------------------------------- /rustc/parser/ast/visitor.md: -------------------------------------------------------------------------------- 1 | # Visitor -------------------------------------------------------------------------------- /rustc/parser/early-lint/readme.md: -------------------------------------------------------------------------------- 1 | # Early Lint 2 | -------------------------------------------------------------------------------- /rustc/parser/readme.md: -------------------------------------------------------------------------------- 1 | # 语法分析 2 | -------------------------------------------------------------------------------- /rustc/readme.md: -------------------------------------------------------------------------------- 1 | # Rust Compiler 2 | -------------------------------------------------------------------------------- /rustc/sema/hir-lowering/readme.md: -------------------------------------------------------------------------------- 1 | # HIR Lowering 2 | -------------------------------------------------------------------------------- /rustc/sema/hir-lowering/trait-solving/readme.md: -------------------------------------------------------------------------------- 1 | # Trait Solving 2 | -------------------------------------------------------------------------------- /rustc/sema/hir-lowering/type-checking/readme.md: -------------------------------------------------------------------------------- 1 | # Type Checking 2 | -------------------------------------------------------------------------------- /rustc/sema/hir-lowering/type-inference/readme.md: -------------------------------------------------------------------------------- 1 | # Type Inference 2 | -------------------------------------------------------------------------------- /rustc/sema/late-lint/readme.md: -------------------------------------------------------------------------------- 1 | # Late Lint 2 | -------------------------------------------------------------------------------- /rustc/sema/lint/1.lint-pass.md: -------------------------------------------------------------------------------- 1 | # Lint 与 LintPass 2 | 3 | ## 背景 4 | 5 | ### Rustc 6 | 7 | Rustc 是 Rust Compiler 的简称,即 Rust 编程语言的编译器。Rust 的编译器是自举的,即 Rustc 由 Rust 语言编写而成,可以通过旧版本编译出新版本。因此,Rustc 可以说是用 Rust 语言编写编译器的最佳实践。 8 | 9 | ### Lint 工具 10 | 11 | Lint 是代码静态分析工具的一种,最早是来源于 C 语言。Lint 工具通常会检查代码中潜在的问题和错误,包括(但不限于)编程风格(缩进、空行、空格)、代码质量(定义未使用的变量、文档缺失)以及错误代码(除0错误、重复定义、循环引用)等问题。通常来说,Lint 工具除了标识错误外,还会带有一定的 fix/refactor suggest 和 auto-fix 的能力。在工程中引入 Lint 工具可以有效的减少错误,提高整体的工程质量。此外,对一种编程语言来说,Lint 工具通常也是其他工具研发的前置条件,例如 IDE 插件的错误提示,CI 的 Pipeline 检测等。 12 | 13 | ## Lint vs. LintPass 14 | 15 | ### 概念与关系 16 | 17 | Rustc 中关于 Lint 最主要的结构有两个, `Lint` 和 `LintPass`。首先需要区分 Lint 和 LintPass 的概念。Rustc 的很多文档中都将它们统称为 `Lint`,这很容易造成混淆。关于这两者之间的区别,rustc-dev-guide 给出的解释是: 18 | 19 | > Lint declarations don't carry any "state" - they are merely global identifiers and descriptions of lints. We assert at runtime that they are not registered twice (by lint name). 20 | Lint passes are the meat of any lint. 21 | 22 | 从定义方面, `Lint` 是对所定义的 lint 检查的静态描述,例如 name, level, description, code 等属性,与检查时的状态无关,Rustc 用 `Lint` 的定义做唯一性的检查。而 `LintPass` 是 `Lint` 的具体实现,是在检查时调用的 `check_*` 方法。 23 | 在具体的代码实现方法, `Lint`定义为一个 Struct,所有 lint 的定义都是此类型的一个实例/对象。而 `LintPass` 则对应为一个 trait。trait 类似于 java/c++ 中的接口,每一个 lintpass 的定义都需要实现该接口中定义的方法。 24 | 25 | ```rust 26 | /// Specification of a single lint. 27 | #[derive(Copy, Clone, Debug)] 28 | pub struct Lint { 29 | pub name: &'static str, 30 | /// Default level for the lint. 31 | pub default_level: Level, 32 | /// Description of the lint or the issue it detects. 33 | /// 34 | /// e.g., "imports that are never used" 35 | pub desc: &'static str, 36 | ... 37 | } 38 | 39 | pub trait LintPass { 40 | fn name(&self) -> &'static str; 41 | } 42 | ``` 43 | 44 | 需要注意的是,尽管刚刚的描述中说到`trait` 类似于接口而 `Lint` 是一个 struct,但 `Lint` 和 `LintPass` 之间并不是 OO 中一个“类”和它的“方法”的关系。而是在声明 `LintPass` 会生成一个实现了该 trait 的同名的 struct,该 struct 中的 `get_lints()` 方法会生成对应的 `Lint` 定义。 45 | 46 | ![lint vs. lintpass](./images/lint_lintpass.jpeg) 47 | 48 | 这与 rustc-dev-guide 的描述也保持了一致: 49 | 50 | > A lint might not have any lint pass that emits it, it could have many, or just one -- the compiler doesn't track whether a pass is in any way associated with a particular lint, and frequently lints are emitted as part of other work (e.g., type checking, etc.). 51 | 52 | ### Lint 与 LintPass 的宏定义 53 | 54 | Rustc 为 Lint 和 LintPass 都提供了用于定义其结构的宏。 55 | 定义 Lint 的宏`declare_lint` 比较简单,可以在`rustc_lint_defs::lib.rs`中找到。`declare_lint` 宏解析输入参数,并生成名称为 `$NAME` 的 Lint struct。 56 | 57 | ```rust 58 | #[macro_export] 59 | macro_rules! declare_lint { 60 | ($(#[$attr:meta])* $vis: vis $NAME: ident, $Level: ident, $desc: expr) => ( 61 | $crate::declare_lint!( 62 | $(#[$attr])* $vis $NAME, $Level, $desc, 63 | ); 64 | ); 65 | ($(#[$attr:meta])* $vis: vis $NAME: ident, $Level: ident, $desc: expr, 66 | $(@feature_gate = $gate:expr;)? 67 | $(@future_incompatible = FutureIncompatibleInfo { $($field:ident : $val:expr),* $(,)* }; )? 68 | $($v:ident),*) => ( 69 | $(#[$attr])* 70 | $vis static $NAME: &$crate::Lint = &$crate::Lint { 71 | name: stringify!($NAME), 72 | default_level: $crate::$Level, 73 | desc: $desc, 74 | edition_lint_opts: None, 75 | is_plugin: false, 76 | $($v: true,)* 77 | $(feature_gate: Some($gate),)* 78 | $(future_incompatible: Some($crate::FutureIncompatibleInfo { 79 | $($field: $val,)* 80 | ..$crate::FutureIncompatibleInfo::default_fields_for_macro() 81 | }),)* 82 | ..$crate::Lint::default_fields_for_macro() 83 | }; 84 | ); 85 | ($(#[$attr:meta])* $vis: vis $NAME: ident, $Level: ident, $desc: expr, 86 | $lint_edition: expr => $edition_level: ident 87 | ) => ( 88 | $(#[$attr])* 89 | $vis static $NAME: &$crate::Lint = &$crate::Lint { 90 | name: stringify!($NAME), 91 | default_level: $crate::$Level, 92 | desc: $desc, 93 | edition_lint_opts: Some(($lint_edition, $crate::Level::$edition_level)), 94 | report_in_external_macro: false, 95 | is_plugin: false, 96 | }; 97 | ); 98 | } 99 | ``` 100 | 101 | LintPass 的定义涉及到两个宏: 102 | 103 | - declare_lint_pass:生成一个名为`$name` 的 struct,并且调用 `impl_lint_pass` 宏。 104 | 105 | ```rust 106 | macro_rules! declare_lint_pass { 107 | ($(#[$m:meta])* $name:ident => [$($lint:expr),* $(,)?]) => { 108 | $(#[$m])* #[derive(Copy, Clone)] pub struct $name; 109 | $crate::impl_lint_pass!($name => [$($lint),*]); 110 | }; 111 | } 112 | ``` 113 | 114 | - impl_lint_pass:为生成的 `LintPass` 结构实现`fn name()`和 `fn get_lints()` 方法。 115 | 116 | ```rust 117 | macro_rules! impl_lint_pass { 118 | ($ty:ty => [$($lint:expr),* $(,)?]) => { 119 | impl $crate::LintPass for $ty { 120 | fn name(&self) -> &'static str { stringify!($ty) } 121 | } 122 | impl $ty { 123 | pub fn get_lints() -> $crate::LintArray { $crate::lint_array!($($lint),*) } 124 | } 125 | }; 126 | } 127 | ``` 128 | 129 | ### EarlyLintPass 与 LateLintPass 130 | 131 | 前面关于 `LintPass` 的宏之中,只定义了`fn name()`和 `fn get_lints()` 方法,但并没有定义用于检查的 `check_*` 函数。这是因为 Rustc 中将 `LintPass` 分为了更为具体的两类:`EarlyLintPass`和`LateLintPass`。其主要区别在于检查的元素是否带有类型信息,即在类型检查之前还是之后执行。例如, `WhileTrue` 检查代码中的 `while true{...}` 并提示用户使用 `loop{...}` 去代替。这项检查不需要任何的类型信息,因此被定义为一个  `EarlyLint`(代码中 `impl EarlyLintPass for WhileTrue`。 132 | 133 | ```rust 134 | declare_lint! { 135 | WHILE_TRUE, 136 | Warn, 137 | "suggest using `loop { }` instead of `while true { }`" 138 | } 139 | 140 | declare_lint_pass!(WhileTrue => [WHILE_TRUE]); 141 | 142 | impl EarlyLintPass for WhileTrue { 143 | fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) { 144 | ... 145 | } 146 | } 147 | ``` 148 | 149 | Rustc 中用了3个宏去定义 `EarlyLintPass`: 150 | 151 | - early_lint_methods:early_lint_methods 中定义了 `EarlyLintPass` 中需要实现的 `check_*`函数,并且将这些函数以及接收的参数 `$args`传递给下一个宏。 152 | 153 | ```rust 154 | macro_rules! early_lint_methods { 155 | ($macro:path, $args:tt) => ( 156 | $macro!($args, [ 157 | fn check_param(a: &ast::Param); 158 | fn check_ident(a: &ast::Ident); 159 | fn check_crate(a: &ast::Crate); 160 | fn check_crate_post(a: &ast::Crate); 161 | ... 162 | ]); 163 | ) 164 | } 165 | ``` 166 | 167 | - declare_early_lint_pass:生成trait `EarlyLintPass` 并调用宏 `expand_early_lint_pass_methods`。 168 | 169 | ```rust 170 | macro_rules! declare_early_lint_pass { 171 | ([], [$($methods:tt)*]) => ( 172 | pub trait EarlyLintPass: LintPass { 173 | expand_early_lint_pass_methods!(&EarlyContext<'_>, [$($methods)*]); 174 | } 175 | ) 176 | } 177 | ``` 178 | 179 | - expand_early_lint_pass_methods:为`check_*`方法提供默认实现,即空检查。 180 | 181 | ```rust 182 | macro_rules! expand_early_lint_pass_methods { 183 | ($context:ty, [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => ( 184 | $(#[inline(always)] fn $name(&mut self, _: $context, $(_: $arg),*) {})* 185 | ) 186 | } 187 | ``` 188 | 189 | 这样的设计好处有以下几点: 190 | 191 | 1. 因为 LintPass 是一个 trait,每一个 LintPass 的定义都需要实现其内部定义的所有方法。但 early lint 和 late lint 发生在编译的不同阶段,函数入参也不一致(AST 和 HIR)。因此,LintPass 的定义只包含了 `fn name()` 和 `fn get_lints()` 这两个通用的方法。而执行检查函数则定义在了更为具体的 `EarlyLintPass` 和 `LateLintPass` 中。 192 | 1. 同样的,对于 `EarlyLintPass`, 每一个 lintpass 的定义都必须实现其中的所有方法。但并非每一个 lintpass 都需要检查 AST 的所有节点。 `expand_early_lint_pass_methods` 为其内部方法提供了默认实现。这样在定义具体的 lintpass 时,只需要关注和实现其相关的检查函数即可。例如,对于 `WhileTrue` 的定义,因为 `while true { }`这样的写法只会出现在 `ast::Expr` 节点中,因此只需要实现 `check_expr` 函数即可。在其他任何节点调用 `WhileTrue` 的检查函数,如在检查 AST 上的标识符节点时,调用 `WhileTrue.check_ident()`,则根据宏 `expand_early_lint_pass_methods` 中的定义执行一个空函数。 193 | 194 | ### pass 的含义 195 | 196 | 在 Rustc 中,除了 `Lint` 和 `LintPass` 外,还有一些 `*Pass` 的命名,如 `Mir` 和 `MirPass`、`rustc_passes` 包等。编译原理龙书中对Pass有对应的解释: 197 | 198 | > 1.2.8 将多个步骤组合成趟 199 | 前面关于步骤的讨论讲的是一个编译器的逻辑组织方式。在一个特定的实现中,多个步骤的活动可以被组合成一趟(pass)。每趟读入一个输入文件并产生一个输出文件。 200 | 201 | 在声明 `LintPass` 的宏 `declare_lint_pass` 中,其第二个参数为一个列表,表示一个 lintpass 可以生成多个 lint。Rustc 中还有一些 CombinedLintPass 中也是将所有 builtin 的 lint 汇总到一个 lintpass 中。这与龙书中“趟”的定义基本一致:`LintPass` 可以组合多个 `Lint` 的检查,每个 LintPass 读取一个 AST 并产生对应的结果。 202 | 203 | ## Lint 的简单实现 204 | 205 | 在 LintPass 的定义中,给每一个 lintpass 的所有 `check_*` 方法都提供了一个默认实现。到这里为止,基本上已经可以实现 Lint 检查的功能。 206 | 207 | ```rust 208 | struct Linter { } 209 | impl ast_visit::Visitor for Linter { 210 | fn visit_crate(a: ast:crate){ 211 | for lintpass in lintpasses{ 212 | lintpass.check_crate(a) 213 | } 214 | walk_crate(); 215 | } 216 | fn visit_stmt(a: ast:stmt){ 217 | for lintpass in lintpasses{ 218 | lintpass.check_stmt(a) 219 | } 220 | walk_stmt(); 221 | } 222 | ... 223 | } 224 | 225 | let linter = Linter::new(); 226 | 227 | for c in crates{ 228 | linter.visit_crate(c); 229 | } 230 | ``` 231 | 232 | `Visitor` 是遍历 AST 的工具,在这里为 Linter 实现其中的 `visit_*` 方法,在遍历时调用所有 lintpass 的 `check_*` 函数。`walk_*` 会继续调用其他的 `visit_*` 函数,遍历其中的子节点。因此,对于每一个 crate, 只需要调用 `visit_crate()` 函数就可以遍历 AST 并完成检查。 233 | 234 | ## 总结 235 | 236 | 本文简单介绍了 Rustc 源码中关于 Lint 的几个重要结构。并以 `WhileTrue` 为例说明了 Rustc 如何中定义和实现一个 `Lint`,最后基于这些结构,提供了一个简易的 Lint 检查的实现方式。希望能够对理解 Rustc 及 Lint 有所帮助,如有错误,欢迎指正。KCL 的 Lint 工具也参考了其中部分设计, 由文末简易的 Linter 结构改进而成。篇幅限制,将后续的文章将继续介绍 Rustc 中 Lint 在编译过程中的注册和执行过程,如何继续优化上述 `Linter` 的实现,以及 KCL Lint 的设计和实践,期待继续关注。 237 | 238 | ## Ref 239 | 240 | - KusionStack: 241 | - Rustc: 242 | - rustc-dev-guide: 243 | - Rust Visitor: 244 | - Rust Clippy: 245 | -------------------------------------------------------------------------------- /rustc/sema/lint/2.combinedlintpass.md: -------------------------------------------------------------------------------- 1 | # CombinedLintPass 2 | 3 | ## CombinedLintpass 4 | 5 | Rustc 在 `LintPass` 的中实现了 `Lint` 工具检查的具体逻辑。并且使用 Visitor 模式遍历 AST 的同时调用 lintpass 中的 `check_*`方法。 6 | 7 | ```rust 8 | impl ast_visit::Visitor for Linter { 9 | fn visit_crate(a: ast:crate){ 10 | for lintpass in lintpasses{ 11 | lintpass.check_crate(a) 12 | } 13 | walk_crate(); 14 | } 15 | fn visit_stmt(a: ast:stmt){ 16 | for lintpass in lintpasses{ 17 | lintpass.check_stmt(a) 18 | } 19 | walk_stmt(); 20 | } 21 | ... 22 | } 23 | ``` 24 | 25 | 但是,Rustc 自身和 clippy 提供的 Lint 定义多达550+多个。考虑到性能因素,定义大量的 LintPass,分别注册和调用显然是不合适的。Rustc 提供了一种更优的解决方法:既然可以将多个 Lint 组织为一个 LintPass,同样也可以将多个 LintPass 组合成一个 CombinedLintPass。 26 | > [Compiler lint passes are combined into one pass](https://rustc-dev-guide.rust-lang.org/diagnostics/lintstore.html#compiler-lint-passes-are-combined-into-one-pass) 27 | > Within the compiler, for performance reasons, we usually do not register dozens of lint passes. Instead, we have a single lint pass of each variety (e.g., BuiltinCombinedModuleLateLintPass) which will internally call all of the individual lint passes; this is because then we get the benefits of static over dynamic dispatch for each of the (often empty) trait methods. 28 | > Ideally, we'd not have to do this, since it adds to the complexity of understanding the code. However, with the current type-erased lint store approach, it is beneficial to do so for performance reasons. 29 | 30 | ### BuiltinCombinedEarlyLintPass 31 | 32 | CombinedLintPass 同样分为 early 和 late 两类。 以 builtin 的 early lint 为例,Rustc 在 `rustc_lint::src::lib.rs` 中为这些 lintpass 定义了一个 `BuiltinCombinedEarlyLintPass` 结构。 33 | 34 | ```rust 35 | early_lint_passes!(declare_combined_early_pass, [BuiltinCombinedEarlyLintPass]); 36 | ``` 37 | 38 | 虽然这个定义看起来只有一行,但其中通过若干个宏的展开,汇总了14个 `LintPass`,并且每个 `LintPass` 提供了50多个 `check_*` 方法。接下来一一说明这些宏。 39 | 40 | #### BuiltinCombinedEarlyLintPass 的宏定义 41 | 42 | ##### early_lint_passes 43 | 44 | ```rust 45 | macro_rules! early_lint_passes { 46 | ($macro:path, $args:tt) => { 47 | $macro!( 48 | $args, 49 | [ 50 | UnusedParens: UnusedParens, 51 | UnusedBraces: UnusedBraces, 52 | UnusedImportBraces: UnusedImportBraces, 53 | UnsafeCode: UnsafeCode, 54 | AnonymousParameters: AnonymousParameters, 55 | EllipsisInclusiveRangePatterns: EllipsisInclusiveRangePatterns::default(), 56 | NonCamelCaseTypes: NonCamelCaseTypes, 57 | DeprecatedAttr: DeprecatedAttr::new(), 58 | WhileTrue: WhileTrue, 59 | NonAsciiIdents: NonAsciiIdents, 60 | HiddenUnicodeCodepoints: HiddenUnicodeCodepoints, 61 | IncompleteFeatures: IncompleteFeatures, 62 | RedundantSemicolons: RedundantSemicolons, 63 | UnusedDocComment: UnusedDocComment, 64 | ] 65 | ); 66 | }; 67 | } 68 | ``` 69 | 70 | 首先是 early_lint_passes 宏,这个宏的主要作用是定义了所有的 early lintpass。这里的 lintpass 是成对出现的,`:`左边为 lintpass 的 Identifier,`:`右边为 lintpass 的constructor。所以会出现 `EllipsisInclusiveRangePatterns::default()` 和 `DeprecatedAttr::new()`这种形式。early_lint_passes 会将定义的 early lintpass 和 第二个参数一起传递给下一个宏。 71 | 通过这个宏,之前的`BuiltinCombinedEarlyLintPass`的定义被展开为: 72 | 73 | ```rust 74 | declare_combined_early_pass!([BuiltinCombinedEarlyLintPass], [ 75 | UnusedParens: UnusedParens, 76 | UnusedBraces: UnusedBraces, 77 | UnusedImportBraces: UnusedImportBraces, 78 | UnsafeCode: UnsafeCode, 79 | AnonymousParameters: AnonymousParameters, 80 | EllipsisInclusiveRangePatterns: EllipsisInclusiveRangePatterns::default(), 81 | NonCamelCaseTypes: NonCamelCaseTypes, 82 | DeprecatedAttr: DeprecatedAttr::new(), 83 | WhileTrue: WhileTrue, 84 | NonAsciiIdents: NonAsciiIdents, 85 | HiddenUnicodeCodepoints: HiddenUnicodeCodepoints, 86 | IncompleteFeatures: IncompleteFeatures, 87 | RedundantSemicolons: RedundantSemicolons, 88 | UnusedDocComment: UnusedDocComment, 89 | ]) 90 | ``` 91 | 92 | ##### declare_combined_early_pass 93 | 94 | ```rust 95 | macro_rules! declare_combined_early_pass { 96 | ([$name:ident], $passes:tt) => ( 97 | early_lint_methods!(declare_combined_early_lint_pass, [pub $name, $passes]); 98 | ) 99 | } 100 | ``` 101 | 102 | declare_combined_early_pass 宏接收 early_lint_passes宏传来的 name(BuiltinCombinedEarlyLintPass) 和 passes,并继续传递给 early_lint_methods 宏。 103 | 通过这个宏,`BuiltinCombinedEarlyLintPass`的定义继续展开为: 104 | 105 | ```rust 106 | early_lint_methods!(declare_combined_early_lint_pass, 107 | [pub BuiltinCombinedEarlyLintPass, 108 | [ 109 | UnusedParens: UnusedParens, 110 | UnusedBraces: UnusedBraces, 111 | UnusedImportBraces: UnusedImportBraces, 112 | UnsafeCode: UnsafeCode, 113 | AnonymousParameters: AnonymousParameters, 114 | EllipsisInclusiveRangePatterns: EllipsisInclusiveRangePatterns::default(), 115 | NonCamelCaseTypes: NonCamelCaseTypes, 116 | DeprecatedAttr: DeprecatedAttr::new(), 117 | WhileTrue: WhileTrue, 118 | NonAsciiIdents: NonAsciiIdents, 119 | HiddenUnicodeCodepoints: HiddenUnicodeCodepoints, 120 | IncompleteFeatures: IncompleteFeatures, 121 | RedundantSemicolons: RedundantSemicolons, 122 | UnusedDocComment: UnusedDocComment, 123 | ] 124 | ]); 125 | ``` 126 | 127 | ##### early_lint_methods 128 | 129 | ```rust 130 | macro_rules! early_lint_methods { 131 | ($macro:path, $args:tt) => ( 132 | $macro!($args, [ 133 | fn check_param(a: &ast::Param); 134 | fn check_ident(a: &ast::Ident); 135 | fn check_crate(a: &ast::Crate); 136 | fn check_crate_post(a: &ast::Crate); 137 | ... 138 | ]); 139 | ) 140 | } 141 | ``` 142 | 143 | early_lint_methods 宏在前一篇文章中也介绍过,它定义了 `EarlyLintPass` 中需要实现的 `check_*`函数,并且将这些函数以及接收的参数 `$args`传递给下一个宏。因为 `BuiltinCombinedEarlyLintPass` 也是 early lint 的一种,所以同样需要实现这些函数。 144 | 通过这个宏,`BuiltinCombinedEarlyLintPass`的定义继续展开为: 145 | 146 | ```rust 147 | declare_combined_early_lint_pass!( 148 | [pub BuiltinCombinedEarlyLintPass, 149 | [ 150 | UnusedParens: UnusedParens, 151 | UnusedBraces: UnusedBraces, 152 | UnusedImportBraces: UnusedImportBraces, 153 | UnsafeCode: UnsafeCode, 154 | AnonymousParameters: AnonymousParameters, 155 | EllipsisInclusiveRangePatterns: EllipsisInclusiveRangePatterns::default(), 156 | NonCamelCaseTypes: NonCamelCaseTypes, 157 | DeprecatedAttr: DeprecatedAttr::new(), 158 | WhileTrue: WhileTrue, 159 | NonAsciiIdents: NonAsciiIdents, 160 | HiddenUnicodeCodepoints: HiddenUnicodeCodepoints, 161 | IncompleteFeatures: IncompleteFeatures, 162 | RedundantSemicolons: RedundantSemicolons, 163 | UnusedDocComment: UnusedDocComment, 164 | ] 165 | ], 166 | [ 167 | fn check_param(a: &ast::Param); 168 | fn check_ident(a: &ast::Ident); 169 | fn check_crate(a: &ast::Crate); 170 | fn check_crate_post(a: &ast::Crate); 171 | ... 172 | ] 173 | ) 174 | ``` 175 | 176 | ##### declare_combined_early_lint_pass 177 | 178 | ```rust 179 | macro_rules! declare_combined_early_lint_pass { 180 | ([$v:vis $name:ident, [$($passes:ident: $constructor:expr,)*]], $methods:tt) => ( 181 | #[allow(non_snake_case)] 182 | $v struct $name { 183 | $($passes: $passes,)* 184 | } 185 | impl $name { 186 | $v fn new() -> Self { 187 | Self { 188 | $($passes: $constructor,)* 189 | } 190 | } 191 | $v fn get_lints() -> LintArray { 192 | let mut lints = Vec::new(); 193 | $(lints.extend_from_slice(&$passes::get_lints());)* 194 | lints 195 | } 196 | } 197 | impl EarlyLintPass for $name { 198 | expand_combined_early_lint_pass_methods!([$($passes),*], $methods); 199 | } 200 | #[allow(rustc::lint_pass_impl_without_macro)] 201 | impl LintPass for $name { 202 | fn name(&self) -> &'static str { 203 | panic!() 204 | } 205 | } 206 | ) 207 | } 208 | ``` 209 | 210 | declare_combined_early_lint_pass宏是生成 `BuiltinCombinedEarlyLintPass` 的主体。这个宏中做了以下工作: 211 | 212 | - 生成一个名为 `BuiltinCombinedEarlyLintPass` 的 struct,其中的属性为宏 `early_lint_passes` 提供的 lintpass 的 identifier。 213 | - 实现 `fn new()` `fn name()` 和 `fn get_lints()` 方法。其中 `new()` 调用了 `early_lint_passes` 提供的 lintpass 的 constructor。 214 | - 调用宏 `expand_combined_early_lint_pass_methods`,实现自身的 `check_*` 方法。 215 | 216 | 通过这个宏,`BuiltinCombinedEarlyLintPass`的定义变为: 217 | 218 | ```rust 219 | pub struct BuiltinCombinedEarlyLintPass { 220 | UnusedParens: UnusedParens, 221 | UnusedBraces: UnusedBraces, 222 | UnusedImportBraces: UnusedImportBraces, 223 | UnsafeCode: UnsafeCode, 224 | AnonymousParameters: AnonymousParameters, 225 | EllipsisInclusiveRangePatterns: EllipsisInclusiveRangePatterns, 226 | NonCamelCaseTypes: NonCamelCaseTypes, 227 | DeprecatedAttr: DeprecatedAttr, 228 | WhileTrue: WhileTrue, 229 | NonAsciiIdents: NonAsciiIdents, 230 | HiddenUnicodeCodepoints: HiddenUnicodeCodepoints, 231 | IncompleteFeatures: IncompleteFeatures, 232 | RedundantSemicolons: RedundantSemicolons, 233 | UnusedDocComment: UnusedDocComment, 234 | } 235 | impl BuiltinCombinedEarlyLintPass { 236 | pub fn new() -> Self { 237 | Self { 238 | UnusedParens: UnusedParens, 239 | UnusedBraces: UnusedBraces, 240 | UnusedImportBraces: UnusedImportBraces, 241 | UnsafeCode: UnsafeCode, 242 | AnonymousParameters: AnonymousParameters, 243 | EllipsisInclusiveRangePatterns: EllipsisInclusiveRangePatterns::default(), 244 | NonCamelCaseTypes: NonCamelCaseTypes, 245 | DeprecatedAttr: DeprecatedAttr::new(), 246 | WhileTrue: WhileTrue, 247 | NonAsciiIdents: NonAsciiIdents, 248 | HiddenUnicodeCodepoints: HiddenUnicodeCodepoints, 249 | IncompleteFeatures: IncompleteFeatures, 250 | RedundantSemicolons: RedundantSemicolons, 251 | UnusedDocComment: UnusedDocComment, 252 | } 253 | } 254 | pub fn get_lints() -> LintArray { 255 | let mut lints = Vec::new(); 256 | lints.extend_from_slice(&UnusedParens::get_lints()); 257 | lints.extend_from_slice(&UnusedBraces::get_lints()); 258 | lints.extend_from_slice(&UnusedImportBraces::get_lints()); 259 | lints.extend_from_slice(&UnsafeCode::get_lints()); 260 | lints.extend_from_slice(&AnonymousParameters::get_lints()); 261 | lints.extend_from_slice(&EllipsisInclusiveRangePatterns::get_lints()); 262 | lints.extend_from_slice(&NonCamelCaseTypes::get_lints()); 263 | lints.extend_from_slice(&DeprecatedAttr::get_lints()); 264 | lints.extend_from_slice(&WhileTrue::get_lints()); 265 | lints.extend_from_slice(&NonAsciiIdents::get_lints()); 266 | lints.extend_from_slice(&HiddenUnicodeCodepoints::get_lints()); 267 | lints.extend_from_slice(&IncompleteFeatures::get_lints()); 268 | lints.extend_from_slice(&RedundantSemicolons::get_lints()); 269 | lints.extend_from_slice(&UnusedDocComment::get_lints()); 270 | 271 | lints 272 | } 273 | } 274 | impl EarlyLintPass for BuiltinCombinedEarlyLintPass { 275 | expand_combined_early_lint_pass_methods!([$($passes),*], $methods); 276 | } 277 | #[allow(rustc::lint_pass_impl_without_macro)] 278 | impl LintPass for BuiltinCombinedEarlyLintPass { 279 | fn name(&self) -> &'static str { 280 | panic!() 281 | } 282 | } 283 | ``` 284 | 285 | ##### expand_combined_early_lint_pass_methods 286 | 287 | ```rust 288 | macro_rules! expand_combined_early_lint_pass_methods { 289 | ($passes:tt, [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => ( 290 | $(fn $name(&mut self, context: &EarlyContext<'_>, $($param: $arg),*) { 291 | expand_combined_early_lint_pass_method!($passes, self, $name, (context, $($param),*)); 292 | })* 293 | ) 294 | } 295 | ``` 296 | 297 | expand_combined_early_lint_pass_methods宏在 `BuiltinCombinedEarlyLintPass` 中展开所有 `early_lint_methods` 中定义的方法。 298 | 通过这个宏,`BuiltinCombinedEarlyLintPass`的定义变为(省略其他定义): 299 | 300 | ```rust 301 | impl EarlyLintPass for BuiltinCombinedEarlyLintPass { 302 | fn check_param(&mut self, context: &EarlyContext<'_>, a: &ast::Param) { 303 | expand_combined_early_lint_pass_method!($passes, self, $name, (context, $($param),*)); 304 | } 305 | fn check_ident(&mut self, context: &EarlyContext<'_>, a: &ast::Ident) { 306 | expand_combined_early_lint_pass_method!($passes, self, $name, (context, $($param),*)); 307 | } 308 | fn check_crate(&mut self, context: &EarlyContext<'_>, a: &ast::Crate) { 309 | expand_combined_early_lint_pass_method!($passes, self, $name, (context, $($param),*)); 310 | } 311 | ... 312 | 313 | } 314 | ``` 315 | 316 | ##### expand_combined_early_lint_pass_method 317 | 318 | ```rust 319 | macro_rules! expand_combined_early_lint_pass_method { 320 | ([$($passes:ident),*], $self: ident, $name: ident, $params:tt) => ({ 321 | $($self.$passes.$name $params;)* 322 | }) 323 | } 324 | ``` 325 | 326 | expand_combined_early_lint_pass_method:在展开的`check_*` 函数中调用每一个 `LintPass` 的 `check_*`。 327 | 通过这个宏,`BuiltinCombinedEarlyLintPass`的定义变为(省略其他定义): 328 | 329 | ```rust 330 | impl EarlyLintPass for BuiltinCombinedEarlyLintPass { 331 | fn check_param(&mut self, context: &EarlyContext<'_>, a: &ast::Param) { 332 | self.UnusedParens.check_param(context, a); 333 | self.UnusedBraces.check_param(context, a); 334 | self.UnusedImportBraces.check_param(context, a); 335 | ... 336 | } 337 | fn check_ident(&mut self, context: &EarlyContext<'_>, a: &ast::Ident) { 338 | self.UnusedParens.check_ident(context, a); 339 | self.UnusedBraces.check_ident(context, a); 340 | self.UnusedImportBraces.check_ident(context, a); 341 | ... 342 | } 343 | fn check_crate(&mut self, context: &EarlyContext<'_>, a: &ast::Crate) { 344 | self.UnusedParens.check_crate(context, a); 345 | self.UnusedBraces.check_crate(context, a); 346 | self.UnusedImportBraces.check_crate(context, a); 347 | ... 348 | } 349 | ... 350 | 351 | } 352 | ``` 353 | 354 | #### BuiltinCombinedEarlyLintPass 的最终定义 355 | 356 | 通过以上宏的展开,`BuiltinCombinedEarlyLintPass`的定义实际为如下形式: 357 | 358 | ```rust 359 | pub struct BuiltinCombinedEarlyLintPass { 360 | UnusedParens: UnusedParens, 361 | UnusedBraces: UnusedBraces, 362 | ... 363 | } 364 | 365 | impl BuiltinCombinedEarlyLintPass{ 366 | pub fn new() -> Self { 367 | UnusedParens: UnusedParens, 368 | UnusedBraces: UnusedBraces, 369 | ... 370 | } 371 | 372 | pub fn get_lints() -> LintArray { 373 | let mut lints = Vec::new(); 374 | lints.extend_from_slice(&UnusedParens::get_lints()); 375 | lints.extend_from_slice(&UnusedBraces::get_lints()); 376 | ... 377 | lints 378 | } 379 | } 380 | 381 | impl EarlyLintPass for BuiltinCombinedEarlyLintPass { 382 | fn check_crates(&mut self, context: &EarlyContext<'_>, a: &ast::Crate){ 383 | self.UnusedParens.check_crates (context, a); 384 | self.UnusedBraces.check_crates (context, a); 385 | ... 386 | } 387 | fn check_ident(&mut self, context: &EarlyContext<'_>, a: Ident){ 388 | self.UnusedParens.check_ident (context, a); 389 | self.UnusedBraces.check_ident (context, a); 390 | ... 391 | } 392 | .. 393 | } 394 | ``` 395 | 396 | 通过这个定义,可以在遍历 AST 时使用 `BuiltinCombinedEarlyLintPass` 的 `check_*` 方法实现多个 lintpass 的检查。 397 | 398 | ## Lint 的进一步优化 399 | 400 | 基于 CombinedLintPass ,可以对上一篇文章中提出的 Linter 的设计做进一步优化。 401 | ![Linter](./images/linter.jpg) 402 | 403 | 这里,可以用 CombinedLintPass 的`check_*` 方法,在 Visitor 遍历 AST 时执行对应的检查。虽然效果与之前一致,但因为宏的关系,所有的 `check_*` 方法和需要执行的 lintpass 都被收集到了一个结构中,也更容易管理。同样的,因为 CombinedLintPass 实际上调用的是每个 lintpass 各自的 check 方法,虽然调用起来可能下图一样很复杂,但因为 lintpass 中定义的 check 方法大部分是由宏生成的空检查,所以也不会造成性能上的损失。 404 | ![调用关系](./images/combinedlintpass.jpg) 405 | 406 | ## 总结 407 | 408 | 本文简单介绍了 Rustc 源码中关于 `CombinedLintPass` 这一结构的定义和实现 ,并以此进一步优化 Linter 的设计。希望能够对理解 Rustc 及 Lint 有所帮助,如有错误,欢迎指正。后续的文章将继续介绍 Rustc 中 Lint 在编译过程中的注册和执行过程,期待继续关注。 409 | 410 | ## Ref 411 | 412 | - KusionStack: [https://github.com/KusionStack](https://github.com/KusionStack) 413 | - Rustc: [https://github.com/rust-lang/rust](https://github.com/rust-lang/rust) 414 | - rustc-dev-guide: [https://rustc-dev-guide.rust-lang.org/](https://rustc-dev-guide.rust-lang.org/) 415 | - Rust Visitor: [https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/visit/index.html](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/visit/index.html) 416 | - Rust Clippy: [https://github.com/rust-lang/rust-clippy](https://github.com/rust-lang/rust-clippy) 417 | -------------------------------------------------------------------------------- /rustc/sema/lint/3.Execution process.md: -------------------------------------------------------------------------------- 1 | # Lint 的执行流程 2 | 3 | 前面两篇介绍了 `Lint`,`LintPass` 和 `CombinedLintPass` 几个结构的实现,并以这些结构写了一个 Lint 的伪代码实现。 4 | 5 | ```rust 6 | impl ast_visit::Visitor for Linter { 7 | fn visit_crate(a: ast:crate){ 8 | combinedlintpass.check_crate(a); 9 | walk_crate(a); 10 | } 11 | fn visit_stmt(a: ast:stmt){ 12 | combinedlintpass.check_stmt(a) 13 | walk_stmt(a); 14 | } 15 | ... 16 | } 17 | 18 | let linter = Linter::new(); 19 | 20 | for c in crates{ 21 | linter.visit_crate(c); 22 | } 23 | ``` 24 | 25 | 本文继续介绍 Rustc 中 `Lint` 的实现,包含 Lint 在编译流程中执行的流程,上述伪代码在 Rustc 中的对应实现,以及编译过程中的一个特殊参数 `no_interleave_lints`。 26 | 27 | ## Rustc 中 Lint 的执行阶段 28 | 29 | Rustc 的设计与经典编译器的设计基本无异,包含词法分析、语法分析、语义分析、生成IR、IR优化和代码生成等流程,但针对 Rust 的语言特性,还加入了一些特有的流程,如借用检查。对应的,代码在整个编译流程中的中间表示也有一定的扩展: 30 | 31 | - Token stream:Lexer 将源代码的字符流转化为词法单元(token) 流,这些词法单元被传递给下一个步骤,即语法分析。 32 | - Abstract Syntax Tree(AST):Parser 将 Token 流转换为抽象语法树(AST),抽象语法树几乎可以完全描述源代码中所写的内容。在 AST 上,Rustc 还执行了宏扩展、 early lint 等过程。 33 | - High-level IR(HIR):这是一种脱糖的 AST。它仍与源代码中的内容非常接近,但它包含一些隐含的东西,例如一些省略的生命周期等。这个 IR 适合类型检查。late lint也在类型检查之后进行。 34 | - Typed HIR(THIR):THIR 与 HIR 类似,但它携带了类型信息,并且更加脱糖(例如,函数调用和隐式的间接引用都会变成完全显式)。 35 | - Middle-level IR(MIR):MIR 基本上是一个控制流图(Control-Flow Graph)。CFG 是程序执行过程的抽象表现,代表了程序执行过程中会遍历到的所有路径。它用图的形式表示一个过程内所有基本块可能流向。Rustc 在 MIR 上除了基础的基于 CFG 的静态分析和 IR 优化外,还进行了 Rust 中所有权的借用检查。 36 | - LLVM IR:Rustc 的后端采用了 LLVM,因此,Rustc 会将 MIR 进一步转化为 LLVM IR 并传递给 LLVM 做进一步优化和代码生成的工作。 37 | 38 | 以上 Rust 代码的中间表示的转化流程也反映了 Rust 整个编译的流程,总结为一张图: 39 | ![编译流程](images/st0008-01.jpg) 40 | Rustc 中的 `rustc_driver::lib.rs` 中控制了编译流程的各个阶段: 41 | 42 | ```bash 43 | fn run_compiler(...) -> interface::Result<()> { 44 | ... 45 | interface::run_compiler(config, |compiler| { 46 | let linker = compiler.enter(|queries| { 47 | ... 48 | queries.parse()?; // lexer parse 49 | ... 50 | queries.expansion()?; // resolver 51 | ... 52 | queries.prepare_outputs()?; 53 | ... 54 | queries.global_ctxt()?; // ast -> hir 55 | ... 56 | queries.ongoing_codegen()?; 57 | ... 58 | } 59 | } 60 | ``` 61 | 62 | 前面介绍过,Rustc 中的 Lint 包含 early 和 late 两种,它们分别在 AST -> HIR 和 HIR -> THIR 两个阶段执行。这里我们同样以 `WhileTrue` 这个例子去看 Lint 从定义、到注册,最后执行的完整的流程。同时,`WhileTrue` 是 builtin 的 early lint 其中的一种,被包含在 `BuiltinCombinedEarlyLintPass` 之中。 63 | 64 | ## 定义 65 | 66 | 首先是 `WhileTrue`的 lint 和对应的 lintpass 的定义,它们被定义在 `rustc_lint/src/builtin.rs` 中 67 | 68 | ```rust 69 | declare_lint! { 70 | /// The `while_true` lint detects `while true { }`. 71 | /// 72 | /// ### Example 73 | /// 74 | /// ```rust,no_run 75 | /// while true { 76 | /// 77 | /// } 78 | /// ``` 79 | /// 80 | /// {{produces}} 81 | /// 82 | /// ### Explanation 83 | /// 84 | /// `while true` should be replaced with `loop`. A `loop` expression is 85 | /// the preferred way to write an infinite loop because it more directly 86 | /// expresses the intent of the loop. 87 | WHILE_TRUE, 88 | Warn, 89 | "suggest using `loop { }` instead of `while true { }`" 90 | } 91 | 92 | declare_lint_pass!(WhileTrue => [WHILE_TRUE]); 93 | 94 | impl EarlyLintPass for WhileTrue { 95 | fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) { 96 | ... 97 | } 98 | } 99 | ``` 100 | 101 | 与前面的介绍一样: 102 | 103 | 1. `declare_lint` 宏声明一个 lint:`WHILE_TRUE` 104 | 1. `declare_lint_pass` 宏声明一个lintpass:`WhileTrue` 105 | 1. 为 `WhileTrue` 实现 `EarlyLintPass` 中对应的检查方法,因为此 lintpass 只检查 Expr 节点,所以只需要实现 `check_expr()`函数即可。 106 | 107 | ## 注册 108 | 109 | 注册是指编译过程中将 Lint 加入到 LintStore 的过程。`WhileTrue` 不需要单独的注册和执行,它的检查方法通过宏扩展的方式展开到 `BuiltinCombinedEarlyLintPass` 中。`BuiltinCombinedEarlyLintPass` 的注册和执行都发生在 `queries.expansion()` 函数中。 110 | 111 | ```rust 112 | pub fn expansion( 113 | &self, 114 | ) -> Result<&Query<(Rc, Rc>, Lrc)>> { 115 | tracing::trace!("expansion"); 116 | self.expansion.compute(|| { 117 | let crate_name = self.crate_name()?.peek().clone(); 118 | // 注册 119 | let (krate, lint_store) = self.register_plugins()?.take(); 120 | let _timer = self.session().timer("configure_and_expand"); 121 | let sess = self.session(); 122 | let mut resolver = passes::create_resolver( 123 | sess.clone(), 124 | self.codegen_backend().metadata_loader(), 125 | &krate, 126 | &crate_name, 127 | ); 128 | let krate = resolver.access(|resolver| { 129 | // 执行 130 | passes::configure_and_expand(sess, &lint_store, krate, &crate_name, resolver) 131 | })?; 132 | Ok((Rc::new(krate), Rc::new(RefCell::new(resolver)), lint_store)) 133 | }) 134 | } 135 | ``` 136 | 137 | 注册的过程会生成定义的 lint 的结构并添加到 [LintStore](https://rustc-dev-guide.rust-lang.org/diagnostics/lintstore.html) 中。Lint 整体上被分为4个种类:pre-expansion, early, late, late-module。尽管 Lint 对应的 LintPass 在编译流程中执行的阶段不同,但注册都是发生在同一个阶段。 138 | Lint 注册过程的函数调用链路如下: 139 | 140 | - rustc_driver::lib::run_compiler() 141 | - rustc_interface::queries::Queries.expansion() 142 | - rustc_interface::queries::Queries.register_plugins() 143 | - rustc_lint::lib::new_lint_store() 144 | - rustc_lint::lib::register_builtins() 145 | 146 | 在这里,默认的编译流程会执行 else{} 分支中的语句,BuiltinCombinedEarlyLintPass::get_lints() 会生成 `WHILE_TRUE` 并添加到 LintStore中。 147 | 148 | ```rust 149 | if no_interleave_lints { 150 | pre_expansion_lint_passes!(register_passes, register_pre_expansion_pass); 151 | early_lint_passes!(register_passes, register_early_pass); 152 | late_lint_passes!(register_passes, register_late_pass); 153 | late_lint_mod_passes!(register_passes, register_late_mod_pass); 154 | } else { 155 | store.register_lints(&BuiltinCombinedPreExpansionLintPass::get_lints()); 156 | store.register_lints(&BuiltinCombinedEarlyLintPass::get_lints()); 157 | store.register_lints(&BuiltinCombinedModuleLateLintPass::get_lints()); 158 | store.register_lints(&BuiltinCombinedLateLintPass::get_lints()); 159 | } 160 | ``` 161 | 162 | ## 执行 163 | 164 | 不同的 LintPass 的执行过程发生在编译过程的不同阶段,其中,`BuiltinCombinedEarlyLintPass` 执行过程的函数调用链路如下: 165 | 166 | - rustc_driver::lib::run_compiler() 167 | - rustc_interface::queries::Queries.expansion() 168 | - rustc_interface::passes::configure_and_expand() 169 | - rustc_lint::early::check_ast_node() 170 | - rustc_lint::early::early_lint_node() 171 | 172 | 首先,在 configure_and_expand() 函数中,执行了 pre-expansion 和 early 两种 lintpass。注册时使用了 BuiltinCombinedEarlyLintPass::get_lints() 方法生成 lints,而这里用 BuiltinCombinedEarlyLintPass::new() 方法生成了 lintpass。 173 | 174 | ```rust 175 | pub fn configure_and_expand( 176 | sess: &Session, 177 | lint_store: &LintStore, 178 | mut krate: ast::Crate, 179 | crate_name: &str, 180 | resolver: &mut Resolver<'_>, 181 | ) -> Result { 182 | pre_expansion_lint(sess, lint_store, resolver.registered_tools(), &krate, crate_name); 183 | ... 184 | sess.time("early_lint_checks", || { 185 | let lint_buffer = Some(std::mem::take(resolver.lint_buffer())); 186 | rustc_lint::check_ast_node( 187 | sess, 188 | false, 189 | lint_store, 190 | resolver.registered_tools(), 191 | lint_buffer, 192 | rustc_lint::BuiltinCombinedEarlyLintPass::new(), 193 | &krate, 194 | ) 195 | }); 196 | } 197 | ``` 198 | 199 | Lint 的执行最终发生在 `rustc_lint::early::early_lint_node()` 函数中。比较 `early_lint_node()` 函数和 `CombinedLintPass` 一节最后的伪代码: 200 | 201 | ![early_lint_node与CombinedLintPass](image/../images/st0008-02.jpg) 202 | 203 | 它们之间有以下的对应关系: 204 | 205 | - 参数 pass 是 configure_and_expand() 函数中新建的 BuiltinCombinedEarlyLintPass,它对应 combinedlintpass。 206 | - EarlyContextAndPass 将 pass 与 context 信息组合在一起,并且实现了 visitor,它对应 Linter。 207 | - check_node.check(cx) 调用了 cx.pass.check_crate() 进行 lint 检查,根据 BuiltinCombinedEarlyLintPass 的定义, 这个函数中会调用所有 builtin early lint 的 check_crate() 方法,然后执行 ast_visit::walk_crate() 遍历子节点,它对应了 visit_crate()。 208 | 209 | ## no_interleave_lints 210 | 211 | 虽然 Rustc 中考虑性能因素,将 LintPass 组合成 CombinedLintPass,但提供了一些编译参数去配置 Lint。其中,Lint 的注册和执行过程中都用到了 no_interleave_lints 参数。这个参数默认为 false,表示是否单独执行每一个 lint。编译时将这个修改这个参数就可以单独注册每一个 lint 以及单独执行 lintpass,这样的设计提供了更好的灵活性和自定义的能力(比如,可以对每一个 lint 单独做 benchmark)。 212 | 213 | ```rust 214 | if no_interleave_lints { 215 | pre_expansion_lint_passes!(register_passes, register_pre_expansion_pass); 216 | early_lint_passes!(register_passes, register_early_pass); 217 | late_lint_passes!(register_passes, register_late_pass); 218 | late_lint_mod_passes!(register_passes, register_late_mod_pass); 219 | } else { 220 | store.register_lints(&BuiltinCombinedPreExpansionLintPass::get_lints()); 221 | store.register_lints(&BuiltinCombinedEarlyLintPass::get_lints()); 222 | store.register_lints(&BuiltinCombinedModuleLateLintPass::get_lints()); 223 | store.register_lints(&BuiltinCombinedLateLintPass::get_lints()); 224 | } 225 | ``` 226 | 227 | ```rust 228 | pub fn check_ast_node<'a>(...) { 229 | if sess.opts.debugging_opts.no_interleave_lints { 230 | for (i, pass) in passes.iter_mut().enumerate() { 231 | buffered = 232 | sess.prof.extra_verbose_generic_activity("run_lint", pass.name()).run(|| { 233 | early_lint_node( 234 | sess, 235 | !pre_expansion && i == 0, 236 | lint_store, 237 | registered_tools, 238 | buffered, 239 | EarlyLintPassObjects { lints: slice::from_mut(pass) }, 240 | check_node, 241 | ) 242 | }); 243 | } 244 | } else { 245 | buffered = early_lint_node( 246 | sess, 247 | !pre_expansion, 248 | lint_store, 249 | registered_tools, 250 | buffered, 251 | builtin_lints, 252 | check_node, 253 | ); 254 | ... 255 | } 256 | } 257 | ``` 258 | 259 | ## 总结 260 | 261 | 至此,我们就分析了 Rustc 中一个 Lint 定义、实现对应的检查(LintPass)、注册、最终执行的完整流程。我们也可以利用这些宏,去定义新的Lint和LintPass(Clippy 中也是以相似的方式)。当然,Rustc 中关于 Lint 的部分远远不止这些,我只是分享了其中我能理解的一小部分,希望能够对大家有所帮助。 262 | 263 | 除此之外,我们在 [KCLVM](https://github.com/KusionStack/KCLVM) 这个项目中,也有对这部分内容的应用与实践,可以在这个 [Issue](https://github.com/KusionStack/KCLVM/issues/109) 和 [PR](https://github.com/KusionStack/KCLVM/pull/160) 看到更为详细的设计方案和具体实现,包含了visitor模式,lint、lintpass、combinedlintpass的定义,在resolver阶段调用lint检查等实现,欢迎批评指正。 264 | -------------------------------------------------------------------------------- /rustc/sema/lint/images/combinedlintpass.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-kusion/rust-code-book/7cf5b600b88117f641b38a1ec45a7c8a9c5d7f59/rustc/sema/lint/images/combinedlintpass.jpg -------------------------------------------------------------------------------- /rustc/sema/lint/images/complication_process.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-kusion/rust-code-book/7cf5b600b88117f641b38a1ec45a7c8a9c5d7f59/rustc/sema/lint/images/complication_process.jpg -------------------------------------------------------------------------------- /rustc/sema/lint/images/early_lint_node.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-kusion/rust-code-book/7cf5b600b88117f641b38a1ec45a7c8a9c5d7f59/rustc/sema/lint/images/early_lint_node.jpg -------------------------------------------------------------------------------- /rustc/sema/lint/images/lint_lintpass.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-kusion/rust-code-book/7cf5b600b88117f641b38a1ec45a7c8a9c5d7f59/rustc/sema/lint/images/lint_lintpass.jpeg -------------------------------------------------------------------------------- /rustc/sema/lint/images/linter.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-kusion/rust-code-book/7cf5b600b88117f641b38a1ec45a7c8a9c5d7f59/rustc/sema/lint/images/linter.jpg -------------------------------------------------------------------------------- /rustc/sema/lint/readme.md: -------------------------------------------------------------------------------- 1 | # Lint 2 | 3 | ## Background 4 | 5 | Lint is a kind of static analysis tool, which originated from the C language. Lint tools usually check potential problems and errors in code, including (but not limited to) programming style (indentation, blank lines, spaces), code quality (unused variables, missing documents), and error codes (division by zero, duplicate definitions, circular references). Generally speaking, in addition to identifying errors, lint tools also have some fix/refactor suggest and auto fix capabilities. Using lint tools in the project can effectively reduce errors and improve the project quality. In addition, for a programming language, the lint tool is usually a prerequisite for the development of other tools, such as the error prompt of IDE plug-ins(e.g., LSP) and the pipeline detection of CI. 6 | 7 | ## Lint vs. LintPass 8 | 9 | ### Concepts 10 | 11 | There are two main structures about lint in Rustc, `Lint` and `LintPass`. First, we need to distinguish the concepts of Lint and LintPass. In many documents of Rustc, they are both referred to as 'Lint', which is easy to confuse. The difference between them is explained by rustc-dev-guide as follows: 12 | 13 | > Lint declarations don't carry any "state" - they are merely global identifiers and descriptions of lints. We assert at runtime that they are not registered twice (by lint name). 14 | Lint passes are the meat of any lint. 15 | 16 | In terms of definition, `Lint` is just a description of the lint check defined, such as name, level, description, code and other attributes. It doesn't carry any state of checking. Rustc checks the uniqueness of registered lints at runtime. `LintPass` is an implementation of `lint`, which contains the `check_*` methods that are called when checking. 17 | 18 | In terms of code implementation, `Lint` is defined as a struct in Rust, and all lint definitions are an instance of this struct. And `LintPass` is a trait. The `Trait` is similar to the interface in Java/C++. Every definition of lintpass needs to implement the methods defined in the interface. 19 | 20 | ```rust 21 | /// Specification of a single lint. 22 | #[derive(Copy, Clone, Debug)] 23 | pub struct Lint { 24 | pub name: &'static str, 25 | /// Default level for the lint. 26 | pub default_level: Level, 27 | /// Description of the lint or the issue it detects. 28 | /// 29 | /// e.g., "imports that are never used" 30 | pub desc: &'static str, 31 | ... 32 | } 33 | 34 | pub trait LintPass { 35 | fn name(&self) -> &'static str; 36 | } 37 | ``` 38 | 39 | It should be noted that although we just said that `trait` is similar to an interface and `Lint` is a struct, the relationship between `Lint` and `LintPass` is not a "class" and its "methods" in OO. Instead, declaring `LintPass` will generate a struct with the same name, this struct implements the trait, and the `get_lints()` method in this struct will generate the corresponding `Lint` definition. 40 | 41 | ![lint vs. lintpass](./images/lint_lintpass.jpeg) 42 | 43 | This is also consistent with the description of the rustc-dev-guide: 44 | 45 | > A lint might not have any lint pass that emits it, it could have many, or just one -- the compiler doesn't track whether a pass is in any way associated with a particular lint, and frequently lints are emitted as part of other work (e.g., type checking, etc.). 46 | 47 | ### Definition of Lint and LintPass 48 | 49 | Rustc provides macros for both Lint and LintPass to define their structure. 50 | The macro `declare_lint` that defines Lint is simple, it can be found in `rustc_lint_defs::lib.rs`. The `declare_lint` macro parses the input arguments and produces a Lint struct named `$NAME`. 51 | 52 | ```rust 53 | #[macro_export] 54 | macro_rules! declare_lint { 55 | ($(#[$attr:meta])* $vis: vis $NAME: ident, $Level: ident, $desc: expr) => ( 56 | $crate::declare_lint!( 57 | $(#[$attr])* $vis $NAME, $Level, $desc, 58 | ); 59 | ); 60 | ($(#[$attr:meta])* $vis: vis $NAME: ident, $Level: ident, $desc: expr, 61 | $(@feature_gate = $gate:expr;)? 62 | $(@future_incompatible = FutureIncompatibleInfo { $($field:ident : $val:expr),* $(,)* }; )? 63 | $($v:ident),*) => ( 64 | $(#[$attr])* 65 | $vis static $NAME: &$crate::Lint = &$crate::Lint { 66 | name: stringify!($NAME), 67 | default_level: $crate::$Level, 68 | desc: $desc, 69 | edition_lint_opts: None, 70 | is_plugin: false, 71 | $($v: true,)* 72 | $(feature_gate: Some($gate),)* 73 | $(future_incompatible: Some($crate::FutureIncompatibleInfo { 74 | $($field: $val,)* 75 | ..$crate::FutureIncompatibleInfo::default_fields_for_macro() 76 | }),)* 77 | ..$crate::Lint::default_fields_for_macro() 78 | }; 79 | ); 80 | ($(#[$attr:meta])* $vis: vis $NAME: ident, $Level: ident, $desc: expr, 81 | $lint_edition: expr => $edition_level: ident 82 | ) => ( 83 | $(#[$attr])* 84 | $vis static $NAME: &$crate::Lint = &$crate::Lint { 85 | name: stringify!($NAME), 86 | default_level: $crate::$Level, 87 | desc: $desc, 88 | edition_lint_opts: Some(($lint_edition, $crate::Level::$edition_level)), 89 | report_in_external_macro: false, 90 | is_plugin: false, 91 | }; 92 | ); 93 | } 94 | ``` 95 | 96 | The definition of LintPass involves two macros: 97 | 98 | - declare_lint_pass: Generate a struct named `$name` and call the macro `impl_lint_pass`. 99 | 100 | ```rust 101 | macro_rules! declare_lint_pass { 102 | ($(#[$m:meta])* $name:ident => [$($lint:expr),* $(,)?]) => { 103 | $(#[$m])* #[derive(Copy, Clone)] pub struct $name; 104 | $crate::impl_lint_pass!($name => [$($lint),*]); 105 | }; 106 | } 107 | ``` 108 | 109 | - impl_lint_pass: Implements the `fn name()` and `fn get_lints()` methods for the generated `LintPass` structure. 110 | 111 | ```rust 112 | macro_rules! impl_lint_pass { 113 | ($ty:ty => [$($lint:expr),* $(,)?]) => { 114 | impl $crate::LintPass for $ty { 115 | fn name(&self) -> &'static str { stringify!($ty) } 116 | } 117 | impl $ty { 118 | pub fn get_lints() -> $crate::LintArray { $crate::lint_array!($($lint),*) } 119 | } 120 | }; 121 | } 122 | ``` 123 | 124 | ### EarlyLintPass and LateLintPass 125 | 126 | In the macro definition of `LintPass`, only the `fn name()` and `fn get_lints()` methods are defined, but the `check_*` functions for checking are not provided. This is because Rustc divides `LintPass` into two more specific categories: `EarlyLintPass` and `LateLintPass`. The main difference is whether the checked element has type information, i.e. is performed before or after the type checking. For example, `WhileTrue` checks for `while true{...}` in the code and prompts the user to use `loop{...}` instead it. This check does not require any type information and is therefore defined as an `EarlyLint` (`impl EarlyLintPass for WhileTrue` in the code. 127 | 128 | ```rust 129 | declare_lint! { 130 | WHILE_TRUE, 131 | Warn, 132 | "suggest using `loop { }` instead of `while true { }`" 133 | } 134 | 135 | declare_lint_pass!(WhileTrue => [WHILE_TRUE]); 136 | 137 | impl EarlyLintPass for WhileTrue { 138 | fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) { 139 | ... 140 | } 141 | } 142 | ``` 143 | 144 | Rustc uses 3 macros to define `EarlyLintPass`: 145 | 146 | - early_lint_methods: early_lint_methods defines the `check_*` functions that need to be implemented in `EarlyLintPass`, and passes these functions and the received parameters `$args` to the next macro. 147 | 148 | ```rust 149 | macro_rules! early_lint_methods { 150 | ($macro:path, $args:tt) => ( 151 | $macro!($args, [ 152 | fn check_param(a: &ast::Param); 153 | fn check_ident(a: &ast::Ident); 154 | fn check_crate(a: &ast::Crate); 155 | fn check_crate_post(a: &ast::Crate); 156 | ... 157 | ]); 158 | ) 159 | } 160 | ``` 161 | 162 | - declare_early_lint_pass: Generate trait `EarlyLintPass` and call macro `expand_early_lint_pass_methods`. 163 | 164 | ```rust 165 | macro_rules! declare_early_lint_pass { 166 | ([], [$($methods:tt)*]) => ( 167 | pub trait EarlyLintPass: LintPass { 168 | expand_early_lint_pass_methods!(&EarlyContext<'_>, [$($methods)*]); 169 | } 170 | ) 171 | } 172 | ``` 173 | 174 | - expand_early_lint_pass_methods: Provides default implementations for `check_*` methods: nothing to do(`{}` in code). 175 | 176 | ```rust 177 | macro_rules! expand_early_lint_pass_methods { 178 | ($context:ty, [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => ( 179 | $(#[inline(always)] fn $name(&mut self, _: $context, $(_: $arg),*) {})* 180 | ) 181 | } 182 | ``` 183 | 184 | The benefits are as follows: 185 | 186 | 1. Because `LintPass` is a trait, every definition of `LintPass` needs to implement all of its methods. But early lint and late lint occur at different stages of compilation, and the input parameters are also different (AST and HIR). Therefore, the definition of LintPass contains only two general methods `fn name()` and `fn get_lints()`. The check methods are defined in the more specific `EarlyLintPass` and `LateLintPass`. 187 | 2. Likewise, for `EarlyLintPass`, every definition of lintpass must implement all of its methods. But not every lintpass needs to check all nodes of the AST. `expand_early_lint_pass_methods` provides default implementations for its methods. In this way, when defining a specific lintpass, you only need to pay attention to implementing its related check methods. For example, for the definition of `WhileTrue`, since `while true { }` only appears in the `ast::Expr` node, it only needs to implement the `check_expr` function. Calling the `WhileTrue` check function at any other node, such as call `WhileTrue.check_ident()` when checking an identifier node on the AST, will only execute an empty method as defined in the macro `expand_early_lint_pass_methods`. 188 | 189 | ### Meaning of pass 190 | 191 | In Rustc, in addition to `Lint` and `LintPass`, there are some `*Pass` naming, such as `Mir` and `MirPass`, the `rustc_passes` package, etc. The **Compilers, Principles, Techniques, & Tools** has a corresponding explanation for Pass: 192 | 193 | > 1.2.8 Combine multiple steps into a pass 194 | The previous discussion of steps was about the logical organization of a compiler. In a particular implementation, the activities of multiple steps can be combined into a pass. Each pass reads in an input file and produces an output file. 195 | 196 | In the macro `declare_lint_pass` that declares `LintPass`, its second parameter is a list, indicating that a lintpass can generate multiple lints. There are also some CombinedLintPass in Rustc that also aggregates all built-in lints into one lintpass. This is basically the same as the definition of "pass" in the Dragon Book: `LintPass` can combine multiple `Lint` checks, each LintPass reads an AST/HIR and produces a corresponding result. 197 | 198 | ## Simple design of Linter 199 | 200 | In the definition of LintPass, a default implementation is provided for all `check_*` methods of each lintpass. So far, we can implement a simple Linter tool: 201 | 202 | ```rust 203 | struct Linter { } 204 | impl ast_visit::Visitor for Linter { 205 | fn visit_crate(a: ast:crate){ 206 | for lintpass in lintpasses{ 207 | lintpass.check_crate(a) 208 | } 209 | walk_crate(); 210 | } 211 | fn visit_stmt(a: ast:stmt){ 212 | for lintpass in lintpasses{ 213 | lintpass.check_stmt(a) 214 | } 215 | walk_stmt(); 216 | } 217 | ... 218 | } 219 | 220 | let linter = Linter::new(); 221 | 222 | for c in crates{ 223 | linter.visit_crate(c); 224 | } 225 | ``` 226 | 227 | `Visitor` is a tool for traversing the AST. Here, the `visit_*` methods are implemented for Linter, and all lintpass `check_*` methods are called during traversal. `walk_*` will continue to call other `visit_*` methods to traverse its child nodes. So, for each crate, just call the `visit_crate()` function to traverse the AST and complete the lint check. 228 | 229 | ## CombinedLintpass 230 | 231 | However, Rustc and Clippy provide more than 550 lint definitions. Considering the performance, it is obviously inappropriate to define a large number of lintpasses, register and call them separately. Rustc provides a better solution: since multiple lints can be organized into one lintpass, multiple lintpasses can also be combined into a CombinedLintPass. 232 | > [Compiler lint passes are combined into one pass](https://rustc-dev-guide.rust-lang.org/diagnostics/lintstore.html#compiler-lint-passes-are-combined-into-one-pass) 233 | > Within the compiler, for performance reasons, we usually do not register dozens of lint passes. Instead, we have a single lint pass of each variety (e.g., BuiltinCombinedModuleLateLintPass) which will internally call all of the individual lint passes; this is because then we get the benefits of static over dynamic dispatch for each of the (often empty) trait methods. 234 | > Ideally, we'd not have to do this, since it adds to the complexity of understanding the code. However, with the current type-erased lint store approach, it is beneficial to do so for performance reasons. 235 | 236 | ### BuiltinCombinedEarlyLintPass 237 | 238 | Combinedlintpass is also divided into early and late. Take builtin's early lint as an example, `rustc_ lint::src::lib.rs` defines a `BuiltinCombinedEarlyLintPass` structure for these lintpasses. 239 | 240 | ```rust 241 | early_lint_passes!(declare_combined_early_pass, [BuiltinCombinedEarlyLintPass]); 242 | ``` 243 | 244 | Although this definition seems to have only one line, it summarizes 14 `LintPass` through the expansion of several macros, and each `LintPass` provides more than 50 'checks_*` method. Let's explain these macros one by one. 245 | 246 | #### Define BuiltinCombinedEarlyLintPass by macros 247 | 248 | - early_lint_passes 249 | 250 | ```rust 251 | macro_rules! early_lint_passes { 252 | ($macro:path, $args:tt) => { 253 | $macro!( 254 | $args, 255 | [ 256 | UnusedParens: UnusedParens, 257 | UnusedBraces: UnusedBraces, 258 | UnusedImportBraces: UnusedImportBraces, 259 | UnsafeCode: UnsafeCode, 260 | AnonymousParameters: AnonymousParameters, 261 | EllipsisInclusiveRangePatterns: EllipsisInclusiveRangePatterns::default(), 262 | NonCamelCaseTypes: NonCamelCaseTypes, 263 | DeprecatedAttr: DeprecatedAttr::new(), 264 | WhileTrue: WhileTrue, 265 | NonAsciiIdents: NonAsciiIdents, 266 | HiddenUnicodeCodepoints: HiddenUnicodeCodepoints, 267 | IncompleteFeatures: IncompleteFeatures, 268 | RedundantSemicolons: RedundantSemicolons, 269 | UnusedDocComment: UnusedDocComment, 270 | ] 271 | ); 272 | }; 273 | } 274 | ``` 275 | 276 | The first is the macro `early_ lint_ passes`. The main function of this macro is to define all early lintpass. The left side of `:`is the identifier of lintpass, and the right side of `:` is the constructor of lintpass. Therefore, `ellipseinclusiverangepatterns::default()` and `deprecedattr::new()` are differnet from others. `early_ lint_ passes` passes the defined early lintpass to the next macro together with the second parameter. 277 | 278 | Through this macro, the previous definition of `BuiltinCombinedEarlyLintPass` is expanded to: 279 | 280 | ```rust 281 | declare_combined_early_pass!([BuiltinCombinedEarlyLintPass], [ 282 | UnusedParens: UnusedParens, 283 | UnusedBraces: UnusedBraces, 284 | UnusedImportBraces: UnusedImportBraces, 285 | UnsafeCode: UnsafeCode, 286 | AnonymousParameters: AnonymousParameters, 287 | EllipsisInclusiveRangePatterns: EllipsisInclusiveRangePatterns::default(), 288 | NonCamelCaseTypes: NonCamelCaseTypes, 289 | DeprecatedAttr: DeprecatedAttr::new(), 290 | WhileTrue: WhileTrue, 291 | NonAsciiIdents: NonAsciiIdents, 292 | HiddenUnicodeCodepoints: HiddenUnicodeCodepoints, 293 | IncompleteFeatures: IncompleteFeatures, 294 | RedundantSemicolons: RedundantSemicolons, 295 | UnusedDocComment: UnusedDocComment, 296 | ]) 297 | ``` 298 | 299 | - declare_combined_early_pass 300 | 301 | ```rust 302 | macro_rules! declare_combined_early_pass { 303 | ([$name:ident], $passes:tt) => ( 304 | early_lint_methods!(declare_combined_early_lint_pass, [pub $name, $passes]); 305 | ) 306 | } 307 | ``` 308 | 309 | Macro `declare_combined_early_pass` receives the name (BuiltinCombinedEarlyLintPass) and passes from macro `early_lint_passes`, and continues to pass them to macro `early_lint_methods`. 310 | 311 | Through this macro, the definition of `BuiltinCombinedEarlyLintPass` expand to: 312 | 313 | ```rust 314 | early_lint_methods!(declare_combined_early_lint_pass, 315 | [pub BuiltinCombinedEarlyLintPass, 316 | [ 317 | UnusedParens: UnusedParens, 318 | UnusedBraces: UnusedBraces, 319 | UnusedImportBraces: UnusedImportBraces, 320 | UnsafeCode: UnsafeCode, 321 | AnonymousParameters: AnonymousParameters, 322 | EllipsisInclusiveRangePatterns: EllipsisInclusiveRangePatterns::default(), 323 | NonCamelCaseTypes: NonCamelCaseTypes, 324 | DeprecatedAttr: DeprecatedAttr::new(), 325 | WhileTrue: WhileTrue, 326 | NonAsciiIdents: NonAsciiIdents, 327 | HiddenUnicodeCodepoints: HiddenUnicodeCodepoints, 328 | IncompleteFeatures: IncompleteFeatures, 329 | RedundantSemicolons: RedundantSemicolons, 330 | UnusedDocComment: UnusedDocComment, 331 | ] 332 | ]); 333 | ``` 334 | 335 | - early_lint_methods 336 | 337 | ```rust 338 | macro_rules! early_lint_methods { 339 | ($macro:path, $args:tt) => ( 340 | $macro!($args, [ 341 | fn check_param(a: &ast::Param); 342 | fn check_ident(a: &ast::Ident); 343 | fn check_crate(a: &ast::Crate); 344 | fn check_crate_post(a: &ast::Crate); 345 | ... 346 | ]); 347 | ) 348 | } 349 | ``` 350 | 351 | Macro `early_lint_methods` has been explained earlier. It defines the methods `check_*` which need to be implemented in the `EarlyLintPass`, and pass these methods and the parameter `$args` to the next macro. Because `BuiltinCombinedEarlyLintPass` is also a kind of early lint, it is also necessary to implement these methods. 352 | 353 | Through this macro, the definition of 'BuiltinCombinedEarlyLintPass' expand to: 354 | 355 | ```rust 356 | declare_combined_early_lint_pass!( 357 | [pub BuiltinCombinedEarlyLintPass, 358 | [ 359 | UnusedParens: UnusedParens, 360 | UnusedBraces: UnusedBraces, 361 | UnusedImportBraces: UnusedImportBraces, 362 | UnsafeCode: UnsafeCode, 363 | AnonymousParameters: AnonymousParameters, 364 | EllipsisInclusiveRangePatterns: EllipsisInclusiveRangePatterns::default(), 365 | NonCamelCaseTypes: NonCamelCaseTypes, 366 | DeprecatedAttr: DeprecatedAttr::new(), 367 | WhileTrue: WhileTrue, 368 | NonAsciiIdents: NonAsciiIdents, 369 | HiddenUnicodeCodepoints: HiddenUnicodeCodepoints, 370 | IncompleteFeatures: IncompleteFeatures, 371 | RedundantSemicolons: RedundantSemicolons, 372 | UnusedDocComment: UnusedDocComment, 373 | ] 374 | ], 375 | [ 376 | fn check_param(a: &ast::Param); 377 | fn check_ident(a: &ast::Ident); 378 | fn check_crate(a: &ast::Crate); 379 | fn check_crate_post(a: &ast::Crate); 380 | ... 381 | ] 382 | ) 383 | ``` 384 | 385 | - declare_combined_early_lint_pass 386 | 387 | ```rust 388 | macro_rules! declare_combined_early_lint_pass { 389 | ([$v:vis $name:ident, [$($passes:ident: $constructor:expr,)*]], $methods:tt) => ( 390 | #[allow(non_snake_case)] 391 | $v struct $name { 392 | $($passes: $passes,)* 393 | } 394 | impl $name { 395 | $v fn new() -> Self { 396 | Self { 397 | $($passes: $constructor,)* 398 | } 399 | } 400 | $v fn get_lints() -> LintArray { 401 | let mut lints = Vec::new(); 402 | $(lints.extend_from_slice(&$passes::get_lints());)* 403 | lints 404 | } 405 | } 406 | impl EarlyLintPass for $name { 407 | expand_combined_early_lint_pass_methods!([$($passes),*], $methods); 408 | } 409 | #[allow(rustc::lint_pass_impl_without_macro)] 410 | impl LintPass for $name { 411 | fn name(&self) -> &'static str { 412 | panic!() 413 | } 414 | } 415 | ) 416 | } 417 | ``` 418 | 419 | Macro `declare_combined_early_lint_pass` is the main structure for generating `BuiltinCombinedEarlyLintPass`. It does the following works: 420 | 421 | - Generate a struct named `BuiltinCombinedEarlyLintPass`, whose fields is the identifier provided by macro `early_lint_passes`. 422 | - Implement methods `fn new()` `fn name()` and `fn get_lints()`. The method `new()` uses constructor of lintpass provided by marco `early_lint_passes`. 423 | - Call the marco `expand_combined_early_lint_pass_methods` to implememt self `check_*` methods. 424 | 425 | Through this macro, the definition of `BuiltinCombinedEarlyLintPass` is changed to: 426 | 427 | ```rust 428 | pub struct BuiltinCombinedEarlyLintPass { 429 | UnusedParens: UnusedParens, 430 | UnusedBraces: UnusedBraces, 431 | UnusedImportBraces: UnusedImportBraces, 432 | UnsafeCode: UnsafeCode, 433 | AnonymousParameters: AnonymousParameters, 434 | EllipsisInclusiveRangePatterns: EllipsisInclusiveRangePatterns, 435 | NonCamelCaseTypes: NonCamelCaseTypes, 436 | DeprecatedAttr: DeprecatedAttr, 437 | WhileTrue: WhileTrue, 438 | NonAsciiIdents: NonAsciiIdents, 439 | HiddenUnicodeCodepoints: HiddenUnicodeCodepoints, 440 | IncompleteFeatures: IncompleteFeatures, 441 | RedundantSemicolons: RedundantSemicolons, 442 | UnusedDocComment: UnusedDocComment, 443 | } 444 | impl BuiltinCombinedEarlyLintPass { 445 | pub fn new() -> Self { 446 | Self { 447 | UnusedParens: UnusedParens, 448 | UnusedBraces: UnusedBraces, 449 | UnusedImportBraces: UnusedImportBraces, 450 | UnsafeCode: UnsafeCode, 451 | AnonymousParameters: AnonymousParameters, 452 | EllipsisInclusiveRangePatterns: EllipsisInclusiveRangePatterns::default(), 453 | NonCamelCaseTypes: NonCamelCaseTypes, 454 | DeprecatedAttr: DeprecatedAttr::new(), 455 | WhileTrue: WhileTrue, 456 | NonAsciiIdents: NonAsciiIdents, 457 | HiddenUnicodeCodepoints: HiddenUnicodeCodepoints, 458 | IncompleteFeatures: IncompleteFeatures, 459 | RedundantSemicolons: RedundantSemicolons, 460 | UnusedDocComment: UnusedDocComment, 461 | } 462 | } 463 | pub fn get_lints() -> LintArray { 464 | let mut lints = Vec::new(); 465 | lints.extend_from_slice(&UnusedParens::get_lints()); 466 | lints.extend_from_slice(&UnusedBraces::get_lints()); 467 | lints.extend_from_slice(&UnusedImportBraces::get_lints()); 468 | lints.extend_from_slice(&UnsafeCode::get_lints()); 469 | lints.extend_from_slice(&AnonymousParameters::get_lints()); 470 | lints.extend_from_slice(&EllipsisInclusiveRangePatterns::get_lints()); 471 | lints.extend_from_slice(&NonCamelCaseTypes::get_lints()); 472 | lints.extend_from_slice(&DeprecatedAttr::get_lints()); 473 | lints.extend_from_slice(&WhileTrue::get_lints()); 474 | lints.extend_from_slice(&NonAsciiIdents::get_lints()); 475 | lints.extend_from_slice(&HiddenUnicodeCodepoints::get_lints()); 476 | lints.extend_from_slice(&IncompleteFeatures::get_lints()); 477 | lints.extend_from_slice(&RedundantSemicolons::get_lints()); 478 | lints.extend_from_slice(&UnusedDocComment::get_lints()); 479 | 480 | lints 481 | } 482 | } 483 | impl EarlyLintPass for BuiltinCombinedEarlyLintPass { 484 | expand_combined_early_lint_pass_methods!([$($passes),*], $methods); 485 | } 486 | #[allow(rustc::lint_pass_impl_without_macro)] 487 | impl LintPass for BuiltinCombinedEarlyLintPass { 488 | fn name(&self) -> &'static str { 489 | panic!() 490 | } 491 | } 492 | ``` 493 | 494 | - expand_combined_early_lint_pass_methods 495 | 496 | ```rust 497 | macro_rules! expand_combined_early_lint_pass_methods { 498 | ($passes:tt, [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => ( 499 | $(fn $name(&mut self, context: &EarlyContext<'_>, $($param: $arg),*) { 500 | expand_combined_early_lint_pass_method!($passes, self, $name, (context, $($param),*)); 501 | })* 502 | ) 503 | } 504 | ``` 505 | 506 | Marco `expand_combined_early_lint_pass_methods` explands all methods defined in `early_lint_methods`. 507 | 508 | Through this macro, the definition of `BuiltinCombinedEarlyLintPass` is changed to(ignore other definitions): 509 | 510 | ```rust 511 | impl EarlyLintPass for BuiltinCombinedEarlyLintPass { 512 | fn check_param(&mut self, context: &EarlyContext<'_>, a: &ast::Param) { 513 | expand_combined_early_lint_pass_method!($passes, self, $name, (context, $($param),*)); 514 | } 515 | fn check_ident(&mut self, context: &EarlyContext<'_>, a: &ast::Ident) { 516 | expand_combined_early_lint_pass_method!($passes, self, $name, (context, $($param),*)); 517 | } 518 | fn check_crate(&mut self, context: &EarlyContext<'_>, a: &ast::Crate) { 519 | expand_combined_early_lint_pass_method!($passes, self, $name, (context, $($param),*)); 520 | } 521 | ... 522 | 523 | } 524 | ``` 525 | 526 | - expand_combined_early_lint_pass_method 527 | 528 | ```rust 529 | macro_rules! expand_combined_early_lint_pass_method { 530 | ([$($passes:ident),*], $self: ident, $name: ident, $params:tt) => ({ 531 | $($self.$passes.$name $params;)* 532 | }) 533 | } 534 | ``` 535 | 536 | Macro `expand_combined_early_lint_pass_method` call `check_*` methods defined in each `LintPass`. 537 | 538 | Through this macro, the definition of `BuiltinCombinedEarlyLintPass` is changed to(ignore other definitions): 539 | 540 | ```rust 541 | impl EarlyLintPass for BuiltinCombinedEarlyLintPass { 542 | fn check_param(&mut self, context: &EarlyContext<'_>, a: &ast::Param) { 543 | self.UnusedParens.check_param(context, a); 544 | self.UnusedBraces.check_param(context, a); 545 | self.UnusedImportBraces.check_param(context, a); 546 | ... 547 | } 548 | fn check_ident(&mut self, context: &EarlyContext<'_>, a: &ast::Ident) { 549 | self.UnusedParens.check_ident(context, a); 550 | self.UnusedBraces.check_ident(context, a); 551 | self.UnusedImportBraces.check_ident(context, a); 552 | ... 553 | } 554 | fn check_crate(&mut self, context: &EarlyContext<'_>, a: &ast::Crate) { 555 | self.UnusedParens.check_crate(context, a); 556 | self.UnusedBraces.check_crate(context, a); 557 | self.UnusedImportBraces.check_crate(context, a); 558 | ... 559 | } 560 | ... 561 | 562 | } 563 | ``` 564 | 565 | #### Definition of BuiltinCombinedEarlyLintPass 566 | 567 | Through the expansion of the above macro, `BuiltinCombinedEarlyLintPass` is defined as follows: 568 | 569 | ```rust 570 | pub struct BuiltinCombinedEarlyLintPass { 571 | UnusedParens: UnusedParens, 572 | UnusedBraces: UnusedBraces, 573 | ... 574 | } 575 | 576 | impl BuiltinCombinedEarlyLintPass{ 577 | pub fn new() -> Self { 578 | UnusedParens: UnusedParens, 579 | UnusedBraces: UnusedBraces, 580 | ... 581 | } 582 | 583 | pub fn get_lints() -> LintArray { 584 | let mut lints = Vec::new(); 585 | lints.extend_from_slice(&UnusedParens::get_lints()); 586 | lints.extend_from_slice(&UnusedBraces::get_lints()); 587 | ... 588 | lints 589 | } 590 | } 591 | 592 | impl EarlyLintPass for BuiltinCombinedEarlyLintPass { 593 | fn check_crates(&mut self, context: &EarlyContext<'_>, a: &ast::Crate){ 594 | self.UnusedParens.check_crates (context, a); 595 | self.UnusedBraces.check_crates (context, a); 596 | ... 597 | } 598 | fn check_ident(&mut self, context: &EarlyContext<'_>, a: Ident){ 599 | self.UnusedParens.check_ident (context, a); 600 | self.UnusedBraces.check_ident (context, a); 601 | ... 602 | } 603 | .. 604 | } 605 | ``` 606 | 607 | Through this definition, we can use the `check_*` method of `BuiltinCombinedEarlyLintPass` to run multiple lintpasses when traversing the AST. 608 | 609 | ## Optimize the design of Linter 610 | 611 | Based on CombinedLintPass,we can optimize the design of Linter: 612 | ![Linter](./images/linter.jpg) 613 | 614 | Here, we use `check_*` of CombinedLintPass to run lint check when traversing the AST. 615 | 616 | Although the effect is the same as before, because of the macro, all `check_*` methods and lintpass to be executed are collected into a structure, which is easier to manage. Similarly, because combinedlintpass actually calls the check methods of each lintpass, although the call may be as complex as the following figure, most of the check methods defined in lintpass are empty checks(just a `{}`) generated by macros, there will be no performance loss. 617 | 618 | ![combinedlintpass](./images/combinedlintpass.jpg) 619 | 620 | ## How Lint Works in Rustc 621 | 622 | Finally, let's see how lint works in Rustc. 623 | 624 | ### Lint's execution phase in Rustc 625 | 626 | Rustc's design is similar to classic compilers, including lexical analysis, syntax analysis, semantic analysis, IR generation, IR optimization, code generation and other processes. In addition, some special processes, such as borrowing check, have been added to the compiler for Rust feature. Correspondingly, the intermediate representation of the code in the whole compilation process also has some extensions. I found the explanation of IR in the [rust-dev-guide](https://rustc-dev-guide.rust-lang.org/overview.html#intermediate-representations): 627 | 628 | - Token stream: the lexer produces a stream of tokens directly from the source code. This stream of tokens is easier for the parser to deal with than raw text. 629 | - Abstract Syntax Tree (AST): the abstract syntax tree is built from the stream of tokens produced by the lexer. It represents pretty much exactly what the user wrote. It helps to do some syntactic sanity checking (e.g. checking that a type is expected where the user wrote one). 630 | - High-level IR (HIR): This is a sort of desugared AST. It's still close to what the user wrote syntactically, but it includes some implicit things such as some elided lifetimes, etc. This IR is amenable to type checking. 631 | - Typed HIR (THIR): This is an intermediate between HIR and MIR, and used to be called High-level Abstract IR (HAIR). It is like the HIR but it is fully typed and a bit more desugared (e.g. method calls and implicit dereferences are made fully explicit). Moreover, it is easier to lower to MIR from THIR than from HIR. 632 | - Middle-level IR (MIR): This IR is basically a Control-Flow Graph (CFG). A CFG is a type of diagram that shows the basic blocks of a program and how control flow can go between them. Likewise, MIR also has a bunch of basic blocks with simple typed statements inside them (e.g. assignment, simple computations, etc) and control flow edges to other basic blocks (e.g., calls, dropping values). MIR is used for borrow checking and other important dataflow-based checks, such as checking for uninitialized values. It is also used for a series of optimizations and for constant evaluation (via MIRI). Because MIR is still generic, we can do a lot of analyses here more efficiently than after monomorphization. 633 | - LLVM IR: This is the standard form of all input to the LLVM compiler. LLVM IR is a sort of typed assembly language with lots of annotations. It's a standard format that is used by all compilers that use LLVM (e.g. the clang C compiler also outputs LLVM IR). LLVM IR is designed to be easy for other compilers to emit and also rich enough for LLVM to run a bunch of optimizations on it. 634 | 635 | The above conversion process of Rust's IR also reflects the whole compilation process of Rust, which is summarized in one figure: 636 | 637 | ![Complication process](images/complication_process.jpg) 638 | `rustc_ driver::lib. Rs' controls each stage of the compilation process: 639 | 640 | ```rust 641 | fn run_compiler(...) -> interface::Result<()> { 642 | ... 643 | interface::run_compiler(config, |compiler| { 644 | let linker = compiler.enter(|queries| { 645 | ... 646 | queries.parse()?; // lexer parse 647 | ... 648 | queries.expansion()?; // resolver 649 | ... 650 | queries.prepare_outputs()?; 651 | ... 652 | queries.global_ctxt()?; // ast -> hir 653 | ... 654 | queries.ongoing_codegen()?; 655 | ... 656 | } 657 | } 658 | ``` 659 | 660 | As described previously, lint is divided into early and late, which are executed in phases of AST -> HIR and HIR -> THIR respectively. Here we will also take the example of `WhileTrue` to see the whole process of Lint from definition to registration and execution. Meanwhile, `WhileTrue` is one of built-in early lint and is included in `BuiltinCombinedEarlyLintPass`. 661 | 662 | ### Definition 663 | 664 | Definitions of `WhileTrue`'s lint and lintpass are defined in `rustc_lint/src/builtin.rs`: 665 | 666 | ```rust 667 | declare_lint! { 668 | /// The `while_true` lint detects `while true { }`. 669 | /// 670 | /// ### Example 671 | /// 672 | /// ```rust,no_run 673 | /// while true { 674 | /// 675 | /// } 676 | /// ``` 677 | /// 678 | /// {{produces}} 679 | /// 680 | /// ### Explanation 681 | /// 682 | /// `while true` should be replaced with `loop`. A `loop` expression is 683 | /// the preferred way to write an infinite loop because it more directly 684 | /// expresses the intent of the loop. 685 | WHILE_TRUE, 686 | Warn, 687 | "suggest using `loop { }` instead of `while true { }`" 688 | } 689 | 690 | declare_lint_pass!(WhileTrue => [WHILE_TRUE]); 691 | 692 | impl EarlyLintPass for WhileTrue { 693 | fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) { 694 | ... 695 | } 696 | } 697 | ``` 698 | 699 | As described previously, 700 | 701 | 1. Macro `declare_lint` declare a lint: `WHILE_TRUE` 702 | 2. Macro `declare_lint_pass` declare a lint: `WhileTrue` 703 | 3. Implement the `check_*` methods defined in `EarlyLintPass` for`WHILE_TRUE`. Because this lintpass only checks the expr node, it only needs to implement `check_ expr()` method. 704 | 705 | ### Registration 706 | 707 | `Whiletrue` does not require separate registration and execution. Its checking method is expanded into `BuiltinCombinedEarlyLintPass` by macro. The `BuiltinCombinedEarlyLintPass` register and execute in the method `queries.expansion()`. 708 | 709 | ```rust 710 | pub fn expansion( 711 | &self, 712 | ) -> Result<&Query<(Rc, Rc>, Lrc)>> { 713 | tracing::trace!("expansion"); 714 | self.expansion.compute(|| { 715 | let crate_name = self.crate_name()?.peek().clone(); 716 | // register 717 | let (krate, lint_store) = self.register_plugins()?.take(); 718 | let _timer = self.session().timer("configure_and_expand"); 719 | let sess = self.session(); 720 | let mut resolver = passes::create_resolver( 721 | sess.clone(), 722 | self.codegen_backend().metadata_loader(), 723 | &krate, 724 | &crate_name, 725 | ); 726 | let krate = resolver.access(|resolver| { 727 | // execute 728 | passes::configure_and_expand(sess, &lint_store, krate, &crate_name, resolver) 729 | })?; 730 | Ok((Rc::new(krate), Rc::new(RefCell::new(resolver)), lint_store)) 731 | }) 732 | } 733 | ``` 734 | 735 | The registration process will generate a defined lint structure and add it to the [LintStore](https://rustc-dev-guide.rust-lang.org/diagnostics/lintstore.html). Lint is divided into four categories: pre-expansion, early, late and late-module. Although lintpasses are executed at different stages in the compilation process, registration occurs at the same time. 736 | 737 | The function call of lint registration process is as follows: 738 | 739 | - rustc_driver::lib::run_compiler() 740 | - rustc_interface::queries::Queries.expansion() 741 | - rustc_interface::queries::Queries.register_plugins() 742 | - rustc_lint::lib::new_lint_store() 743 | - rustc_lint::lib::register_builtins() 744 | 745 | Here, the default compilation process will execute the statement in the `else` branch, `BuiltinCombinedEarlyLintPass::get_lints()` will generate `WhileTrue` and added it to LintStore. 746 | 747 | ```rust 748 | if no_interleave_lints { 749 | pre_expansion_lint_passes!(register_passes, register_pre_expansion_pass); 750 | early_lint_passes!(register_passes, register_early_pass); 751 | late_lint_passes!(register_passes, register_late_pass); 752 | late_lint_mod_passes!(register_passes, register_late_mod_pass); 753 | } else { 754 | store.register_lints(&BuiltinCombinedPreExpansionLintPass::get_lints()); 755 | store.register_lints(&BuiltinCombinedEarlyLintPass::get_lints()); 756 | store.register_lints(&BuiltinCombinedModuleLateLintPass::get_lints()); 757 | store.register_lints(&BuiltinCombinedLateLintPass::get_lints()); 758 | } 759 | ``` 760 | 761 | ### Execution 762 | 763 | The execution of different lintpass occurs at different stages of the compilation process. The function call of the `BuiltinCombinedEarlyLintPass` execution process are as follows: 764 | 765 | - rustc_driver::lib::run_compiler() 766 | - rustc_interface::queries::Queries.expansion() 767 | - rustc_interface::passes::configure_and_expand() 768 | - rustc_lint::early::check_ast_node() 769 | - rustc_lint::early::early_lint_node() 770 | 771 | 首先,在 configure_and_expand() 函数中,执行了 pre-expansion 和 early 两种 lintpass。注册时使用了 BuiltinCombinedEarlyLintPass::get_lints() 方法生成 lints,而这里用 BuiltinCombinedEarlyLintPass::new() 方法生成了 lintpass。 772 | 773 | First, in function `configure_ and_expand()`, pre-expansion and early lintpass are executed. Lints which generated by `BuiltinCombinedEarlyLintPass::get_lints()` were used for registration, and here, and lintpasses which generated by BuiltinCombinedEarlyLintPass::new() were used for execution. 774 | 775 | ```rust 776 | pub fn configure_and_expand( 777 | sess: &Session, 778 | lint_store: &LintStore, 779 | mut krate: ast::Crate, 780 | crate_name: &str, 781 | resolver: &mut Resolver<'_>, 782 | ) -> Result { 783 | pre_expansion_lint(sess, lint_store, resolver.registered_tools(), &krate, crate_name); 784 | ... 785 | sess.time("early_lint_checks", || { 786 | let lint_buffer = Some(std::mem::take(resolver.lint_buffer())); 787 | rustc_lint::check_ast_node( 788 | sess, 789 | false, 790 | lint_store, 791 | resolver.registered_tools(), 792 | lint_buffer, 793 | rustc_lint::BuiltinCombinedEarlyLintPass::new(), 794 | &krate, 795 | ) 796 | }); 797 | } 798 | ``` 799 | 800 | Lint execution finally occurs in function `rustc_lint::early::early_lint_node()`. Compare `early_ lint_ Node()` and the pseudo-code at the end of the `CombinedLintPass` section: 801 | 802 | ![early_lint_node与CombinedLintPass](images/early_lint_node.jpg) 803 | 804 | They have the following relationship: 805 | 806 | - The parameter `pass` is the `BuiltinCombinedEarlyLintPass` created in `configure_and_expand()`. It corresponds to `combinedlintpass。`. 807 | - `EarlyContextAndPass` combined `pass` and `context`, and implement the `visitor`. It corresponds to `Linter`. 808 | - `check_node.check(cx)` call `cx.pass.check_crate()` and execute lint check. According to the definition of `BuiltinCombinedEarlyLintPass`, this method will call all `check_crate()` defined in builtin early lint, and run `ast_visit::walk_crate()` to traverses sub-node. It corresponds to `visit_crate()`. 809 | 810 | ### no_interleave_lints 811 | 812 | Although Rustc combines lintpass into combinedlintpass in consideration of performance, it provides some compilation parameters to configure lint. Among them, the parameter `no_interleave_lints` is used in the registration and execution of lint. This parameter defaults to false, indicating whether to execute each lint separately. By modifying this parameter during compilation, each lint can be registered separately and lintpass can be executed separately. This design provides better flexibility and customization (for example, you can benchmark each lint separately). 813 | 814 | ```rust 815 | if no_interleave_lints { 816 | pre_expansion_lint_passes!(register_passes, register_pre_expansion_pass); 817 | early_lint_passes!(register_passes, register_early_pass); 818 | late_lint_passes!(register_passes, register_late_pass); 819 | late_lint_mod_passes!(register_passes, register_late_mod_pass); 820 | } else { 821 | store.register_lints(&BuiltinCombinedPreExpansionLintPass::get_lints()); 822 | store.register_lints(&BuiltinCombinedEarlyLintPass::get_lints()); 823 | store.register_lints(&BuiltinCombinedModuleLateLintPass::get_lints()); 824 | store.register_lints(&BuiltinCombinedLateLintPass::get_lints()); 825 | } 826 | ``` 827 | 828 | ```rust 829 | pub fn check_ast_node<'a>(...) { 830 | if sess.opts.debugging_opts.no_interleave_lints { 831 | for (i, pass) in passes.iter_mut().enumerate() { 832 | buffered = 833 | sess.prof.extra_verbose_generic_activity("run_lint", pass.name()).run(|| { 834 | early_lint_node( 835 | sess, 836 | !pre_expansion && i == 0, 837 | lint_store, 838 | registered_tools, 839 | buffered, 840 | EarlyLintPassObjects { lints: slice::from_mut(pass) }, 841 | check_node, 842 | ) 843 | }); 844 | } 845 | } else { 846 | buffered = early_lint_node( 847 | sess, 848 | !pre_expansion, 849 | lint_store, 850 | registered_tools, 851 | buffered, 852 | builtin_lints, 853 | check_node, 854 | ); 855 | ... 856 | } 857 | } 858 | ``` 859 | 860 | ## Summary 861 | 862 | So far, we have analyzed the complete process of a lint in Rustc, including defining a lint, implementing the corresponding lintpass, registration and execution. We can also use these macros to define new lint and lintpass (e.g., extension in Clippy. It works in a similar way). Of course, lint in Rustc is far more than that. I only share a small part of it that I can understand and I have learned. I hope it can help you. 863 | 864 | In addition to this, we have practiced part of this content in Project [KCLVM](https://github.com/KusionStack/KCLVM). You can find more detailed design and implementation of lint in [issue](https://github.com/KusionStack/KCLVM/issues/109) and [PR](https://github.com/KusionStack/KCLVM/pull/160), including the definition of `visitor`, lint, lintpass, combinedlintpass, and execution of lint in resolver. Welcome for your comments. 865 | -------------------------------------------------------------------------------- /rustc/sema/mir-lowering/borrow-check/readme.md: -------------------------------------------------------------------------------- 1 | # 借用检查 -------------------------------------------------------------------------------- /rustc/sema/mir-lowering/mir-optimized/readme.md: -------------------------------------------------------------------------------- 1 | # MIR 优化 2 | -------------------------------------------------------------------------------- /rustc/sema/mir-lowering/readme.md: -------------------------------------------------------------------------------- 1 | # MIR Lowering 2 | -------------------------------------------------------------------------------- /rustc/sema/readme.md: -------------------------------------------------------------------------------- 1 | # Sema 2 | 3 | - [Lint](../sema/lint/readme.md) 4 | -------------------------------------------------------------------------------- /rustc/sema/resolver/readme.md: -------------------------------------------------------------------------------- 1 | # Resolver -------------------------------------------------------------------------------- /stdlib/readme.md: -------------------------------------------------------------------------------- 1 | # Standard Library 2 | - [sort: Timsort and pdqsort](../stdlib/sort/readme.md) 3 | -------------------------------------------------------------------------------- /stdlib/sort/readme.md: -------------------------------------------------------------------------------- 1 | # 排序算法: Timsort 和 pdqsort 2 | 3 | ## 前言 4 | 5 | Rust 中排序算法的实现可以分为稳定和不稳定的两类。其中稳定的排序算法是一种受 Tim Peters 的 [Timsort](https://en.wikipedia.org/wiki/Timsort) 算法启发的自适应、迭代归并排序;而不稳定的排序算法则是基于 Orson Peters 的 [pdqsort](https://github.com/orlp/pdqsort)[pattern-defeating quicksort]。本文将介绍这两个算法在 Rust 中的实现。 6 | 7 | ## 稳定排序: Timsort 8 | 9 | 稳定排序是指在排序过程中不改变相等的元素的顺序。 Rust 中的稳定排序的实现是一种改进的 timsort 算法。可以在 `libray:alloc:src:slice.rs` 中看到它的实现。 10 | 11 | ### Timsort 简介 12 | 13 | Timsort 算法由 Tim Peters 在 2002 年设计,是一种归并和插入排序的混合的排序算法。在最坏的情况,它的时间复杂度为 *O*(*n* \* log(*n*)),需要分配排序的数组一半大小的内存空间,所以空间复杂度为 *O*(*n*),所以在各个方面都优于*O*(*n*)空间和稳定*O*(*n* \* log(*n*))时间的归并排序算法。由于其出色的性能,在 Python 中最先引入,作为 list.sort 的默认实现,后续 Java 也在 JDK1.7 中使用了 Timsort 算法。 14 | 15 | Timsort 算法的基本流程是: 16 | 17 | 1. 确定数组的单调上升段和严格单调下降段,并将严格下降段反转 18 | 2. 定义最小片段(run)长度,低于此长度的片段通过插入排序合并到较长的段中 19 | 3. 反复归并相邻片段,直到整个排序完成 20 | 21 | 因此,Timsort 基本上是一种归并排序,但是在一些小片段的合并中使用了插入排序。 22 | 23 | ### 算法实现 24 | 25 | 可以在 `libray:alloc:src:slice.rs` 中看到 Rust 中 Timsort 算法的实现。 26 | 27 | #### 空数组和短数组处理 28 | 29 | 首先是一些特殊情况的处理: 30 | 31 | ```rust 32 | fn merge_sort(v: &mut [T], mut is_less: F) 33 | where 34 | F: FnMut(&T, &T) -> bool, 35 | { 36 | // Slices of up to this length get sorted using insertion sort. 37 | const MAX_INSERTION: usize = 20; 38 | // Sorting has no meaningful behavior on zero-sized types. 39 | if T::IS_ZST { 40 | return; 41 | } 42 | let len = v.len(); 43 | // Short arrays get sorted in-place via insertion sort to avoid allocations. 44 | if len <= MAX_INSERTION { 45 | if len >= 2 { 46 | for i in (0..len - 1).rev() { 47 | insert_head(&mut v[i..], &mut is_less); 48 | } 49 | } 50 | return; 51 | } 52 | } 53 | ``` 54 | 55 | 这段非常容易理解,如果是空数组就直接返回;如果是比较短的数组(低于20),就直接用简单的插入排序。 56 | 57 | #### 扫描数组,确定单调片段 58 | 59 | Timsort 算法的第一步是识别单调片段(run):单调递增片段和严格单调递减片段,并将严格单调递减片段反转。 60 | 61 | ```rust 62 | fn merge_sort(v: &mut [T], mut is_less: F) 63 | where 64 | F: FnMut(&T, &T) -> bool, 65 | { 66 | let mut end = len; 67 | while end > 0 { 68 | let mut start = end - 1; 69 | if start > 0 { 70 | start -= 1; 71 | unsafe { 72 | if is_less(v.get_unchecked(start + 1), v.get_unchecked(start)) { 73 | while start > 0 && is_less(v.get_unchecked(start), v.get_unchecked(start - 1)) { 74 | start -= 1; 75 | } 76 | v[start..end].reverse(); 77 | } else { 78 | while start > 0 && !is_less(v.get_unchecked(start), v.get_unchecked(start - 1)) 79 | { 80 | start -= 1; 81 | } 82 | } 83 | } 84 | } 85 | ... 86 | } 87 | } 88 | 89 | 90 | ``` 91 | 92 | 首先从后向前遍历数组,找到单调递增或严格单调递减的段的起点,并将严格单调递减的段反转。以数组`[4,5,6, 7, 3(1), 3(2), 1, 0]`为例(为了简化掩饰,暂不考虑`MAX_INSERTION`),首先找到第一个严格单调递减段`[3(2), 1, 0]`,并将其反转为`[0, 1, 3(2)]`。 93 | 94 | #### 合并较短的段 95 | 96 | 在较短的数组上,插入排序的性能优于归并排序。所以 Timsort 算法的第二步是定义最短段长度,并利用插入排序合并较短的段。 97 | 98 | ```rust 99 | fn merge_sort(v: &mut [T], mut is_less: F) 100 | where 101 | F: FnMut(&T, &T) -> bool, 102 | { 103 | const MIN_RUN: usize = 10; 104 | while end > 0 { 105 | // omit step 1 106 | 107 | while start > 0 && end - start < MIN_RUN { 108 | start -= 1; 109 | insert_head(&mut v[start..end], &mut is_less); 110 | } 111 | runs.push(Run { start, len: end - start }); 112 | } 113 | } 114 | ``` 115 | 116 | 上述的例子中,同样为了方便演示,假设 `MIN_RUN` 的值为5。则根据上述代码,使用插入排序在段中插入 `7` 和 `3(1)`,则段变为 `[0, 1, 3(1), 3(2), 7]`。最后将这个段入栈。 117 | 118 | #### 合并相邻段 119 | 120 | ```rust 121 | fn merge_sort(v: &mut [T], mut is_less: F) 122 | where 123 | F: FnMut(&T, &T) -> bool, 124 | { 125 | const MIN_RUN: usize = 10; 126 | while end > 0 { 127 | // omit step 1 and step 2 128 | while let Some(r) = collapse(&runs) { 129 | let left = runs[r + 1]; 130 | let right = runs[r]; 131 | unsafe { 132 | merge( 133 | &mut v[left.start..right.start + right.len], 134 | left.len, 135 | buf.as_mut_ptr(), 136 | &mut is_less, 137 | ); 138 | } 139 | runs[r] = Run { start: left.start, len: left.len + right.len }; 140 | runs.remove(r + 1); 141 | } 142 | } 143 | fn collapse(runs: &[Run]) -> Option { 144 | let n = runs.len(); 145 | if n >= 2 146 | && (runs[n - 1].start == 0 147 | || runs[n - 2].len <= runs[n - 1].len 148 | || (n >= 3 && runs[n - 3].len <= runs[n - 2].len + runs[n - 1].len) 149 | || (n >= 4 && runs[n - 4].len <= runs[n - 3].len + runs[n - 2].len)) 150 | { 151 | if n >= 3 && runs[n - 3].len < runs[n - 1].len { Some(n - 3) } else { Some(n - 2) } 152 | } else { 153 | None 154 | } 155 | } 156 | 157 | } 158 | ``` 159 | 160 | 首先看 `collapse` 函数。这里用 `collapse` 判断是否有能够合并的段,如果有,则返回其下标 `r`,如果没有,则返回 `None`。具体判断的逻辑稍后说明。 161 | 162 | 步骤3中根据 `collapse` 函数的返回结果,使用归并排序合并 `runs[r]`和 `runs[r + 1]`,或者重复步骤 1 和步骤 2,继续在栈 `runs` 中构建新的段。 163 | 164 | 刚刚的例子中,栈 `runs` 中只有一个段 `[0, 1, 3(1), 3(2), 7]`,显然不能合并,因此重复步骤 1 和步骤 2,在 `runs` 中添加第二个段,使其变为 `[[0, 1, 3(1), 3(2), 7], [4, 5, 6]]`(用 `[]` 表示一个段)。此时 `collapse` 会返回下标 `0`,然后使用归并合并 `[0, 1, 3(1), 3(2), 7]` 和 `[4, 5, 6]`。得到结果 `[0, 1, 3(1), 3(2), 4, 5, 6, 7]`,完成整个遍历。 165 | 166 | ### Timsort 算法的 bug 167 | 168 | Rust 中的实现并非默认的 Timsort 的算法,这是因为 Timsort 算法存在 bug(http://envisage-project.eu/timsort-specification-and-verification/)。Rust 的实现在 `collapse` 这个函数做了修改。 169 | 170 | Timsort 算法在 JDK1.7 中引入 Java,但在 1.8 版本仍未修复这个 bug。 比较 Java JDK1.8中对应的实现。Java的实现中只比较了栈顶3个元素,但 Rust 的现实比较了栈顶 4 个元素。 171 | 172 | ```java 173 | private void mergeCollapse() { 174 | while (stackSize > 1) { 175 | int n = stackSize - 2; 176 | if (n > 0 && runLen[n - 1] <= runLen[n] + runLen[n + 1]) { 177 | if (runLen[n - 1] < runLen[n + 1]) 178 | n--; 179 | mergeAt(n); 180 | } else if (runLen[n] <= runLen[n + 1]) { 181 | mergeAt(n); 182 | } else { 183 | break; // Invariant is established 184 | } 185 | } 186 | } 187 | ``` 188 | 189 | 出于性能原因,Timsort 要维护尽可能少的 run。因此在每次新的 `run` 入栈时,会运行 `mergeCollapse` 函数合并栈顶 3 个元素,又因为每次入栈都会执行,所以栈中所有 run 的长度都满足以下两个条件: 190 | 191 | 1. runLen[n - 2] > runLen[n - 1] + runLen[n] 192 | 2. runLen[n - 1] > runLen[n] 193 | 194 | 如果不满足规则 1,则将 run[n - 1] 与 run[n] 和 run[n - 2] 较短的合并。例如,runs 中存在两个长度分别为 12 和 7 的 run,此时入栈一个长度为 6 的run,则合并长度为 7 和 6 两个 run,栈变为 [12, 13]。 195 | 如果不满足规则 2,则将 run[n - 1] 与 run[n] 合并。如上面的例子,继续合并 12 和 13,此时 runs 中仅剩一个长度为 25 的 run。就可以继续执行 Timsort 算法的第一步和第二步构造新的 run 或完成排序。 196 | 197 | 但问题在哪呢?考虑一个例子: 198 | 199 | ``` 200 | 120, 80, 25, 20, 30 201 | ``` 202 | 203 | 因为 25 < 20 + 30, 所以合并为 204 | 205 | ``` 206 | 120, 80, 45, 30 207 | ``` 208 | 209 | 此时, `120, 80, 45` 已经不满足规则。这个bug在[这里](http://www.envisage-project.eu/proving-android-java-and-python-sorting-algorithm-is-broken-and-how-to-fix-it)有更为详细的描述以及解决方法。 210 | 211 | ## 不稳定排序: pdqsort 212 | todo 213 | 214 | ## Ref 215 | 216 | + Timsort: 217 | + OpenJDK’s java.utils.Collection.sort() is broken: The good, the bad and the worst case: 218 | + Proving that Android’s, Java’s and Python’s sorting algorithm is broken (and showing how to fix it): 219 | + Java bug track: 220 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | @media only screen and (max-width:1079px) { 2 | .sidetoc { 3 | display: none; 4 | } 5 | } 6 | 7 | @media only screen and (min-width:1080px) { 8 | main { 9 | position: relative; 10 | padding-right: 170px; 11 | } 12 | .sidetoc { 13 | margin-left: auto; 14 | margin-right: auto; 15 | /*left: calc(100% + (var(--content-max-width))/4 - 180px);*/ 16 | left: calc(100% - 200px); 17 | position: absolute; 18 | } 19 | .pagetoc { 20 | position: fixed; 21 | width: 200px; 22 | height: calc(100vh - var(--menu-bar-height) - 0.67em * 4); 23 | overflow: auto; 24 | z-index: 1000; 25 | } 26 | .pagetoc a { 27 | border-left: 1px solid var(--sidebar-bg); 28 | color: var(--fg) !important; 29 | display: block; 30 | padding-bottom: 5px; 31 | padding-top: 5px; 32 | padding-left: 10px; 33 | text-align: left; 34 | text-decoration: none; 35 | font-size: 1.2rem; 36 | } 37 | .pagetoc a:hover, 38 | .pagetoc a.active { 39 | background: var(--sidebar-bg); 40 | color: var(--sidebar-fg) !important; 41 | } 42 | .pagetoc .active { 43 | background: var(--sidebar-bg); 44 | color: var(--sidebar-fg); 45 | } 46 | } 47 | 48 | .page-footer { 49 | margin-top: 50px; 50 | border-top: 1px solid #ccc; 51 | overflow: hidden; 52 | padding: 10px 0; 53 | color: gray; 54 | } -------------------------------------------------------------------------------- /theme/index.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ title }} 7 | {{#if is_print }} 8 | 9 | {{/if}} 10 | {{#if base_url}} 11 | 12 | {{/if}} 13 | 14 | 15 | 16 | {{> head}} 17 | 18 | 19 | 20 | 21 | 22 | 23 | {{#if favicon_svg}} 24 | 25 | {{/if}} 26 | {{#if favicon_png}} 27 | 28 | {{/if}} 29 | 30 | 31 | 32 | {{#if print_enable}} 33 | 34 | {{/if}} 35 | 36 | 37 | 38 | {{#if copy_fonts}} 39 | 40 | {{/if}} 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | {{#each additional_css}} 49 | 50 | {{/each}} 51 | 52 | {{#if mathjax_support}} 53 | 54 | 55 | {{/if}} 56 | 57 | 58 | 59 | 60 | 64 | 65 | 66 | 80 | 81 | 82 | 92 | 93 | 94 | 104 | 105 | 111 | 112 |
113 | 114 |
115 | {{> header}} 116 | 117 | 160 | 161 | {{#if search_enabled}} 162 | 172 | {{/if}} 173 | 174 | 175 | 182 | 183 |
184 | 185 |
186 |
187 | 188 | {{!-- --}} 192 | {{!--
--}} 193 | 194 | {{{ content }}} 195 | 196 | 197 |
198 | {{!--
欢迎扫码加群参与讨论
199 |
200 | 201 |
--}} 202 |
203 | 206 |
207 | 208 | 224 |
225 |
226 | 227 | 240 | 241 |
242 | 243 | {{#if livereload}} 244 | 245 | 258 | {{/if}} 259 | 260 | {{#if google_analytics}} 261 | 262 | 277 | {{/if}} 278 | 279 | {{#if playground_line_numbers}} 280 | 283 | {{/if}} 284 | 285 | {{#if playground_copyable}} 286 | 289 | {{/if}} 290 | 291 | {{#if playground_js}} 292 | 293 | 294 | 295 | 296 | 297 | {{/if}} 298 | 299 | {{#if search_js}} 300 | 301 | 302 | 303 | {{/if}} 304 | 305 | 306 | 307 | 308 | 311 | 312 | 313 | 314 | {{#each additional_js}} 315 | 316 | {{/each}} 317 | 318 | {{#if is_print}} 319 | {{#if mathjax_support}} 320 | 327 | {{else}} 328 | 333 | {{/if}} 334 | {{/if}} 335 | 336 | 337 | 338 | -------------------------------------------------------------------------------- /wechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-kusion/rust-code-book/7cf5b600b88117f641b38a1ec45a7c8a9c5d7f59/wechat.png --------------------------------------------------------------------------------