diff --git a/CONTRIBUTORS b/CONTRIBUTORS index e2e102f610..48567eed15 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -120,6 +120,7 @@ Alex Kohler Alex Myasoedov Alex Opie Alex Plugaru +Alex Schade <39062967+aschade92@users.noreply.github.com> Alex Schroeder Alex Sergeyev Alex Tokarev @@ -135,6 +136,7 @@ Alexander Klauer Alexander Kucherenko Alexander Larsson Alexander Lourier +Alexander Melentyev Alexander Menzhinsky Alexander Morozov Alexander Neumann @@ -145,6 +147,7 @@ Alexander Polcyn Alexander Rakoczy Alexander Reece Alexander Surma +Alexander Yastrebov Alexander Zhavnerchik Alexander Zillion Alexander Zolotov @@ -179,6 +182,7 @@ Alok Menghrajani Alwin Doss Aman Gupta Amarjeet Anand +Amelia Downs Amir Mohammad Saied Amit Kumar Amr Mohammed @@ -191,6 +195,7 @@ Anatol Pomozov Anders Pearson Anderson Queiroz André Carvalho +Andre Marianiello André Martins Andre Nathan Andrea Nodari @@ -221,6 +226,7 @@ Andrew Gerrand Andrew Harding Andrew Jackura Andrew Kemm +Andrew LeFevre Andrew Louis Andrew Lutomirski Andrew Medvedev @@ -234,6 +240,7 @@ Andrew Stormont Andrew Stribblehill Andrew Szeto Andrew Todd +Andrew Wansink Andrew Werner Andrew Wilkins Andrew Williams @@ -283,6 +290,7 @@ Antonio Bibiano Antonio Garcia Antonio Huete Jimenez Antonio Murdaca +Antonio Ojea Antonio Troina Anze Kolar Aofei Sheng @@ -290,6 +298,7 @@ Apisak Darakananda Aram Hăvărneanu Araragi Hokuto Arash Bina +Archana Ravindar Arda Güçlü Areski Belaid Ariel Mashraki @@ -299,6 +308,7 @@ Arnaud Ysmal Arne Hormann Arnout Engelen Aron Nopanen +Arran Walker Artem Alekseev Artem Khvastunov Artem Kolin @@ -337,6 +347,7 @@ Balaram Makam Balazs Lecz Baokun Lee Barnaby Keene +Bartłomiej Klimczak Bartosz Grzybowski Bartosz Oler Bassam Ojeil @@ -368,6 +379,7 @@ Benny Siegert Benoit Sigoure Berengar Lehr Berkant Ipek <41230766+0xbkt@users.noreply.github.com> +Beth Brown Bharath Kumar Uppala Bharath Thiruveedula Bhavin Gandhi @@ -430,6 +442,7 @@ Brian Ketelsen Brian Slesinsky Brian Smith Brian Starke +Bruce Huang Bryan Alexander Bryan Boreham Bryan C. Mills @@ -482,17 +495,21 @@ Charles Kenney Charles L. Dorian Charles Lee Charles Weill +Charlie Getzen Charlie Moog Charlotte Brandhorst-Satzkorn Chauncy Cullitan Chen Zhidong Chen Zhihan +Cheng Wang Cherry Mui Chew Choon Keat +Chia-Chi Hsu Chiawen Chen Chirag Sukhala Cholerae Hu Chotepud Teo +Chressie Himpel Chris Ball Chris Biscardi Chris Broadfoot @@ -570,6 +587,7 @@ Cuong Manh Le Curtis La Graff Cyrill Schumacher Dai Jie +Dai Wentao Daisuke Fujita Daisuke Suzuki Daker Fernandes Pinheiro @@ -603,6 +621,7 @@ Daniel Langner Daniel Lidén Daniel Lublin Daniel Mangum +Daniel Marshall Daniel Martí Daniel McCarney Daniel Morsing @@ -727,6 +746,7 @@ Dmitry Mottl Dmitry Neverov Dmitry Savintsev Dmitry Yakunin +Dmytro Shynkevych Doga Fincan Domas Tamašauskas Domen Ipavec @@ -751,6 +771,7 @@ Dustin Herbison Dustin Long Dustin Sallings Dustin Shields-Cloues +Dustin Spicuzza Dvir Volk Dylan Waits Ed Schouten @@ -810,9 +831,11 @@ Erin Masatsugu Ernest Chiang Erwin Oegema Esko Luontola +Ethan Anderson Ethan Burns Ethan Hur Ethan Miller +Ethan Reesor Euan Kemp Eugene Formanenko Eugene Kalinin @@ -831,8 +854,10 @@ Evgeniy Polyakov Ewan Chou Ewan Valentine Eyal Posener +F. Talha Altınel Fabian Wickborn Fabian Zaremba +Fabio Falzoi Fabrizio Milo Faiyaz Ahmed Fan Hongjian @@ -861,21 +886,25 @@ Firmansyah Adiputra Florian Forster Florian Uekermann Florian Weimer +Florin Papa Florin Patan Folke Behrens Ford Hurley +Forest Johnson Francesc Campoy Francesco Guardiani Francesco Renzi Francisco Claude Francisco Rojas Francisco Souza +Frank Chiarulli Jr Frank Schroeder Frank Somers Frederic Guillot Frederick Kelly Mayle III Frederik Ring Frederik Zipp +Frediano Ziglio Fredrik Enestad Fredrik Forsmo Fredrik Wallgren @@ -914,6 +943,7 @@ Geon Kim Georg Reinke George Gkirtsou George Hartzell +George Looshch George Shammas George Tsilias Gerasimos (Makis) Maropoulos @@ -954,19 +984,27 @@ GitHub User @fatedier (7346661) GitHub User @frennkie (6499251) GitHub User @geedchin (11672310) GitHub User @GrigoriyMikhalkin (3637857) +GitHub User @Gusted (25481501) GitHub User @hengwu0 (41297446) <41297446+hengwu0@users.noreply.github.com> GitHub User @hitzhangjie (3725760) +GitHub User @hkhere (33268704) <33268704+hkhere@users.noreply.github.com> +GitHub User @hopehook (7326168) GitHub User @hqpko (13887251) +GitHub User @Illirgway (5428603) GitHub User @itchyny (375258) GitHub User @jinmiaoluo (39730824) GitHub User @jopbrown (6345470) GitHub User @kazyshr (30496953) GitHub User @kc1212 (1093806) GitHub User @komisan19 (18901496) +GitHub User @korzhao (64203902) GitHub User @Kropekk (13366453) +GitHub User @lgbgbl (65756378) GitHub User @lhl2617 (33488131) GitHub User @linguohua (3434367) +GitHub User @lloydchang (1329685) GitHub User @LotusFenn (13775899) +GitHub User @luochuanhang (96416201) GitHub User @ly303550688 (11519839) GitHub User @madiganz (18340029) GitHub User @maltalex (10195391) @@ -976,6 +1014,7 @@ GitHub User @micnncim (21333876) GitHub User @mkishere (224617) <224617+mkishere@users.noreply.github.com> GitHub User @nu50218 (40682920) GitHub User @OlgaVlPetrova (44112727) +GitHub User @pierwill (19642016) GitHub User @pityonline (438222) GitHub User @po3rin (29445112) GitHub User @pokutuna (57545) @@ -983,13 +1022,18 @@ GitHub User @povsister (11040951) GitHub User @pytimer (17105586) GitHub User @qcrao (7698088) GitHub User @ramenjuniti (32011829) +GitHub User @renthraysk (30576707) +GitHub User @roudkerk (52280478) GitHub User @saitarunreddy (21041941) GitHub User @SataQiu (9354727) +GitHub User @seifchen (23326132) GitHub User @shogo-ma (9860598) GitHub User @sivchari (55221074) GitHub User @skanehira (7888591) GitHub User @soolaugust (10558124) GitHub User @surechen (7249331) +GitHub User @syumai (6882878) +GitHub User @tangxi666 (48145175) GitHub User @tatsumack (4510569) GitHub User @tell-k (26263) GitHub User @tennashi (10219626) @@ -999,6 +1043,7 @@ GitHub User @unbyte (5772358) GitHub User @uropek (39370426) GitHub User @utkarsh-extc (53217283) GitHub User @witchard (4994659) +GitHub User @wmdngngng (22067700) GitHub User @wolf1996 (5901874) GitHub User @yah01 (12216890) GitHub User @yuanhh (1298735) @@ -1029,12 +1074,14 @@ Guilherme Garnier Guilherme Goncalves Guilherme Rezende Guilherme Souza <32180229+gqgs@users.noreply.github.com> +Guillaume Blaquiere Guillaume J. Charmes Guillaume Sottas Günther Noack Guobiao Mei Guodong Li Guoliang Wang +Guoqi Chen Gustav Paul Gustav Westling Gustavo Franco @@ -1050,6 +1097,8 @@ Hang Qian Hanjun Kim Hanlin He Hanlin Shi +Hans Nielsen +Hao Mou Haoran Luo Haosdent Huang Harald Nordgren @@ -1126,6 +1175,7 @@ Igor Zhilianin Ikko Ashimine Illya Yalovyy Ilya Chukov <56119080+Elias506@users.noreply.github.com> +Ilya Mateyko Ilya Sinelnikov Ilya Tocar INADA Naoki @@ -1157,6 +1207,7 @@ Jaana Burcu Dogan Jaap Aarts Jack Britton Jack Lindamood +Jack You Jacob Baskin Jacob Blain Christen Jacob H. Haven @@ -1165,6 +1216,7 @@ Jacob Walker Jaden Teng Jae Kwon Jake B +Jake Ciolek Jakob Borg Jakob Weisblat Jakub Čajka @@ -1183,6 +1235,7 @@ James Eady James Fennell James Fysh James Gray +James Harris James Hartig James Kasten James Lawrence @@ -1246,6 +1299,7 @@ Jean de Klerk Jean-André Santoni Jean-François Bustarret Jean-Francois Cantin +Jean-Hadrien Chabran Jean-Marc Eurin Jean-Nicolas Moal Jed Denlea @@ -1260,6 +1314,7 @@ Jeff Johnson Jeff R. Allen Jeff Sickel Jeff Wendling +Jeff Wentworth Jeff Widman Jeffrey H Jelte Fennema @@ -1282,6 +1337,7 @@ Jesús Espino Jia Zhan Jiacai Liu Jiahao Lu +Jiahua Wang Jianing Yu Jianqiao Li Jiayu Yi @@ -1298,10 +1354,12 @@ Jingcheng Zhang Jingguo Yao Jingnan Si Jinkun Zhang +Jinwen Wo Jiong Du Jirka Daněk Jiulong Wang Joakim Sernbrant +Jochen Weber Joe Bowbeer Joe Cortopassi Joe Farrell @@ -1324,6 +1382,7 @@ Johan Euphrosine Johan Jansson Johan Knutzen Johan Sageryd +Johannes Altmanninger Johannes Huning John Asmuth John Bampton @@ -1338,10 +1397,12 @@ John Howard Palevich John Jago John Jeffery John Jenkins +John Kelly John Leidegren John McCabe John Moore John Newlin +John Olheiser John Papandriopoulos John Potocny John R. Lenton @@ -1382,6 +1443,7 @@ Jordan Rupprecht Jordi Martin Jorge Araya Jorge L. Fatta +Jorge Troncoso Jos Visser Josa Gesell Jose Luis Vázquez González @@ -1508,6 +1570,7 @@ Keyuan Li Kezhu Wang Khosrow Moossavi Kieran Colford +Kieran Gorman Kim Shrier Kim Yongbin Kir Kolyshkin @@ -1577,6 +1640,7 @@ Leonel Quinteros Lev Shamardin Lewin Bormann Lewis Waddicor +Li-Yu Yu Liam Haworth Lily Chung Lingchao Xin @@ -1657,7 +1721,9 @@ Mark Adams Mark Bucciarelli Mark Dain Mark Glines +Mark Hansen Mark Harrison +Mark Jeffery Mark Percival Mark Pulford Mark Rushakoff @@ -1686,7 +1752,7 @@ Martin Hoefling Martin Kreichgauer Martin Kunc Martin Lindhe -Martin Möhrmann +Martin Möhrmann Martin Neubauer Martin Olsen Martin Olsson @@ -1741,6 +1807,7 @@ Matthew Denton Matthew Holt Matthew Horsnell Matthew Waters +Matthias Dötsch Matthias Frei Matthieu Hauglustaine Matthieu Olivier @@ -1814,6 +1881,7 @@ Michal Bohuslávek Michal Cierniak Michał Derkacz Michal Franc +Michal Hruby Michał Łowicki Michal Pristas Michal Rostecki @@ -1844,6 +1912,7 @@ Mike Solomon Mike Strosaker Mike Tsao Mike Wiacek +Mikhail Faraponov <11322032+moredure@users.noreply.github.com> Mikhail Fesenko Mikhail Gusarov Mikhail Panchenko @@ -1870,6 +1939,7 @@ Moritz Fain Moriyoshi Koizumi Morten Siebuhr Môshe van der Sterre +Mostafa Solati Mostyn Bramley-Moore Mrunal Patel Muhammad Falak R Wani @@ -1927,6 +1997,7 @@ Nick Miyake Nick Patavalis Nick Petroni Nick Robinson +Nick Sherron Nick Smolin Nicolas BRULEZ Nicolas Kaiser @@ -1956,6 +2027,7 @@ Noah Santschi-Cooney Noble Johnson Nodir Turakulov Noel Georgi +Nooras Saba Norberto Lopes Norman B. Lancaster Nuno Cruces @@ -1973,6 +2045,7 @@ Oliver Tan Oliver Tonnhofer Olivier Antoine Olivier Duperray +Olivier Mengué Olivier Poitrey Olivier Saingre Olivier Wulveryck @@ -1982,6 +2055,7 @@ Ori Bernstein Ori Rawlings Oryan Moshe Osamu TONOMORI +Oscar Söderlund Özgür Kesim Pablo Caderno Pablo Lalloni @@ -2014,6 +2088,7 @@ Patrick Pelletier Patrick Riley Patrick Smith Patrik Lundin +Patrik Nyblom Paul A Querna Paul Borman Paul Boyd @@ -2042,6 +2117,7 @@ Paul Wankadia Paulo Casaretto Paulo Flabiano Smorigo Paulo Gomes +Pavel Kositsyn Pavel Paulau Pavel Watson Pavel Zinovkin @@ -2049,6 +2125,7 @@ Pavlo Sumkin Pawel Knap Pawel Szczur Paweł Szulik +Pedro Lopez Mareque Pei Xian Chee Pei-Ming Wu Pen Tree @@ -2164,6 +2241,7 @@ Rhys Hiltner Ricardo Padilha Ricardo Pchevuzinske Katz Ricardo Seriani +Rich Hong Richard Barnes Richard Crowley Richard Dingwall @@ -2179,6 +2257,7 @@ Rick Hudson Rick Sayre Rijnard van Tonder Riku Voipio +Riley Avron Risto Jaakko Saarelma Rob Earhart Rob Findley @@ -2186,8 +2265,10 @@ Rob Norman Rob Phoenix Rob Pike Robert Ayrapetyan +Robert Burke Robert Daniel Kortschak Robert Dinu +Robert Engels Robert Figueiredo Robert Griesemer Robert Hencke @@ -2212,6 +2293,7 @@ Roger Peppe Rohan Challa Rohan Verma Rohith Ravi +Roi Martin Roland Illig Roland Shoemaker Romain Baugue @@ -2242,6 +2324,7 @@ Ryan Canty Ryan Dahl Ryan Hitchman Ryan Kohler +Ryan Leung Ryan Lower Ryan Roden-Corrent Ryan Seys @@ -2275,6 +2358,7 @@ Sami Pönkänen Samuel Kelemen Samuel Tan Samuele Pedroni +San Ye Sander van Harmelen Sanjay Menakuru Santhosh Kumar Tekuri @@ -2339,6 +2423,7 @@ Shaba Abhiram Shahar Kohanim Shailesh Suryawanshi Shamil Garatuev +Shamim Akhtar Shane Hansen Shang Jian Ding Shaozhen Ding @@ -2375,6 +2460,7 @@ Simon Drake Simon Ferquel Simon Frei Simon Jefford +Simon Law Simon Rawet Simon Rozman Simon Ser @@ -2440,6 +2526,7 @@ Suharsh Sivakumar Sukrit Handa Sunny Suriyaa Sundararuban +Suvaditya Sur Suyash Suzy Mueller Sven Almgren @@ -2502,6 +2589,7 @@ Thomas Symborski Thomas Wanielista Thorben Krueger Thordur Bjornsson +Tiago Peczenyj Tiago Queiroz Tianji Wu Tianon Gravi @@ -2636,6 +2724,7 @@ Vladimir Varankin Vojtech Bocek Volker Dobler Volodymyr Paprotski +Vyacheslav Pachkov W. Trevor King Wade Simmons Wagner Riffel @@ -2653,6 +2742,7 @@ Wei Guangjing Wei Xiao Wei Xikai Weichao Tang +Weilu Jia Weixie Cui <523516579@qq.com> Wembley G. Leach, Jr Wenlei (Frank) He @@ -2722,9 +2812,11 @@ Yuichi Nishiwaki Yuji Yaginuma Yuki Ito Yuki OKUSHI +Yuki Osaki Yuki Yugui Sonoda Yukihiro Nishinaka <6elpinal@gmail.com> YunQiang Su +Yuntao Wang Yury Smolsky Yusuke Kagiwada Yuusei Kuwana @@ -2736,7 +2828,9 @@ Zach Gershman Zach Hoffman Zach Jones Zachary Amsden +Zachary Burkett Zachary Gershman +Zaiyang Li Zak Zakatell Kanda Zellyn Hunter @@ -2745,6 +2839,7 @@ Zhang Boyang Zheng Dayu Zheng Xu Zhengyu He +Zhi Zheng Zhongpeng Lin Zhongtao Chen Zhongwei Yao diff --git a/api/go1.18.txt b/api/go1.18.txt index 7a81ce259e..0f3e26df9d 100644 --- a/api/go1.18.txt +++ b/api/go1.18.txt @@ -165,8 +165,8 @@ pkg reflect, method (Value) FieldByIndexErr([]int) (Value, error) pkg reflect, method (Value) SetIterKey(*MapIter) pkg reflect, method (Value) SetIterValue(*MapIter) pkg reflect, method (Value) UnsafePointer() unsafe.Pointer -pkg runtime/debug, method (*BuildInfo) MarshalText() ([]uint8, error) -pkg runtime/debug, method (*BuildInfo) UnmarshalText([]uint8) error +pkg runtime/debug, func ParseBuildInfo(string) (*BuildInfo, error) +pkg runtime/debug, method (*BuildInfo) String() string pkg runtime/debug, type BuildInfo struct, GoVersion string pkg runtime/debug, type BuildInfo struct, Settings []BuildSetting pkg runtime/debug, type BuildSetting struct diff --git a/codereview.cfg b/codereview.cfg index 4157a7260b..d59488856d 100644 --- a/codereview.cfg +++ b/codereview.cfg @@ -1,2 +1,2 @@ -branch: dev.boringcrypto +branch: dev.boringcrypto.go1.18 parent-branch: master diff --git a/doc/go1.18.html b/doc/go1.18.html index ed32a93bc3..b320579c37 100644 --- a/doc/go1.18.html +++ b/doc/go1.18.html @@ -84,7 +84,7 @@ Do not send CLs removing the interior tags from such phrases.
  • The new predeclared identifier - comparable is an interface the denotes the set of all types which can be + comparable is an interface that denotes the set of all types which can be compared using == or !=. It may only be used as (or embedded in) a type constraint.
  • @@ -135,9 +135,19 @@ Do not send CLs removing the interior tags from such phrases. the predeclared functions real, imag, and complex. We hope to remove this restriction in Go 1.19. +
  • + The Go compiler currently only supports calling a method m on a value + x of type parameter type P if m is explicitly + declared by P's constraint interface. + Similarly, method values x.m and method expressions + P.m also are only supported if m is explicitly + declared by P, even though m might be in the method set + of P by virtue of the fact that all types in P implement + m. We hope to remove this restriction in Go 1.19. +
  • Embedding a type parameter, or a pointer to a type parameter, as - an unnamed field in a struct type is not permitted. Similarly + an unnamed field in a struct type is not permitted. Similarly, embedding a type parameter in an interface type is not permitted. Whether these will ever be permitted is unclear at present.
  • @@ -182,7 +192,7 @@ Do not send CLs removing the interior tags from such phrases.

    Go 1.18 introduces the new GOAMD64 environment variable, which selects at compile time - a mininum target version of the AMD64 architecture. Allowed values are v1, + a minimum target version of the AMD64 architecture. Allowed values are v1, v2, v3, or v4. Each higher level requires, and takes advantage of, additional processor features. A detailed description can be found @@ -199,6 +209,12 @@ Do not send CLs removing the interior tags from such phrases. now supports the c-archive and c-shared build modes.

    +

    Linux

    + +

    + Go 1.18 requires Linux kernel version 2.6.32 or later. +

    +

    Windows

    @@ -250,6 +266,8 @@ Do not send CLs removing the interior tags from such phrases.

    Go command

    +

    go get

    +

    go get no longer builds or installs packages in module-aware mode. go get is now dedicated to @@ -269,9 +287,25 @@ Do not send CLs removing the interior tags from such phrases. and installs packages, as before.

    +

    Automatic go.mod and go.sum updates

    + +

    + The go mod graph, + go mod vendor, + go mod verify, and + go mod why subcommands + no longer automatically update the go.mod and + go.sum files. + (Those files can be updated explicitly using go get, + go mod tidy, or + go mod download.) +

    + +

    go version

    +

    The go command now embeds version control information in - binaries including the currently checked-out revision, commit time, and a + binaries. It includes the currently checked-out revision, commit time, and a flag indicating whether edited or untracked files are present. Version control information is embedded if the go command is invoked in a directory within a Git, Mercurial, Fossil, or Bazaar repository, and the @@ -281,7 +315,7 @@ Do not send CLs removing the interior tags from such phrases.

    - Additionally, the go command embeds information about the build + Additionally, the go command embeds information about the build, including build and tool tags (set with -tags), compiler, assembler, and linker flags (like -gcflags), whether cgo was enabled, and if it was, the values of the cgo environment variables @@ -303,6 +337,8 @@ Do not send CLs removing the interior tags from such phrases. debug/buildinfo package from go 1.18+.

    +

    go mod download

    +

    If the main module's go.mod file specifies go 1.17 @@ -316,6 +352,8 @@ Do not send CLs removing the interior tags from such phrases. go mod download all.

    +

    go mod vendor

    +

    The go mod vendor subcommand now supports a -o flag to set the output directory. @@ -325,14 +363,9 @@ Do not send CLs removing the interior tags from such phrases. third-party tools that need to collect package source code.)

    -

    - The go build command and related commands - now support an -asan flag that enables interoperation - with C (or C++) code compiled with the address sanitizer (C compiler - option -fsanitize=address). -

    +

    go mod tidy

    -

    +

    The go mod tidy command now retains additional checksums in the go.sum file for modules whose source code is needed to verify that each imported package is provided by only one @@ -342,11 +375,13 @@ Do not send CLs removing the interior tags from such phrases. module's go.mod file.

    +

    go work

    +

    The go command now supports a "Workspace" mode. If a go.work file is found in the working directory or a - parent directory, or one is specified using the -workfile - flag, it will put the go command into workspace mode. + parent directory, or one is specified using the GOWORK + environment variable, it will put the go command into workspace mode. In workspace mode, the go.work file will be used to determine the set of main modules used as the roots for module resolution, instead of using the normally-found go.mod @@ -355,6 +390,17 @@ Do not send CLs removing the interior tags from such phrases. documentation.

    +

    go build -asan

    + +

    + The go build command and related commands + now support an -asan flag that enables interoperation + with C (or C++) code compiled with the address sanitizer (C compiler + option -fsanitize=address). +

    + +

    go test

    +

    The go command now supports additional command line options for the new fuzzing support described @@ -376,7 +422,28 @@ Do not send CLs removing the interior tags from such phrases.

    -

    gofmt

    +

    //go:build lines

    + +

    +Go 1.17 introduced //go:build lines as a more readable way to write build constraints, +instead of // +build lines. +As of Go 1.17, gofmt adds //go:build lines +to match existing +build lines and keeps them in sync, +while go vet diagnoses when they are out of sync. +

    + +

    Since the release of Go 1.18 marks the end of support for Go 1.16, +all supported versions of Go now understand //go:build lines. +In Go 1.18, go fix now removes the now-obsolete +// +build lines in modules declaring +go 1.17 or later in their go.mod files. +

    + +

    +For more information, see https://go.dev/design/draft-gobuild. +

    + +

    Gofmt

    gofmt now reads and formats input files concurrently, with a @@ -384,7 +451,7 @@ Do not send CLs removing the interior tags from such phrases. multiple CPUs, gofmt should now be significantly faster.

    -

    vet

    +

    Vet

    Updates for Generics

    @@ -472,7 +539,7 @@ Do not send CLs removing the interior tags from such phrases.

    - The new compiler -asan option supports the + The new -asan compiler option supports the new go command -asan option.

    @@ -502,10 +569,23 @@ Do not send CLs removing the interior tags from such phrases.

    - The new linker -asan option supports the + The new -asan linker option supports the new go command -asan option.

    +

    Bootstrap

    + +

    +When building a Go release from source and GOROOT_BOOTSTRAP +is not set, previous versions of Go looked for a Go 1.4 or later bootstrap toolchain +in the directory $HOME/go1.4 (%HOMEDRIVE%%HOMEPATH%\go1.4 on Windows). +Go now looks first for $HOME/go1.17 or $HOME/sdk/go1.17 +before falling back to $HOME/go1.4. +We intend for Go 1.19 to require Go 1.17 or later for bootstrap, +and this change should make the transition smoother. +For more details, see go.dev/issue/44505. +

    +

    Core library

    New debug/buildinfo package

    @@ -630,8 +710,8 @@ Do not send CLs removing the interior tags from such phrases.

    - The methods Reader.Reset and - Writer.Reset + The Reader.Reset and + Writer.Reset methods now use the default buffer size when called on objects with a nil buffer.

    @@ -909,6 +989,19 @@ Do not send CLs removing the interior tags from such phrases. field. +

    + The predicates + AssignableTo, + ConvertibleTo, + Implements, + Identical, + IdenticalIgnoreTags, and + AssertableTo + now also work with arguments that are or contain generalized interfaces, i.e. interfaces + that may only be used as type constraints in Go code. + Note that the behavior of AssertableTo is undefined if the first argument + is a generalized interface. +

    @@ -980,7 +1073,7 @@ Do not send CLs removing the interior tags from such phrases.
    os/user

    - User.GroupIds. + User.GroupIds now uses a Go native implementation when cgo is not available.

    @@ -993,7 +1086,7 @@ Do not send CLs removing the interior tags from such phrases. Value.SetIterKey and Value.SetIterValue methods set a Value using a map iterator as the source. They are equivalent to - Value.Set(iter.Key()) and Value.Set(iter.Value()) but + Value.Set(iter.Key()) and Value.Set(iter.Value()), but do fewer allocations.

    @@ -1045,7 +1138,7 @@ Do not send CLs removing the interior tags from such phrases. The old names will continue to work, but will be deprecated in a future Go release.

    - +
    regexp
    @@ -1078,6 +1171,16 @@ Do not send CLs removing the interior tags from such phrases.
    +
    runtime/pprof
    +
    +

    + The CPU profiler now uses per-thread timers on Linux. This increases the + maximum CPU usage that a profile can observe, and reduces some forms of + bias. +

    +
    +
    +
    strconv

    @@ -1147,7 +1250,7 @@ Do not send CLs removing the interior tags from such phrases.

    - SysProcAttr.Pdeathsig. + SysProcAttr.Pdeathsig is now supported in FreeBSD.

    @@ -1156,7 +1259,7 @@ Do not send CLs removing the interior tags from such phrases.
    syscall/js

    - Wrapper interface has been removed. + The Wrapper interface has been removed.

    @@ -1228,7 +1331,7 @@ Do not send CLs removing the interior tags from such phrases.
    unicode/utf8

    - The AppendRune function appends the UTF-8 new + The new AppendRune function appends the UTF-8 encoding of a rune to a []byte.

    diff --git a/doc/go_spec.html b/doc/go_spec.html index 69ac1d353f..ad12fcfaa9 100644 --- a/doc/go_spec.html +++ b/doc/go_spec.html @@ -1,21 +1,16 @@ -

    Earlier version

    - -

    -For the pre-Go1.18 specification without generics support see -The Go Programming Language Specification. -

    -

    Introduction

    -This is a reference manual for the Go programming language. For -more information and other documents, see golang.org. +This is the reference manual for the Go programming language. +The pre-Go1.18 version, without generics, can be found +here. +For more information and other documents, see golang.org.

    @@ -679,6 +674,8 @@ or conversion, or implicitly when used in a operand in an expression. It is an error if the constant value cannot be represented as a value of the respective type. +If the type is a type parameter, the constant is converted into a non-constant +value of the type parameter.

    @@ -759,7 +756,7 @@ type given in its declaration, the type provided in the new call or composite literal, or the type of an element of a structured variable. Variables of interface type also have a distinct dynamic type, -which is the concrete type of the value assigned to the variable at run time +which is the (non-interface) type of the value assigned to the variable at run time (unless the value is the predeclared identifier nil, which has no type). The dynamic type may vary during execution but values stored in interface @@ -788,7 +785,7 @@ If a variable has not yet been assigned a value, its value is the

    A type determines a set of values together with operations and methods specific to those values. A type may be denoted by a type name, if it has one, which must be -followed by type arguments if the type is parameterized. +followed by type arguments if the type is generic. A type may also be specified using a type literal, which composes a type from existing types.

    @@ -805,7 +802,7 @@ TypeLit = ArrayType | StructType | PointerType | FunctionType | InterfaceType

    The language predeclares certain type names. Others are introduced with type declarations -or type parameter lists. +or type parameter lists. Composite types—array, struct, pointer, function, interface, slice, map, and channel types—may be constructed using type literals. @@ -816,79 +813,6 @@ Predeclared types, defined types, and type parameters are called named types< An alias denotes a named type if the type given in the alias declaration is a named type.

    -

    Underlying types

    - -

    -Each type T has an underlying type: If T -is one of the predeclared boolean, numeric, or string types, or a type literal, -the corresponding underlying type is T itself. -Otherwise, T's underlying type is the underlying type of the -type to which T refers in its type -declaration. Accordingly, the underlying type of a type parameter is the -underlying type of its type constraint, which -is always an interface. -

    - -
    -type (
    -	A1 = string
    -	A2 = A1
    -)
    -
    -type (
    -	B1 string
    -	B2 B1
    -	B3 []B1
    -	B4 B3
    -)
    -
    -func f[P any](x P) { … }
    -
    - -

    -The underlying type of string, A1, A2, B1, -and B2 is string. -The underlying type of []B1, B3, and B4 is []B1. -The underlying type of P is interface{}. -

    - -

    Method sets

    - -

    -The method set of a type determines the methods that can be -called on an operand of that type. -Every type has a (possibly empty) method set associated with it: -

    - -
      -
    • The method set of a defined type T consists of all -methods declared with receiver type T. -
    • - -
    • -The method set of a pointer to a defined type T -(where T is neither a pointer nor an interface) -is the set of all methods declared with receiver *T or T. -
    • - -
    • The method set of an interface type is the intersection -of the method sets of each type in the interface's type set -(the resulting method set is usually just the set of declared methods in the interface). -
    • -
    - -

    -Further rules apply to structs (and pointer to structs) containing embedded fields, -as described in the section on struct types. -Any other type has an empty method set. -

    - -

    -In a method set, each method must have a -unique -non-blank method name. -

    -

    Boolean types

    @@ -901,7 +825,9 @@ it is a defined type.

    Numeric types

    -A numeric type represents sets of integer or floating-point values. +An integer, floating-point, or complex type +represents the set of integer, floating-point, or complex values, respectively. +They are collectively called numeric types. The predeclared architecture-independent numeric types are:

    @@ -932,7 +858,7 @@ The value of an n-bit integer is n bits wide and represented using

    -There is also a set of predeclared numeric types with implementation-specific sizes: +There is also a set of predeclared integer types with implementation-specific sizes:

    @@ -1051,7 +977,7 @@ built-in function cap(a).
     

    -A new, initialized slice value for a given element type T is +A new, initialized slice value for a given element type T may be made using the built-in function make, which takes a slice type @@ -1274,7 +1200,8 @@ func(n int) func(p *T)

    An interface type defines a type set. A variable of interface type can store a value of any type that is in the type -set of the interface. Such a type is said to implement the interface. +set of the interface. Such a type is said to +implement the interface. The value of an uninitialized variable of interface type is nil.

    @@ -1295,11 +1222,15 @@ where a type element is a union of one or more type terms. A type term is either a single type or a single underlying type.

    +

    Basic interfaces

    +

    In its most basic form an interface specifies a (possibly empty) list of methods. The type set defined by such an interface is the set of types which implement all of those methods, and the corresponding method set consists exactly of the methods specified by the interface. +Interfaces whose type sets can be defined entirely by a list of methods are called +basic interfaces.

    @@ -1384,6 +1315,8 @@ they implement the Locker interface as well
     as the File interface.
     

    +

    Embedded interfaces

    +

    In a slightly more general form an interface T may use a (possibly qualified) interface type @@ -1428,8 +1361,10 @@ type ReadCloser interface { }

    +

    General interfaces

    +

    -Finally, in their most general form, an interface element may also be an arbitrary type term +In their most general form, an interface element may also be an arbitrary type term T, or a term of the form ~T specifying the underlying type T, or a union of terms t1|t2|…|tn. Together with method specifications, these elements enable the precise @@ -1437,7 +1372,7 @@ definition of an interface's type set as follows:

      -
    • The type set of the empty interface is the set of all types. +
    • The type set of the empty interface is the set of all non-interface types.
    • The type set of a non-empty interface is the intersection of the type sets @@ -1462,6 +1397,10 @@ definition of an interface's type set as follows:
    +

    +By construction, an interface's type set never contains an interface type. +

    +
     // An interface representing only the type int.
     interface {
    @@ -1473,7 +1412,7 @@ interface {
     	~int
     }
     
    -// An interface representing all types with underlying type int which implement the String method.
    +// An interface representing all types with underlying type int that implement the String method.
     interface {
     	~int
     	String() string
    @@ -1506,63 +1445,57 @@ Union elements denote unions of type sets:
     

    -// The Floats interface represents all floating-point types
    +// The Float interface represents all floating-point types
     // (including any named types whose underlying types are
     // either float32 or float64).
    -type Floats interface {
    +type Float interface {
     	~float32 | ~float64
     }
     

    -In a union, a term cannot be a type parameter, and the type sets of all +In a union, a term cannot be a type parameter, and the type sets of all non-interface terms must be pairwise disjoint (the pairwise intersection of the type sets must be empty). Given a type parameter P:

     interface {
    -	P                 // illegal: the term P is a type parameter
    -	int | P           // illegal: the term P is a type parameter
    -	~int | MyInt      // illegal: the type sets for ~int and MyInt are not disjoint (~int includes MyInt)
    -	float32 | Floats  // overlapping type sets but Floats is an interface
    +	P                // illegal: P is a type parameter
    +	int | P          // illegal: P is a type parameter
    +	~int | MyInt     // illegal: the type sets for ~int and MyInt are not disjoint (~int includes MyInt)
    +	float32 | Float  // overlapping type sets but Float is an interface
     }
     

    Implementation restriction: -A union with more than one term cannot contain interface types -with non-empty method sets or which -are or embed the predeclared identifier -comparable. +A union (with more than one term) cannot contain the +predeclared identifier comparable +or interfaces that specify methods, or embed comparable or interfaces +that specify methods.

    -Interfaces that contain non-interface types, terms of the form ~T, -or unions may only be used as type constraints, or as elements of other interfaces used -as constraints. They cannot be the types of values or variables, or components of other, +Interfaces that are not basic may only be used as type +constraints, or as elements of other interfaces used as constraints. +They cannot be the types of values or variables, or components of other, non-interface types.

    -var x Floats                     // illegal: Floats is restricted by float32 and float64
    +var x Float                     // illegal: Float is not a basic interface
     
    -var x interface{} = Floats(nil)  // illegal
    +var x interface{} = Float(nil)  // illegal
     
     type Floatish struct {
    -	f Floats                 // illegal
    +	f Float                 // illegal
     }
     
    - -

    -An interface type T may not embed itself -or any interface type that embeds T, recursively. +An interface type T may not embed any type element +that is, contains, or embeds T, recursively.

    @@ -1578,8 +1511,34 @@ type Bad1 interface {
     type Bad2 interface {
     	Bad1
     }
    +
    +// illegal: Bad3 cannot embed a union containing Bad3
    +type Bad3 interface {
    +	~int | ~string | Bad3
    +}
     
    +

    Implementing an interface

    + +

    +A type T implements an interface I if +

    + +
      +
    • + T is not an interface and is an element of the type set of I; or +
    • +
    • + T is an interface and the type set of T is a subset of the + type set of I. +
    • +
    + +

    +A value of type T implements an interface if T +implements the interface. +

    +

    Map types

    @@ -1723,27 +1682,118 @@ and a second goroutine receives them, the values are received in the order sent.

    -

    Type parameters

    - -

    -A type parameter is an (unqualified) type name declared in the -type parameter list of a -function declaration or -type definition; or in the receiver specification -of a method declaration that is associated -with a parameterized type. -A type parameter acts as a place holder for an (as of yet) unknown type in the declaration; -the type parameter is replaced with a type argument upon -instantiation of the parameterized function or type. -

    - -

    -The properties of a type parameter are determined by its -type constraint. -

    -

    Properties of types and values

    +

    Underlying types

    + +

    +Each type T has an underlying type: If T +is one of the predeclared boolean, numeric, or string types, or a type literal, +the corresponding underlying type is T itself. +Otherwise, T's underlying type is the underlying type of the +type to which T refers in its declaration. +For a type parameter that is the underlying type of its +type constraint, which is always an interface. +

    + +
    +type (
    +	A1 = string
    +	A2 = A1
    +)
    +
    +type (
    +	B1 string
    +	B2 B1
    +	B3 []B1
    +	B4 B3
    +)
    +
    +func f[P any](x P) { … }
    +
    + +

    +The underlying type of string, A1, A2, B1, +and B2 is string. +The underlying type of []B1, B3, and B4 is []B1. +The underlying type of P is interface{}. +

    + +

    Core types

    + +

    +Each non-interface type T has a core type, which is the same as the +underlying type of T. +

    + +

    +An interface T has a core type if one of the following +conditions is satisfied: +

    + +
      +
    1. +There is a single type U which is the underlying type +of all types in the type set of T; or +
    2. +
    3. +the type set of T contains only channel types +with identical element type E, and all directional channels have the same +direction. +
    4. +
    + +

    +No other interfaces have a core type. +

    + +

    +The core type of an interface is, depending on the condition that is satisfied, either: +

    + +
      +
    1. +the type U; or +
    2. +
    3. +the type chan E if T contains only bidirectional +channels, or the type chan<- E or <-chan E +depending on the direction of the directional channels present. +
    4. +
    + +

    +By definition, a core type is never a defined type, +type parameter, or +interface type. +

    + +

    +Examples of interfaces with core types: +

    + +
    +type Celsius float32
    +type Kelvin  float32
    +
    +interface{ int }                          // int
    +interface{ Celsius|Kelvin }               // float32
    +interface{ ~chan int }                    // chan int
    +interface{ ~chan int|~chan<- int }        // chan<- int
    +interface{ ~[]*data; String() string }    // []*data
    +
    + +

    +Examples of interfaces without core types: +

    + +
    +interface{}                               // no single underlying type
    +interface{ Celsius|float64 }              // no single underlying type
    +interface{ chan int | chan<- string }     // channels have different element types
    +interface{ <-chan int | chan<- int }      // directional channels have different directions
    +
    +

    Type identity

    @@ -1848,27 +1898,27 @@ defined type while the latter is a type literal

    Assignability

    -A value x is assignable to a variable of type T +A value x of type V is assignable to a variable of type T ("x is assignable to T") if one of the following conditions applies:

    • -x's type is identical to T. +V and T are identical.
    • -x's type V and T have identical +V and T have identical underlying types and at least one of V or T is not a named type.
    • -x's type V and T are channel types with +V and T are channel types with identical element types, V is a bidirectional channel, and at least one of V or T is not a named type.
    • T is an interface type, but not a type parameter, and -x implements T. +x implements T.
    • x is the predeclared identifier nil and T @@ -1883,25 +1933,24 @@ by a value of type T.

    -Additionally, if x's type V or T are type parameters -with specific types, x +Additionally, if x's type V or T are type parameters, x is assignable to a variable of type T if one of the following conditions applies:

    • x is the predeclared identifier nil, T is -a type parameter, and x is assignable to each specific type of -T. +a type parameter, and x is assignable to each type in +T's type set.
    • V is not a named type, T is -a type parameter, and x is assignable to each specific type of -T. +a type parameter, and x is assignable to each type in +T's type set.
    • V is a type parameter and T is not a named type, -and values of each specific type of V are assignable +and values of each type in V's type set are assignable to T.
    @@ -1911,7 +1960,7 @@ to T.

    A constant x is representable by a value of type T, -where T is not a type parameter, +where T is not a type parameter, if one of the following conditions applies:

    @@ -1921,7 +1970,7 @@ if one of the following conditions applies:
  • -T is a floating-point type and x can be rounded to T's +T is a floating-point type and x can be rounded to T's precision without overflow. Rounding uses IEEE 754 round-to-even rules but with an IEEE negative zero further simplified to an unsigned zero. Note that constant values never result in an IEEE negative zero, NaN, or infinity. @@ -1936,9 +1985,9 @@ are representable by values of T's component type (float32

    -If T is a type parameter with specific types, +If T is a type parameter, x is representable by a value of type T if x is representable -by a value of each specific type of T. +by a value of each type in T's type set.

    @@ -1968,125 +2017,43 @@ x                   T           x is not representable by a value of T because
     1e1000              float64     1e1000 overflows to IEEE +Inf after rounding
     
    -

    Structure of interfaces

    +

    Method sets

    -An interface specification which contains type elements -defines a (possibly empty) set of specific types. -Loosely speaking, these are the types T that appear in the -interface definition in terms of the form T, ~T, -or in unions of such terms. -

    - -

    -More precisely, for a given interface, the set of specific types corresponds to -the set 𝑅 of representative types of the interface, if 𝑅 is non-empty and finite. -Otherwise, if 𝑅 is empty or infinite, the interface has no specific types. -

    - -

    -For a given interface, type element or type term, the set 𝑅 of representative types is defined as follows: +The method set of a type determines the methods that can be +called on an operand of that type. +Every type has a (possibly empty) method set associated with it:

      -
    • For an interface with no type elements, 𝑅 is the (infinite) set of all types. -
    • +
    • The method set of a defined type T consists of all +methods declared with receiver type T. +
    • -
    • For an interface with type elements, - 𝑅 is the intersection of the representative types of its type elements. -
    • +
    • +The method set of a pointer to a defined type T +(where T is neither a pointer nor an interface) +is the set of all methods declared with receiver *T or T. +
    • -
    • For a non-interface type term T or a term of the form ~T, - 𝑅 is the set consisting of the type T. -
    • - -
    • For a union of terms - t1|t2|…|tn, - 𝑅 is the union of the representative types of the terms. -
    • +
    • The method set of an interface type is the intersection +of the method sets of each type in the interface's type set +(the resulting method set is usually just the set of declared methods in the interface). +

    -An interface may have specific types even if its type set -is empty. +Further rules apply to structs (and pointer to structs) containing embedded fields, +as described in the section on struct types. +Any other type has an empty method set.

    -Examples of interfaces with their specific types: +In a method set, each method must have a +unique +non-blank method name.

    -
    -type Celsius float32
    -type Kelvin  float32
    -
    -interface{}                    // no specific types
    -interface{ int }               // int
    -interface{ ~string }           // string
    -interface{ int|~string }       // int, string
    -interface{ Celsius|Kelvin }    // Celsius, Kelvin
    -interface{ float64|any }       // no specific types (union is all types)
    -interface{ int; m() }          // int (but type set is empty because int has no method m)
    -interface{ ~int; m() }         // int (but type set is infinite because many integer types have a method m)
    -interface{ int; any }          // int
    -interface{ int; string }       // no specific types (intersection is empty)
    -
    - -

    -An interface T is called structural if one of the following -conditions is satisfied: -

    - -
      -
    1. -There is a single type U which is the underlying type -of all types in the type set of T; or -
    2. -
    3. -the type set of T contains only channel types -with identical element type E, and all directional channels have the same -direction. -
    4. -
    - -

    -A structural interface has a structural type which is, depending on the -condition that is satisfied, either: -

    - -
      -
    1. -the type U; or -
    2. -
    3. -the type chan E if T contains only bidirectional -channels, or the type chan<- E or <-chan E -depending on the direction of the directional channels present. -
    4. -
    - -

    -Examples of structural interfaces with their structural types: -

    - -
    -interface{ int }                          // int
    -interface{ Celsius|Kelvin }               // float32
    -interface{ ~chan int }                    // chan int
    -interface{ ~chan int|~chan<- int }        // chan<- int
    -interface{ ~[]*data; String() string }    // []*data
    -
    - -

    -Examples of non-structural interfaces: -

    - -
    -interface{}                               // no single underlying type
    -interface{ Celsius|float64 }              // no single underlying type
    -interface{ chan int | chan<- string }     // channels have different element types
    -interface{ <-chan int | chan<- int }      // directional channels have different directions
    -
    -

    Blocks

    @@ -2133,6 +2100,7 @@ Blocks nest and influence scoping. A declaration binds a non-blank identifier to a constant, type, +type parameter, variable, function, label, or @@ -2177,13 +2145,13 @@ Go is lexically scoped using blocks:

  • The scope of an identifier denoting a method receiver, function parameter, or result variable is the function body.
  • -
  • The scope of an identifier denoting a type parameter of a type-parameterized function +
  • The scope of an identifier denoting a type parameter of a function or declared by a method receiver is the function body and all parameter lists of the function.
  • -
  • The scope of an identifier denoting a type parameter of a parameterized type - begins after the name of the parameterized type and ends at the end +
  • The scope of an identifier denoting a type parameter of a type + begins after the name of the type and ends at the end of the TypeSpec.
  • The scope of a constant or variable identifier declared @@ -2307,7 +2275,8 @@ ExpressionList = Expression { "," Expression } .

    If the type is present, all constants take the type specified, and -the expressions must be assignable to that type. +the expressions must be assignable to that type, +which must not be a type parameter. If the type is omitted, the constants take the individual types of the corresponding expressions. If the expression values are untyped constants, @@ -2468,7 +2437,7 @@ type ( type TreeNode struct { left, right *TreeNode - value *Comparable + value any } type Block interface { @@ -2529,9 +2498,9 @@ func (tz TimeZone) String() string {

  • -If the type definition specifies type parameters, -the type name denotes a parameterized type. -Parameterized types must be instantiated when they +If the type definition specifies type parameters, +the type name denotes a generic type. +Generic types must be instantiated when they are used.

    @@ -2540,15 +2509,10 @@ type List[T any] struct { next *List[T] value T } - -type Tree[T constraints.Ordered] struct { - left, right *Tree[T] - value T -}

    -The given type cannot be a type parameter in a type definition. +In a type definition the given type cannot be a type parameter.

    @@ -2560,9 +2524,9 @@ func f[T any]() {
     

    -A parameterized type may also have methods associated with it. In this case, -the method receivers must declare the same number of type parameters as -present in the parameterized type definition. +A generic type may also have methods associated with it. +In this case, the method receivers must declare the same number of type parameters as +present in the generic type definition.

    @@ -2570,11 +2534,10 @@ present in the parameterized type definition.
     func (l *List[T]) Len() int  { … }
     
    -

    Type parameter lists

    +

    Type parameter declarations

    -A type parameter list declares the type parameters -in a type-parameterized function or type declaration. +A type parameter list declares the type parameters of a generic function or type declaration. The type parameter list looks like an ordinary function parameter list except that the type parameter names must all be present and the list is enclosed in square brackets rather than parentheses. @@ -2587,9 +2550,11 @@ TypeParamDecl = IdentifierList TypeConstraint .

    -Each identifier declares a type parameter. All non-blank names in the list must be unique. -Each type parameter is a new and different named type. +Each name declares a type parameter, which is a new and different named type +that acts as a place holder for an (as of yet) unknown type in the declaration. +The type parameter is replaced with a type argument upon +instantiation of the generic function or type.

    @@ -2607,23 +2572,23 @@ has a corresponding (meta-)type which is called its
     

    -A parsing ambiguity arises when the type parameter list for a parameterized type -declares a single type parameter with a type constraint of the form *C -or (C) where C is not a (possibly parenthesized) -type literal: +A parsing ambiguity arises when the type parameter list for a generic type +declares a single type parameter P with a constraint C +such that the text P C forms a valid expression:

     type T[P *C] …
     type T[P (C)] …
    +type T[P *C|Q] …
    +…
     

    -In these rare cases, the type parameter declaration is indistinguishable from -the expressions P*C or P(C) and the type declaration -is parsed as an array type declaration. -To resolve the ambiguity, embed the constraint in an interface or use a trailing -comma: +In these rare cases, the type parameter list is indistinguishable from an +expression and the type declaration is parsed as an array type declaration. +To resolve the ambiguity, embed the constraint in an +interface or use a trailing comma:

    @@ -2631,6 +2596,17 @@ type T[P interface{*C}] …
     type T[P *C,] …
     
    +

    +Type parameters may also be declared by the receiver specification +of a method declaration associated +with a generic type. +

    + + +

    Type constraints

    @@ -2644,28 +2620,28 @@ TypeConstraint = TypeElem .

    -If the constraint is an interface literal containing exactly one embedded type element -interface{E}, in a type parameter list the enclosing interface{ … } -may be omitted for convenience: +If the constraint is an interface literal of the form interface{E} where +E is an embedded type element (not a method), in a type parameter list +the enclosing interface{ … } may be omitted for convenience:

    -[T *P]                             // = [T interface{*P}]
    -[T ~int]                           // = [T interface{~int}]
    -[T int|string]                     // = [T interface{int|string}]
    -type Constraint ~int               // illegal: ~int is not inside a type parameter list
    +[T []P]                      // = [T interface{[]P}]
    +[T ~int]                     // = [T interface{~int}]
    +[T int|string]               // = [T interface{int|string}]
    +type Constraint ~int         // illegal: ~int is not inside a type parameter list
     

    The predeclared interface type comparable -denotes the set of all concrete (non-interface) types that are +denotes the set of all non-interface types that are comparable. Specifically, a type T implements comparable if:

    @@ -2846,14 +2822,14 @@ func IndexRune(s string, r rune) int {

    -If the function declaration specifies type parameters, -the function name denotes a type-parameterized function. -Type-parameterized functions must be instantiated when they -are used. +If the function declaration specifies type parameters, +the function name denotes a generic function. +A generic function must be instantiated before it can be +called or used as a value.

    -func min[T constraints.Ordered](x, y T) T {
    +func min[T ~int|~float64](x, y T) T {
     	if x < y {
     		return x
     	}
    @@ -2912,7 +2888,7 @@ the non-blank method and field names must be distinct.
     

    -Given defined type Point, the declarations +Given defined type Point the declarations

    @@ -2933,16 +2909,13 @@ to the base type Point.
     

    -If the receiver base type is a parameterized type, the +If the receiver base type is a generic type, the receiver specification must declare corresponding type parameters for the method to use. This makes the receiver type parameters available to the method. -

    - -

    Syntactically, this type parameter declaration looks like an -instantiation of the receiver base type, except that -the type arguments are the type parameters being declared, one for each type parameter -of the receiver base type. +instantiation of the receiver base type: the type +arguments must be identifiers denoting the type parameters being declared, one +for each type parameter of the receiver base type. The type parameter names do not need to match their corresponding parameter names in the receiver base type definition, and all non-blank parameter names must be unique in the receiver parameter section and the method signature. @@ -2956,8 +2929,8 @@ type Pair[A, B any] struct { b B } -func (p Pair[A, B]) Swap() Pair[B, A] { return Pair[B, A]{p.b, p.a} } -func (p Pair[First, _]) First() First { return p.a } +func (p Pair[A, B]) Swap() Pair[B, A] { … } // receiver declares A, B +func (p Pair[First, _]) First() First { … } // receiver declares First, corresponds to A in Pair

    Expressions

    @@ -2979,8 +2952,15 @@ non-blank identifier denoting a or a parenthesized expression.

    +
    +Operand     = Literal | OperandName [ TypeArgs ] | "(" Expression ")" .
    +Literal     = BasicLit | CompositeLit | FunctionLit .
    +BasicLit    = int_lit | float_lit | imaginary_lit | rune_lit | string_lit .
    +OperandName = identifier | QualifiedIdent .
    +
    +

    -An operand name denoting a type-parameterized function +An operand name denoting a generic function may be followed by a list of type arguments; the resulting operand is an instantiated function.

    @@ -2990,12 +2970,13 @@ The blank identifier may appear as an operand only on the left-hand side of an assignment.

    -
    -Operand     = Literal | OperandName [ TypeArgs ] | "(" Expression ")" .
    -Literal     = BasicLit | CompositeLit | FunctionLit .
    -BasicLit    = int_lit | float_lit | imaginary_lit | rune_lit | string_lit .
    -OperandName = identifier | QualifiedIdent .
    -
    +

    +Implementation restriction: A compiler need not report an error if an operand's +type is a type parameter with an empty +type set. Functions with such type parameters +cannot be instantiated; any attempt will lead +to an error at the instantiation site. +

    Qualified identifiers

    @@ -3023,8 +3004,7 @@ math.Sin // denotes the Sin function in package math

    Composite literals

    -Composite literals construct values for structs, arrays, slices, and maps -and create a new value each time they are evaluated. +Composite literals construct new composite values each time they are evaluated. They consist of the type of the literal followed by a brace-bound list of elements. Each element may optionally be preceded by a corresponding key.

    @@ -3042,11 +3022,12 @@ Element = Expression | LiteralValue .

    -The LiteralType's underlying type must be a struct, array, slice, or map type +The LiteralType's core type T +must be a struct, array, slice, or map type (the grammar enforces this constraint except when the type is given as a TypeName). The types of the elements and keys must be assignable -to the respective field, element, and key types of the literal type; +to the respective field, element, and key types of type T; there is no additional conversion. The key is interpreted as a field name for struct literals, an index for array and slice literals, and a key for map literals. @@ -3108,7 +3089,7 @@ For array and slice literals the following rules apply: key must be a non-negative constant representable by a value of type int; and if it is typed - it must be of integer type. + it must be of integer type.

  • An element without a key uses the previous element's index plus one. If the first element has no key, its index is zero. @@ -3346,8 +3327,7 @@ The following rules apply to selectors: For a value x of type T or *T where T is not a pointer or interface type, x.f denotes the field or method at the shallowest depth -in T where there -is such an f. +in T where there is such an f. If there is not exactly one f with shallowest depth, the selector expression is illegal.
  • @@ -3704,10 +3684,11 @@ The following rules apply:

    -If a is not a map: +If a is neither a map nor a type parameter:

      -
    • the index x must be of integer type or an untyped constant
    • +
    • the index x must be an untyped constant or its + core type must be an integer
    • a constant index must be non-negative and representable by a value of type int
    • a constant index that is untyped is given type int
    • @@ -3772,23 +3753,22 @@ For a of map type M:

    -For a of type parameter type P: +For a of type parameter type P:

      -
    • P must have specific types.
    • The index expression a[x] must be valid for values - of all specific types of P.
    • -
    • The element types of all specific types of P must be identical. + of all types in P's type set.
    • +
    • The element types of all types in P's type set must be identical. In this context, the element type of a string type is byte.
    • -
    • If there is a map type among the specific types of P, - all specific types must be map types, and the respective key types +
    • If there is a map type in the type set of P, + all types in that type set must be map types, and the respective key types must be all identical.
    • a[x] is the array, slice, or string element at index x, or the map element with key x of the type argument that P is instantiated with, and the type of a[x] is the type of the (identical) element types.
    • -
    • a[x] may not be assigned to if the specific types of P - include string types. +
    • a[x] may not be assigned to if P's type set + includes string types.

    @@ -3829,7 +3809,7 @@ and high bound, and a full form that also specifies a bound on the capacity.

    Simple slice expressions

    -For a string, array, pointer to array, or slice a, the primary expression +The primary expression

    @@ -3837,7 +3817,9 @@ a[low : high]
     

    -constructs a substring or slice. The indices low and +constructs a substring or slice. The core type of +a must be a string, array, pointer to array, or slice. +The indices low and high select which elements of operand a appear in the result. The result has indices starting at 0 and length equal to high - low. @@ -3913,7 +3895,7 @@ s2[1] = 42 // s2[1] == s1[2] == a[5] == 42; they all refer to the same under

    Full slice expressions

    -For an array, pointer to array, or slice a (but not a string), the primary expression +The primary expression

    @@ -3924,6 +3906,8 @@ a[low : high : max]
     constructs a slice of the same type, and with the same length and elements as the simple slice
     expression a[low : high]. Additionally, it controls the resulting slice's capacity
     by setting it to max - low. Only the first index may be omitted; it defaults to 0.
    +The core type of a must be an array, pointer to array,
    +or slice (but not a string).
     After slicing the array a
     

    @@ -3962,7 +3946,7 @@ If the indices are out of range at run time, a run-ti

    For an expression x of interface type, -but not a type parameter, and a type T, +but not a type parameter, and a type T, the primary expression

    @@ -3983,7 +3967,7 @@ In this case, T must implement the (inte otherwise the type assertion is invalid since it is not possible for x to store a value of type T. If T is an interface type, x.(T) asserts that the dynamic type -of x implements the interface T. +of x implements the interface T.

    If the type assertion holds, the value of the expression is the value @@ -4029,8 +4013,8 @@ No run-time panic occurs in this case.

    Calls

    -Given an expression f of function type -F, +Given an expression f with a core type +F of function type,

    @@ -4056,7 +4040,7 @@ pt.Scale(3.5)     // method call with receiver pt
     

    -If f denotes a parameterized function, it must be +If f denotes a generic function, it must be instantiated before it can be called or used as a function value.

    @@ -4175,14 +4159,14 @@ with the same underlying array.

    Instantiations

    -A parameterized function or type is instantiated by substituting type arguments +A generic function or type is instantiated by substituting type arguments for the type parameters. -Instantiation proceeds in two phases: +Instantiation proceeds in two steps:

    1. -Each type argument is substituted for its corresponding type parameter in the parameterized +Each type argument is substituted for its corresponding type parameter in the generic declaration. This substitution happens across the entire function or type declaration, including the type parameter list itself and any types in that list. @@ -4190,72 +4174,75 @@ including the type parameter list itself and any types in that list.
    2. After substitution, each type argument must implement -the constraint (instantiated, if necessary) +the constraint (instantiated, if necessary) of the corresponding type parameter. Otherwise instantiation fails.

    -Instantiating a type results in a new non-parameterized named type; -instantiating a function produces a new non-parameterized function. +Instantiating a type results in a new non-generic named type; +instantiating a function produces a new non-generic function.

     type parameter list    type arguments    after substitution
     
    -[P any]                int               [int any]
    -[S ~[]E, E any]        []int, int        [[]int ~[]int, int any]
    -[P io.Writer]          string            [string io.Writer]         // illegal: string doesn't implement io.Writer
    +[P any]                int               int implements any
    +[S ~[]E, E any]        []int, int        []int implements ~[]int, int implements any
    +[P io.Writer]          string            illegal: string doesn't implement io.Writer
     

    -Type arguments may be provided explicitly, or they may be partially or completely -inferred. -A partially provided type argument list cannot be empty; there must be at least the -first argument. -

    - -
    -type T[P1 ~int, P2 ~[]P1] struct{ … }
    -
    -T[]            // illegal: at least the first type argument must be present, even if it could be inferred
    -T[int]         // argument for P1 explicitly provided, argument for P2 inferred
    -T[int, []int]  // both arguments explicitly provided
    -
    - -

    -A partial type argument list specifies a prefix of the full list of type arguments, leaving -the remaining arguments to be inferred. Loosely speaking, type arguments may be omitted from -"right to left". -

    - -

    -Parameterized types, and parameterized functions that are not called, -require a type argument list for instantiation; if the list is partial, all +For a generic function, type arguments may be provided explicitly, or they +may be partially or completely inferred. +A generic function that is is not called requires a +type argument list for instantiation; if the list is partial, all remaining type arguments must be inferrable. -Calls to parameterized functions may provide a (possibly partial) type +A generic function that is called may provide a (possibly partial) type argument list, or may omit it entirely if the omitted type arguments are inferrable from the ordinary (non-type) function arguments.

    -func min[T constraints.Ordered](x, y T) T { … }
    +func min[T ~int|~float64](x, y T) T { … }
     
    -f := min                   // illegal: min must be instantiated when used without being called
    +f := min                   // illegal: min must be instantiated with type arguments when used without being called
     minInt := min[int]         // minInt has type func(x, y int) int
     a := minInt(2, 3)          // a has value 2 of type int
     b := min[float64](2.0, 3)  // b has value 2.0 of type float64
     c := min(b, -1)            // c has value -1.0 of type float64
     
    +

    +A partial type argument list cannot be empty; at least the first argument must be present. +The list is a prefix of the full list of type arguments, leaving the remaining arguments +to be inferred. Loosely speaking, type arguments may be omitted from "right to left". +

    + +
    +func apply[S ~[]E, E any](s S, f(E) E) S { … }
    +
    +f0 := apply[]                  // illegal: type argument list cannot be empty
    +f1 := apply[[]int]             // type argument for S explicitly provided, type argument for E inferred
    +f2 := apply[[]string, string]  // both type arguments explicitly provided
    +
    +var bytes []byte
    +r := apply(bytes, func(byte) byte { … })  // both type arguments inferred from the function arguments
    +
    + +

    +For a generic type, all type arguments must always be provided explicitly. +

    +

    Type inference

    -Missing type arguments may be inferred by a series of steps, described below. +Missing function type arguments may be inferred by a series of steps, described below. Each step attempts to use known information to infer additional type arguments. Type inference stops as soon as all type arguments are known. After type inference is complete, it is still necessary to substitute all type arguments -for type parameters and verify that each type argument implements the relevant constraint; +for type parameters and verify that each type argument +implements the relevant constraint; it is possible for an inferred type argument to fail to implement a constraint, in which case instantiation fails.

    @@ -4266,7 +4253,7 @@ Type inference is based on
    • - a type parameter list + a type parameter list
    • a substitution map M initialized with the known type arguments, if any @@ -4309,7 +4296,7 @@ The process stops as soon as M has a type argument for each type paramete If an inference step fails, or if M is still missing type arguments after the last step, type inference fails.

      -

      Type unification

      +

      Type unification

      Type inference is based on type unification. A single unification step @@ -4386,7 +4373,7 @@ and the type literal []E, unification compares []float64 -

      Function argument type inference

      +

      Function argument type inference

      +

      Constraint type inference

      -Constraint type inference infers type arguments from already known -type arguments by considering structural type constraints: -if the structural type T of a structural constraint is parameterized, -unifying a known type argument with T may -infer type arguments for other type parameters used by the structural type. +Constraint type inference infers type arguments by considering type constraints. +If a type parameter P has a constraint with a +core type C, +unifying P with C +may infer additional type arguments, either the type argument for P, +or if that is already known, possibly the type arguments for type parameters +used in C.

      @@ -4521,8 +4514,8 @@ For instance, consider the type parameter list with type parameters List Constraint type inference can deduce the type of Elem from the type argument -for List because Elem is a type parameter in the structural constraint -~[]Elem for List. +for List because Elem is a type parameter in the core type +[]Elem of List. If the type argument is Bytes:

      @@ -4531,7 +4524,7 @@ type Bytes []byte

    -unifying the underlying type of Bytes with the structural constraint means +unifying the underlying type of Bytes with the core type means unifying []byte with []Elem. That unification succeeds and yields the substitution map entry Elembyte. @@ -4539,6 +4532,17 @@ Thus, in this example, constraint type inference can infer the second type argum first one.

    +

    +Using the core type of a constraint may lose some information: In the (unlikely) case that +the constraint's type set contains a single defined type +N, the corresponding core type is N's underlying type rather than +N itself. In this case, constraint type inference may succeed but instantiation +will fail because the inferred type is not in the type set of the constraint. +Thus, constraint type inference uses the adjusted core type of +a constraint: if the type set contains a single type, use that type; otherwise use the +constraint's core type. +

    +

    Generally, constraint type inference proceeds in two phases: Starting with a given substitution map M @@ -4546,8 +4550,8 @@ substitution map M

    1. -For all type parameters with a structural constraint, unify the type parameter with the structural -type of its constraint. If any unification fails, constraint type inference fails. +For all type parameters with an adjusted core type, unify the type parameter with that +type. If any unification fails, constraint type inference fails.
    2. @@ -4582,7 +4586,7 @@ the initial substitution map M contains the entry A &RightAr

      In the first phase, the type parameters B and C are unified -with the structural type of their respective constraints. This adds the entries +with the core type of their respective constraints. This adds the entries B[]C and C*A to M. @@ -4660,7 +4664,7 @@ to the type of the other operand.

      -The right operand in a shift expression must have integer type +The right operand in a shift expression must have integer type or be an untyped constant representable by a value of type uint. If the left operand of a non-constant shift expression is an untyped constant, @@ -4740,8 +4744,9 @@ x == y+1 && <-chanInt > 0

      Arithmetic operators apply to numeric values and yield a result of the same type as the first operand. The four standard arithmetic operators (+, --, *, /) apply to integer, -floating-point, and complex types; + also applies to strings. +-, *, /) apply to +integer, floating-point, and +complex types; + also applies to strings. The bitwise logical and shift operators apply to integers only.

      @@ -4761,6 +4766,31 @@ The bitwise logical and shift operators apply to integers only. >> right shift integer >> integer >= 0 +

      +If the operand type is a type parameter, +the operator must apply to each type in that type set. +The operands are represented as values of the type argument that the type parameter +is instantiated with, and the operation is computed +with the precision of that type argument. For example, given the function: +

      + +
      +func dotProduct[F ~float32|~float64](v1, v2 []F) F {
      +	var s F
      +	for i, x := range v1 {
      +		y := v2[i]
      +		s += x * y
      +	}
      +	return s
      +}
      +
      + +

      +the product x * y and the addition s += x * y +are computed with float32 or float64 precision, +respectively, depending on the type argument for F. +

      +

      Integer operators

      @@ -4846,10 +4876,10 @@ follows:

      Integer overflow

      -For unsigned integer values, the operations +, +For unsigned integer values, the operations +, -, *, and << are computed modulo 2n, where n is the bit width of -the unsigned integer's type. +the unsigned integer's type. Loosely speaking, these unsigned integer operations discard high bits upon overflow, and programs may rely on "wrap around".

      @@ -4864,7 +4894,6 @@ A compiler may not optimize code under the assumption that overflow does not occur. For instance, it may not assume that x < x + 1 is always true.

      -

      Floating-point operators

      @@ -4880,7 +4909,7 @@ occurs is implementation-specific. An implementation may combine multiple floating-point operations into a single fused operation, possibly across statements, and produce a result that differs from the value obtained by executing and rounding the instructions individually. -An explicit floating-point type conversion rounds to +An explicit floating-point type conversion rounds to the precision of the target type, preventing fusion that would discard that rounding.

      @@ -4920,7 +4949,6 @@ s += " and good bye" String addition creates a new string by concatenating the operands.

      -

      Comparison operators

      @@ -4999,7 +5027,7 @@ These terms and the result of the comparisons are defined as follows: A value x of non-interface type X and a value t of interface type T are comparable when values of type X are comparable and - X implements T. + X implements T. They are equal if t's dynamic type is identical to X and t's dynamic value is equal to x.

    3. @@ -5101,7 +5129,8 @@ var x *int = nil

      Receive operator

      -For an operand ch of channel type, +For an operand ch whose core type is a +channel, the value of the receive operation <-ch is the value received from the channel ch. The channel direction must permit receive operations, and the type of the receive operation is the element type of the channel. @@ -5187,9 +5216,8 @@ as for non-constant x.

      -Converting a constant to a type that is not a type parameter +Converting a constant to a type that is not a type parameter yields a typed constant. -Converting a constant to a type parameter yields a non-constant value of that type.

      @@ -5207,6 +5235,29 @@ int(1.2)                 // illegal: 1.2 cannot be represented as an int
       string(65.0)             // illegal: 65.0 is not an integer constant
       
      +

      +Converting a constant to a type parameter yields a non-constant value of that type, +with the value represented as a value of the type argument that the type parameter +is instantiated with. +For example, given the function: +

      + +
      +func f[P ~float32|~float64]() {
      +	… P(1.1) …
      +}
      +
      + +

      +the conversion P(1.1) results in a non-constant value of type P +and the value 1.1 is represented as a float32 or a float64 +depending on the type argument for f. +Accordingly, if f is instantiated with a float32 type, +the numeric value of the expression P(1.1) + 1.2 will be computed +with the same precision as the corresponding non-constant float32 +addition. +

      +

      A non-constant value x can be converted to type T in any of these cases: @@ -5220,7 +5271,7 @@ in any of these cases:

    4. ignoring struct tags (see below), x's type and T are not - type parameters but have + type parameters but have identical underlying types.
    5. @@ -5251,24 +5302,24 @@ in any of these cases:

      -Additionally, if T or x's type V are type -parameters with specific types, x +Additionally, if T or x's type V are type +parameters, x can also be converted to type T if one of the following conditions applies:

      • Both V and T are type parameters and a value of each -specific type of V can be converted to each specific type -of T. +type in V's type set can be converted to each type in T's +type set.
      • Only V is a type parameter and a value of each -specific type of V can be converted to T. +type in V's type set can be converted to T.
      • Only T is a type parameter and x can be converted to each -specific type of T. +type in T's type set.
      @@ -5309,8 +5360,7 @@ of x.

      There is no linguistic mechanism to convert between pointers and integers. The package unsafe -implements this functionality under -restricted circumstances. +implements this functionality under restricted circumstances.

      Conversions between numeric types

      @@ -5321,19 +5371,19 @@ For the conversion of non-constant numeric values, the following rules apply:
      1. -When converting between integer types, if the value is a signed integer, it is +When converting between integer types, if the value is a signed integer, it is sign extended to implicit infinite precision; otherwise it is zero extended. It is then truncated to fit in the result type's size. For example, if v := uint16(0x10F0), then uint32(int8(v)) == 0xFFFFFFF0. The conversion always yields a valid value; there is no indication of overflow.
      2. -When converting a floating-point number to an integer, the fraction is discarded +When converting a floating-point number to an integer, the fraction is discarded (truncation towards zero).
      3. When converting an integer or floating-point number to a floating-point type, -or a complex number to another complex type, the result value is rounded +or a complex number to another complex type, the result value is rounded to the precision specified by the destination type. For instance, the value of a variable x of type float32 may be stored using additional precision beyond that of an IEEE-754 32-bit number, @@ -5792,7 +5842,8 @@ len("foo") // illegal if len is the built-in function

        A send statement sends a value on a channel. -The channel expression must be of channel type, +The channel expression's core type +must be a channel, the channel direction must permit send operations, and the type of the value to be sent must be assignable to the channel's element type. @@ -6139,7 +6190,7 @@ switch x.(type) { Cases then match actual types T against the dynamic type of the expression x. As with type assertions, x must be of interface type, but not a -type parameter, and each non-interface type +type parameter, and each non-interface type T listed in a case must implement the type of x. The types listed in the cases of a type switch must all be different. @@ -6220,6 +6271,32 @@ if v == nil { } +

        +A type parameter or a generic type +may be used as a type in a case. If upon instantiation that type turns +out to duplicate another entry in the switch, the first matching case is chosen. +

        + +
        +func f[P any](x any) int {
        +	switch x.(type) {
        +	case P:
        +		return 0
        +	case string:
        +		return 1
        +	case []P:
        +		return 2
        +	case []byte:
        +		return 3
        +	default:
        +		return 4
        +	}
        +}
        +
        +var v1 = f[string]("foo")   // v1 == 0
        +var v2 = f[byte]([]byte{})  // v2 == 2
        +
        +

        The type switch guard may be preceded by a simple statement, which executes before the guard is evaluated. @@ -6312,7 +6389,8 @@ RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Expression .

        The expression on the right in the "range" clause is called the range expression, -which may be an array, pointer to an array, slice, string, map, or channel permitting +its core type must be +an array, pointer to an array, slice, string, map, or channel permitting receive operations. As with an assignment, if present the operands on the left must be addressable or map index expressions; they @@ -6897,9 +6975,10 @@ they cannot be used as function values.

        Close

        -For a channel c, the built-in function close(c) +For an argument ch with a core type +that is a channel, the built-in function close records that no more values will be sent on the channel. -It is an error if c is a receive-only channel. +It is an error if ch is a receive-only channel. Sending to or closing a closed channel causes a run-time panic. Closing the nil channel also causes a run-time panic. After calling close, and after any previously @@ -6934,10 +7013,9 @@ cap(s) [n]T, *[n]T array length (== n)

        -If the argument type is a type parameter P, -P must have specific types, and +If the argument type is a type parameter P, the call len(e) (or cap(e) respectively) must be valid for -each specific type of P. +each type in P's type set. The result is the length (or capacity, respectively) of the argument whose type corresponds to the type argument with which P was instantiated. @@ -7015,30 +7093,32 @@ of the location.

        The built-in function make takes a type T, -which must be a slice, map or channel type, optionally followed by a type-specific list of expressions. +The core type of T must +be a slice, map or channel. It returns a value of type T (not *T). The memory is initialized as described in the section on initial values.

        -Call             Type T     Result
        +Call             Core type    Result
         
        -make(T, n)       slice      slice of type T with length n and capacity n
        -make(T, n, m)    slice      slice of type T with length n and capacity m
        +make(T, n)       slice        slice of type T with length n and capacity n
        +make(T, n, m)    slice        slice of type T with length n and capacity m
         
        -make(T)          map        map of type T
        -make(T, n)       map        map of type T with initial space for approximately n elements
        +make(T)          map          map of type T
        +make(T, n)       map          map of type T with initial space for approximately n elements
         
        -make(T)          channel    unbuffered channel of type T
        -make(T, n)       channel    buffered channel of type T, buffer size n
        +make(T)          channel      unbuffered channel of type T
        +make(T, n)       channel      buffered channel of type T, buffer size n
         

        -Each of the size arguments n and m must be of integer type -or an untyped constant. +Each of the size arguments n and m must be of integer type, +have a type set containing only integer types, +or be an untyped constant. A constant size argument must be non-negative and representable by a value of type int; if it is an untyped constant it is given type int. If both n and m are provided and are constant, then @@ -7074,21 +7154,20 @@ by the arguments overlaps.

        The variadic function append -appends zero or more values x -to s of type S, which must be a slice type, and -returns the resulting slice, also of type S. -The values x are passed to a parameter of type ...T -where T is the element type of -S and the respective -parameter passing rules apply. -As a special case, append also accepts a first argument -assignable to type []byte with a second argument of -string type followed by .... This form appends the -bytes of the string. +appends zero or more values x to a slice s +and returns the resulting slice of the same type as s. +The core type of s must be a slice +of type []E. +The values x are passed to a parameter of type ...E +and the respective parameter +passing rules apply. +As a special case, if the core type of s is []byte, +append also accepts a second argument with core type string +followed by .... This form appends the bytes of the string.

        -append(s S, x ...T) S  // T is the element type of S
        +append(s S, x ...E) S  // core type of S is []E
         

        @@ -7116,12 +7195,12 @@ b = append(b, "bar"...) // append string contents b == []byte{'b The function copy copies slice elements from a source src to a destination dst and returns the number of elements copied. -Both arguments must have identical element type T and must be -assignable to a slice of type []T. +The core types of both arguments must be slices +with identical element type. The number of elements copied is the minimum of len(src) and len(dst). -As a special case, copy also accepts a destination argument assignable -to type []byte with a source argument of a string type. +As a special case, if the destination's core type is []byte, +copy also accepts a source argument with core type string. This form copies the bytes from the string into the byte slice.

        @@ -7149,7 +7228,7 @@ n3 := copy(b, "Hello, World!") // n3 == 5, b == []byte("Hello")

        The built-in function delete removes the element with key k from a map m. The -type of k must be assignable +value k must be assignable to the key type of m.

        @@ -7157,6 +7236,11 @@ to the key type of m. delete(m, k) // remove element m[k] from map m +

        +If the type of m is a type parameter, +all types in that type set must be maps, and they must all have identical key types. +

        +

        If the map m is nil or the element m[k] does not exist, delete is a no-op. @@ -7182,7 +7266,8 @@ imag(complexT) floatT

        The type of the arguments and return value correspond. For complex, the two arguments must be of the same -floating-point type and the return type is the complex type +floating-point type and the return type is the +complex type with the corresponding floating-point constituents: complex64 for float32 arguments, and complex128 for float64 arguments. @@ -7226,6 +7311,10 @@ const c = imag(b) // untyped constant -1.4 _ = imag(3 << s) // illegal: 3 assumes complex type, cannot shift +

        +Arguments of type parameter type are not permitted. +

        +

        Handling panics

        Two built-in functions, panic and recover, @@ -7244,7 +7333,7 @@ an explicit call to panic or a run-time terminates the execution of F. Any functions deferred by F are then executed as usual. -Next, any deferred functions run by F's caller are run, +Next, any deferred functions run by F's caller are run, and so on up to any deferred by the top-level function in the executing goroutine. At that point, the program is terminated and the error condition is reported, including the value of the argument to panic. @@ -7834,11 +7923,17 @@ func Add(ptr Pointer, len IntegerType) Pointer func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType + +

        A Pointer is a pointer type but a Pointer value may not be dereferenced. -Any pointer or value of underlying type uintptr can be converted to -a type of underlying type Pointer and vice versa. +Any pointer or value of underlying type uintptr can be +converted to a type of underlying type Pointer and vice versa. The effect of converting between Pointer and uintptr is implementation-defined.

        @@ -7885,7 +7980,8 @@ uintptr(unsafe.Pointer(&x)) % unsafe.Alignof(x) == 0

        A (variable of) type T has variable size if T -is a type parameter, or if it is an array or struct type containing elements +is a type parameter, or if it is an +array or struct type containing elements or fields of variable size. Otherwise the size is constant. Calls to Alignof, Offsetof, and Sizeof are compile-time constant expressions of @@ -7897,7 +7993,7 @@ of constant size.

        The function Add adds len to ptr and returns the updated pointer unsafe.Pointer(uintptr(ptr) + uintptr(len)). -The len argument must be of integer type or an untyped constant. +The len argument must be of integer type or an untyped constant. A constant len argument must be representable by a value of type int; if it is an untyped constant it is given type int. The rules for valid uses of Pointer still apply. @@ -7920,7 +8016,7 @@ is nil and len is zero,

        -The len argument must be of integer type or an untyped constant. +The len argument must be of integer type or an untyped constant. A constant len argument must be non-negative and representable by a value of type int; if it is an untyped constant it is given type int. At run time, if len is negative, diff --git a/misc/cgo/testsanitizers/asan_test.go b/misc/cgo/testsanitizers/asan_test.go index 1b70bce3d1..22dcf23c3b 100644 --- a/misc/cgo/testsanitizers/asan_test.go +++ b/misc/cgo/testsanitizers/asan_test.go @@ -63,7 +63,7 @@ func TestASAN(t *testing.T) { // sanitizer library needs a // symbolizer program and can't find it. const noSymbolizer = "external symbolizer" - // Check if -asan option can correctly print where the error occured. + // Check if -asan option can correctly print where the error occurred. if tc.errorLocation != "" && !strings.Contains(out, tc.errorLocation) && !strings.Contains(out, noSymbolizer) && diff --git a/misc/reboot/overlaydir_test.go b/misc/reboot/overlaydir_test.go index c446d0891c..71faf0936b 100644 --- a/misc/reboot/overlaydir_test.go +++ b/misc/reboot/overlaydir_test.go @@ -6,6 +6,7 @@ package reboot_test import ( "io" + "io/fs" "os" "path/filepath" "strings" @@ -26,10 +27,14 @@ func overlayDir(dstRoot, srcRoot string) error { return err } - return filepath.Walk(srcRoot, func(srcPath string, info os.FileInfo, err error) error { + return filepath.WalkDir(srcRoot, func(srcPath string, entry fs.DirEntry, err error) error { if err != nil || srcPath == srcRoot { return err } + if filepath.Base(srcPath) == "testdata" { + // We're just building, so no need to copy those. + return fs.SkipDir + } suffix := strings.TrimPrefix(srcPath, srcRoot) for len(suffix) > 0 && suffix[0] == filepath.Separator { @@ -37,6 +42,7 @@ func overlayDir(dstRoot, srcRoot string) error { } dstPath := filepath.Join(dstRoot, suffix) + info, err := entry.Info() perm := info.Mode() & os.ModePerm if info.Mode()&os.ModeSymlink != 0 { info, err = os.Stat(srcPath) @@ -46,14 +52,15 @@ func overlayDir(dstRoot, srcRoot string) error { perm = info.Mode() & os.ModePerm } - // Always copy directories (don't symlink them). + // Always make copies of directories. // If we add a file in the overlay, we don't want to add it in the original. if info.IsDir() { return os.MkdirAll(dstPath, perm|0200) } - // If the OS supports symlinks, use them instead of copying bytes. - if err := os.Symlink(srcPath, dstPath); err == nil { + // If we can use a hard link, do that instead of copying bytes. + // Go builds don't like symlinks in some cases, such as go:embed. + if err := os.Link(srcPath, dstPath); err == nil { return nil } diff --git a/misc/reboot/reboot_test.go b/misc/reboot/reboot_test.go index ef164d3232..a134affbc2 100644 --- a/misc/reboot/reboot_test.go +++ b/misc/reboot/reboot_test.go @@ -12,6 +12,7 @@ import ( "path/filepath" "runtime" "testing" + "time" ) func TestRepeatBootstrap(t *testing.T) { @@ -19,16 +20,14 @@ func TestRepeatBootstrap(t *testing.T) { t.Skipf("skipping test that rebuilds the entire toolchain") } - goroot, err := os.MkdirTemp("", "reboot-goroot") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(goroot) + goroot := t.TempDir() gorootSrc := filepath.Join(goroot, "src") + overlayStart := time.Now() if err := overlayDir(gorootSrc, filepath.Join(runtime.GOROOT(), "src")); err != nil { t.Fatal(err) } + t.Logf("GOROOT/src overlay set up in %s", time.Since(overlayStart)) if err := os.WriteFile(filepath.Join(goroot, "VERSION"), []byte(runtime.Version()), 0666); err != nil { t.Fatal(err) diff --git a/src/builtin/builtin.go b/src/builtin/builtin.go index 08ae7ed313..5657be4564 100644 --- a/src/builtin/builtin.go +++ b/src/builtin/builtin.go @@ -95,11 +95,11 @@ type rune = int32 type any = interface{} // comparable is an interface that is implemented by all comparable types -// (booleans, numbers, strings, pointers, channels, interfaces, -// arrays of comparable types, structs whose fields are all comparable types). +// (booleans, numbers, strings, pointers, channels, arrays of comparable types, +// structs whose fields are all comparable types). // The comparable interface may only be used as a type parameter constraint, // not as the type of a variable. -type comparable comparable +type comparable interface{ comparable } // iota is a predeclared identifier representing the untyped integer ordinal // number of the current const specification in a (usually parenthesized) diff --git a/src/bytes/bytes.go b/src/bytes/bytes.go index 6fdaa49c73..41323ad549 100644 --- a/src/bytes/bytes.go +++ b/src/bytes/bytes.go @@ -372,6 +372,8 @@ func genSplit(s, sep []byte, sepSave, n int) [][]byte { // n > 0: at most n subslices; the last subslice will be the unsplit remainder. // n == 0: the result is nil (zero subslices) // n < 0: all subslices +// +// To split around the first instance of a separator, see Cut. func SplitN(s, sep []byte, n int) [][]byte { return genSplit(s, sep, 0, n) } // SplitAfterN slices s into subslices after each instance of sep and @@ -389,6 +391,8 @@ func SplitAfterN(s, sep []byte, n int) [][]byte { // the subslices between those separators. // If sep is empty, Split splits after each UTF-8 sequence. // It is equivalent to SplitN with a count of -1. +// +// To split around the first instance of a separator, see Cut. func Split(s, sep []byte) [][]byte { return genSplit(s, sep, 0, -1) } // SplitAfter slices s into all subslices after each instance of sep and diff --git a/src/cmd/compile/abi-internal.md b/src/cmd/compile/abi-internal.md index 7fe4463665..53eaa84d54 100644 --- a/src/cmd/compile/abi-internal.md +++ b/src/cmd/compile/abi-internal.md @@ -155,7 +155,7 @@ as follows: 1. Remember I and FP. 1. If T has zero size, add T to the stack sequence S and return. 1. Try to register-assign V. -1. If step 2 failed, reset I and FP to the values from step 1, add T +1. If step 3 failed, reset I and FP to the values from step 1, add T to the stack sequence S, and assign V to this field in S. Register-assignment of a value V of underlying type T works as follows: diff --git a/src/cmd/compile/internal/base/base.go b/src/cmd/compile/internal/base/base.go index be6d49fac7..39ce8e66f7 100644 --- a/src/cmd/compile/internal/base/base.go +++ b/src/cmd/compile/internal/base/base.go @@ -62,8 +62,9 @@ func Compiling(pkgs []string) bool { // at best instrumentation would cause infinite recursion. var NoInstrumentPkgs = []string{ "runtime/internal/atomic", - "runtime/internal/sys", "runtime/internal/math", + "runtime/internal/sys", + "runtime/internal/syscall", "runtime", "runtime/race", "runtime/msan", diff --git a/src/cmd/compile/internal/escape/call.go b/src/cmd/compile/internal/escape/call.go index d1215afca8..ee76adb0fa 100644 --- a/src/cmd/compile/internal/escape/call.go +++ b/src/cmd/compile/internal/escape/call.go @@ -238,6 +238,15 @@ func (e *escape) goDeferStmt(n *ir.GoDeferStmt) { fn.SetWrapper(true) fn.Nname.SetType(types.NewSignature(types.LocalPkg, nil, nil, nil, nil)) fn.Body = []ir.Node{call} + if call, ok := call.(*ir.CallExpr); ok && call.Op() == ir.OCALLFUNC { + // If the callee is a named function, link to the original callee. + x := call.X + if x.Op() == ir.ONAME && x.(*ir.Name).Class == ir.PFUNC { + fn.WrappedFunc = call.X.(*ir.Name).Func + } else if x.Op() == ir.OMETHEXPR && ir.MethodExprFunc(x).Nname != nil { + fn.WrappedFunc = ir.MethodExprName(x).Func + } + } clo := fn.OClosure if n.Op() == ir.OGO { diff --git a/src/cmd/compile/internal/escape/escape.go b/src/cmd/compile/internal/escape/escape.go index c2145bdf91..bc6f7c93bb 100644 --- a/src/cmd/compile/internal/escape/escape.go +++ b/src/cmd/compile/internal/escape/escape.go @@ -10,6 +10,7 @@ import ( "cmd/compile/internal/base" "cmd/compile/internal/ir" "cmd/compile/internal/logopt" + "cmd/compile/internal/typecheck" "cmd/compile/internal/types" ) @@ -243,6 +244,9 @@ func (b *batch) flowClosure(k hole, clo *ir.ClosureExpr) { n.SetByval(!loc.addrtaken && !loc.reassigned && n.Type().Size() <= 128) if !n.Byval() { n.SetAddrtaken(true) + if n.Sym().Name == typecheck.LocalDictName { + base.FatalfAt(n.Pos(), "dictionary variable not captured by value") + } } if base.Flag.LowerM > 1 { diff --git a/src/cmd/compile/internal/gc/obj.go b/src/cmd/compile/internal/gc/obj.go index dcb54047f1..74e4c0a890 100644 --- a/src/cmd/compile/internal/gc/obj.go +++ b/src/cmd/compile/internal/gc/obj.go @@ -217,6 +217,10 @@ func dumpGlobalConst(n ir.Node) { if ir.ConstOverflow(v, t) { return } + } else { + // If the type of the constant is an instantiated generic, we need to emit + // that type so the linker knows about it. See issue 51245. + _ = reflectdata.TypeLinksym(t) } base.Ctxt.DwarfIntConst(base.Ctxt.Pkgpath, n.Sym().Name, types.TypeSymName(t), ir.IntVal(t, v)) } @@ -263,6 +267,10 @@ func addGCLocals() { objw.Global(x, int32(len(x.P)), obj.RODATA|obj.DUPOK) x.Set(obj.AttrStatic, true) } + if x := fn.WrapInfo; x != nil && !x.OnList() { + objw.Global(x, int32(len(x.P)), obj.RODATA|obj.DUPOK) + x.Set(obj.AttrStatic, true) + } } } diff --git a/src/cmd/compile/internal/importer/iimport.go b/src/cmd/compile/internal/importer/iimport.go index a827987a48..bed4fbb016 100644 --- a/src/cmd/compile/internal/importer/iimport.go +++ b/src/cmd/compile/internal/importer/iimport.go @@ -180,6 +180,14 @@ func ImportData(imports map[string]*types2.Package, data, path string) (pkg *typ p.doDecl(localpkg, name) } + // SetConstraint can't be called if the constraint type is not yet complete. + // When type params are created in the 'P' case of (*importReader).obj(), + // the associated constraint type may not be complete due to recursion. + // Therefore, we defer calling SetConstraint there, and call it here instead + // after all types are complete. + for _, d := range p.later { + d.t.SetConstraint(d.constraint) + } // record all referenced packages as imports list := append(([]*types2.Package)(nil), pkgList[1:]...) sort.Sort(byPath(list)) @@ -191,6 +199,11 @@ func ImportData(imports map[string]*types2.Package, data, path string) (pkg *typ return localpkg, nil } +type setConstraintArgs struct { + t *types2.TypeParam + constraint types2.Type +} + type iimporter struct { exportVersion int64 ipath string @@ -206,6 +219,9 @@ type iimporter struct { tparamIndex map[ident]*types2.TypeParam interfaceList []*types2.Interface + + // Arguments for calls to SetConstraint that are deferred due to recursive types + later []setConstraintArgs } func (p *iimporter) doDecl(pkg *types2.Package, name string) { @@ -401,7 +417,11 @@ func (r *importReader) obj(name string) { } iface.MarkImplicit() } - t.SetConstraint(constraint) + // The constraint type may not be complete, if we + // are in the middle of a type recursion involving type + // constraints. So, we defer SetConstraint until we have + // completely set up all types in ImportData. + r.p.later = append(r.p.later, setConstraintArgs{t: t, constraint: constraint}) case 'V': typ := r.typ() diff --git a/src/cmd/compile/internal/ir/func.go b/src/cmd/compile/internal/ir/func.go index 41c96079f7..23d56f7234 100644 --- a/src/cmd/compile/internal/ir/func.go +++ b/src/cmd/compile/internal/ir/func.go @@ -133,6 +133,10 @@ type Func struct { // function for go:nowritebarrierrec analysis. Only filled in // if nowritebarrierrecCheck != nil. NWBRCalls *[]SymAndPos + + // For wrapper functions, WrappedFunc point to the original Func. + // Currently only used for go/defer wrappers. + WrappedFunc *Func } func NewFunc(pos src.XPos) *Func { diff --git a/src/cmd/compile/internal/ir/sizeof_test.go b/src/cmd/compile/internal/ir/sizeof_test.go index a4421fcf53..72b6320261 100644 --- a/src/cmd/compile/internal/ir/sizeof_test.go +++ b/src/cmd/compile/internal/ir/sizeof_test.go @@ -20,7 +20,7 @@ func TestSizeof(t *testing.T) { _32bit uintptr // size on 32bit platforms _64bit uintptr // size on 64bit platforms }{ - {Func{}, 192, 328}, + {Func{}, 196, 336}, {Name{}, 112, 200}, } diff --git a/src/cmd/compile/internal/ir/stmt.go b/src/cmd/compile/internal/ir/stmt.go index e7d0d873b7..80bd205436 100644 --- a/src/cmd/compile/internal/ir/stmt.go +++ b/src/cmd/compile/internal/ir/stmt.go @@ -362,7 +362,7 @@ func NewSendStmt(pos src.XPos, ch, value Node) *SendStmt { return n } -// A SwitchStmt is a switch statement: switch Init; Expr { Cases }. +// A SwitchStmt is a switch statement: switch Init; Tag { Cases }. type SwitchStmt struct { miniStmt Tag Node diff --git a/src/cmd/compile/internal/noder/expr.go b/src/cmd/compile/internal/noder/expr.go index 8a9afeb095..4b5ae706c1 100644 --- a/src/cmd/compile/internal/noder/expr.go +++ b/src/cmd/compile/internal/noder/expr.go @@ -114,7 +114,7 @@ func (g *irgen) expr0(typ types2.Type, expr syntax.Expr) ir.Node { case *syntax.CallExpr: fun := g.expr(expr.Fun) - return Call(pos, g.typ(typ), fun, g.exprs(expr.ArgList), expr.HasDots) + return g.callExpr(pos, g.typ(typ), fun, g.exprs(expr.ArgList), expr.HasDots) case *syntax.IndexExpr: args := unpackListExpr(expr.Index) @@ -206,6 +206,53 @@ func (g *irgen) substType(typ *types.Type, tparams *types.Type, targs []ir.Node) return newt } +// callExpr creates a call expression (which might be a type conversion, built-in +// call, or a regular call) and does standard transforms, unless we are in a generic +// function. +func (g *irgen) callExpr(pos src.XPos, typ *types.Type, fun ir.Node, args []ir.Node, dots bool) ir.Node { + n := ir.NewCallExpr(pos, ir.OCALL, fun, args) + n.IsDDD = dots + typed(typ, n) + + if fun.Op() == ir.OTYPE { + // Actually a type conversion, not a function call. + if !g.delayTransform() { + return transformConvCall(n) + } + return n + } + + if fun, ok := fun.(*ir.Name); ok && fun.BuiltinOp != 0 { + if !g.delayTransform() { + return transformBuiltin(n) + } + return n + } + + // Add information, now that we know that fun is actually being called. + switch fun := fun.(type) { + case *ir.SelectorExpr: + if fun.Op() == ir.OMETHVALUE { + op := ir.ODOTMETH + if fun.X.Type().IsInterface() { + op = ir.ODOTINTER + } + fun.SetOp(op) + // Set the type to include the receiver, since that's what + // later parts of the compiler expect + fun.SetType(fun.Selection.Type) + } + } + + // A function instantiation (even if fully concrete) shouldn't be + // transformed yet, because we need to add the dictionary during the + // transformation. + if fun.Op() != ir.OFUNCINST && !g.delayTransform() { + transformCall(n) + } + return n +} + // selectorExpr resolves the choice of ODOT, ODOTPTR, OMETHVALUE (eventually // ODOTMETH & ODOTINTER), and OMETHEXPR and deals with embedded fields here rather // than in typecheck.go. @@ -332,13 +379,13 @@ func (g *irgen) exprs(exprs []syntax.Expr) []ir.Node { } func (g *irgen) compLit(typ types2.Type, lit *syntax.CompositeLit) ir.Node { - if ptr, ok := types2.StructuralType(typ).(*types2.Pointer); ok { + if ptr, ok := types2.CoreType(typ).(*types2.Pointer); ok { n := ir.NewAddrExpr(g.pos(lit), g.compLit(ptr.Elem(), lit)) n.SetOp(ir.OPTRLIT) return typed(g.typ(typ), n) } - _, isStruct := types2.StructuralType(typ).(*types2.Struct) + _, isStruct := types2.CoreType(typ).(*types2.Struct) exprs := make([]ir.Node, len(lit.ElemList)) for i, elem := range lit.ElemList { diff --git a/src/cmd/compile/internal/noder/helpers.go b/src/cmd/compile/internal/noder/helpers.go index 5524673e66..33acd6051a 100644 --- a/src/cmd/compile/internal/noder/helpers.go +++ b/src/cmd/compile/internal/noder/helpers.go @@ -98,95 +98,6 @@ func Binary(pos src.XPos, op ir.Op, typ *types.Type, x, y ir.Node) *ir.BinaryExp } } -func Call(pos src.XPos, typ *types.Type, fun ir.Node, args []ir.Node, dots bool) ir.Node { - n := ir.NewCallExpr(pos, ir.OCALL, fun, args) - n.IsDDD = dots - - if fun.Op() == ir.OTYPE { - // Actually a type conversion, not a function call. - if !fun.Type().IsInterface() && - (fun.Type().HasTParam() || args[0].Type().HasTParam()) { - // For type params, we can transform if fun.Type() is known - // to be an interface (in which case a CONVIFACE node will be - // inserted). Otherwise, don't typecheck until we actually - // know the type. - return typed(typ, n) - } - typed(typ, n) - return transformConvCall(n) - } - - if fun, ok := fun.(*ir.Name); ok && fun.BuiltinOp != 0 { - // For most Builtin ops, we delay doing transformBuiltin if any of the - // args have type params, for a variety of reasons: - // - // OMAKE: transformMake can't choose specific ops OMAKESLICE, etc. - // until arg type is known - // OREAL/OIMAG: transformRealImag can't determine type float32/float64 - // until arg type known - // OAPPEND: transformAppend requires that the arg is a slice - // ODELETE: transformDelete requires that the arg is a map - // OALIGNOF, OSIZEOF: can be eval'ed to a constant until types known. - switch fun.BuiltinOp { - case ir.OMAKE, ir.OREAL, ir.OIMAG, ir.OAPPEND, ir.ODELETE, ir.OALIGNOF, ir.OOFFSETOF, ir.OSIZEOF: - hasTParam := false - for _, arg := range args { - if fun.BuiltinOp == ir.OOFFSETOF { - // It's the type of left operand of the - // selection that matters, not the type of - // the field itself (which is irrelevant for - // offsetof). - arg = arg.(*ir.SelectorExpr).X - } - if arg.Type().HasTParam() { - hasTParam = true - break - } - } - if hasTParam { - return typed(typ, n) - } - } - - typed(typ, n) - return transformBuiltin(n) - } - - // Add information, now that we know that fun is actually being called. - switch fun := fun.(type) { - case *ir.SelectorExpr: - if fun.Op() == ir.OMETHVALUE { - op := ir.ODOTMETH - if fun.X.Type().IsInterface() { - op = ir.ODOTINTER - } - fun.SetOp(op) - // Set the type to include the receiver, since that's what - // later parts of the compiler expect - fun.SetType(fun.Selection.Type) - } - } - - if fun.Type().HasTParam() || fun.Op() == ir.OXDOT || fun.Op() == ir.OFUNCINST { - // If the fun arg is or has a type param, we can't do all the - // transformations, since we may not have needed properties yet - // (e.g. number of return values, etc). The same applies if a fun - // which is an XDOT could not be transformed yet because of a generic - // type in the X of the selector expression. - // - // A function instantiation (even if fully concrete) shouldn't be - // transformed yet, because we need to add the dictionary during the - // transformation. - return typed(typ, n) - } - - // If no type params, do the normal call transformations. This - // will convert OCALL to OCALLFUNC. - typed(typ, n) - transformCall(n) - return n -} - func Compare(pos src.XPos, typ *types.Type, op ir.Op, x, y ir.Node) *ir.BinaryExpr { n := ir.NewBinaryExpr(pos, op, x, y) typed(typ, n) diff --git a/src/cmd/compile/internal/noder/stencil.go b/src/cmd/compile/internal/noder/stencil.go index 50b6c0efcd..cd586cab78 100644 --- a/src/cmd/compile/internal/noder/stencil.go +++ b/src/cmd/compile/internal/noder/stencil.go @@ -410,7 +410,8 @@ func (g *genInst) buildClosure(outer *ir.Func, x ir.Node) ir.Node { fn, formalParams, formalResults := startClosure(pos, outer, typ) // This is the dictionary we want to use. - // It may be a constant, or it may be a dictionary acquired from the outer function's dictionary. + // It may be a constant, it may be the outer functions's dictionary, or it may be + // a subdictionary acquired from the outer function's dictionary. // For the latter, dictVar is a variable in the outer function's scope, set to the subdictionary // read from the outer function's dictionary. var dictVar *ir.Name @@ -640,6 +641,11 @@ func (g *genInst) getInstantiation(nameNode *ir.Name, shapes []*types.Type, isMe // over any pointer) recvType := nameNode.Type().Recv().Type recvType = deref(recvType) + if recvType.IsFullyInstantiated() { + // Get the type of the base generic type, so we get + // its original typeparams. + recvType = recvType.OrigSym().Def.(*ir.Name).Type() + } tparams = recvType.RParams() } else { fields := nameNode.Type().TParams().Fields().Slice() @@ -656,11 +662,9 @@ func (g *genInst) getInstantiation(nameNode *ir.Name, shapes []*types.Type, isMe s1 := make([]*types.Type, len(shapes)) for i, t := range shapes { var tparam *types.Type - if tparams[i].Kind() == types.TTYPEPARAM { - // Shapes are grouped differently for structural types, so we - // pass the type param to Shapify(), so we can distinguish. - tparam = tparams[i] - } + // Shapes are grouped differently for structural types, so we + // pass the type param to Shapify(), so we can distinguish. + tparam = tparams[i] if !t.IsShape() { s1[i] = typecheck.Shapify(t, i, tparam) } else { @@ -1055,8 +1059,6 @@ func (subst *subster) node(n ir.Node) ir.Node { // Transform the conversion, now that we know the // type argument. m = transformConvCall(call) - // CONVIFACE transformation was already done in noder2 - assert(m.Op() != ir.OCONVIFACE) case ir.OMETHVALUE, ir.OMETHEXPR: // Redo the transformation of OXDOT, now that we @@ -1076,14 +1078,7 @@ func (subst *subster) node(n ir.Node) ir.Node { case ir.ONAME: name := call.X.Name() if name.BuiltinOp != ir.OXXX { - switch name.BuiltinOp { - case ir.OMAKE, ir.OREAL, ir.OIMAG, ir.OAPPEND, ir.ODELETE, ir.OALIGNOF, ir.OOFFSETOF, ir.OSIZEOF: - // Transform these builtins now that we - // know the type of the args. - m = transformBuiltin(call) - default: - base.FatalfAt(call.Pos(), "Unexpected builtin op") - } + m = transformBuiltin(call) } else { // This is the case of a function value that was a // type parameter (implied to be a function via a @@ -1154,6 +1149,7 @@ func (subst *subster) node(n ir.Node) ir.Node { newfn.Dcl = append(newfn.Dcl, ldict) as := ir.NewAssignStmt(x.Pos(), ldict, cdict) as.SetTypecheck(1) + ldict.Defn = as newfn.Body.Append(as) // Create inst info for the instantiated closure. The dict @@ -1183,6 +1179,26 @@ func (subst *subster) node(n ir.Node) ir.Node { subst.g.newInsts = append(subst.g.newInsts, m.(*ir.ClosureExpr).Func) m.(*ir.ClosureExpr).SetInit(subst.list(x.Init())) + case ir.OSWITCH: + m := m.(*ir.SwitchStmt) + if m.Tag != nil && m.Tag.Op() == ir.OTYPESW { + break // Nothing to do here for type switches. + } + if m.Tag != nil && !m.Tag.Type().IsInterface() && m.Tag.Type().HasShape() { + // To implement a switch on a value that is or has a type parameter, we first convert + // that thing we're switching on to an interface{}. + m.Tag = assignconvfn(m.Tag, types.Types[types.TINTER]) + } + for _, c := range m.Cases { + for i, x := range c.List { + // If we have a case that is or has a type parameter, convert that case + // to an interface{}. + if !x.Type().IsInterface() && x.Type().HasShape() { + c.List[i] = assignconvfn(x, types.Types[types.TINTER]) + } + } + } + } return m } diff --git a/src/cmd/compile/internal/noder/transform.go b/src/cmd/compile/internal/noder/transform.go index 5f1f41163b..db28e8de3b 100644 --- a/src/cmd/compile/internal/noder/transform.go +++ b/src/cmd/compile/internal/noder/transform.go @@ -242,7 +242,7 @@ func transformCompare(n *ir.BinaryExpr) { aop, _ := typecheck.Assignop(rt, lt) if aop != ir.OXXX { types.CalcSize(rt) - if rt.HasTParam() || rt.IsInterface() == lt.IsInterface() || rt.Size() >= 1<<16 { + if rt.HasShape() || rt.IsInterface() == lt.IsInterface() || rt.Size() >= 1<<16 { r = ir.NewConvExpr(base.Pos, aop, lt, r) r.SetTypecheck(1) } diff --git a/src/cmd/compile/internal/noder/writer.go b/src/cmd/compile/internal/noder/writer.go index 933f577825..46e8339120 100644 --- a/src/cmd/compile/internal/noder/writer.go +++ b/src/cmd/compile/internal/noder/writer.go @@ -1338,10 +1338,10 @@ func (w *writer) compLit(lit *syntax.CompositeLit) { w.typ(tv.Type) typ := tv.Type - if ptr, ok := types2.StructuralType(typ).(*types2.Pointer); ok { + if ptr, ok := types2.CoreType(typ).(*types2.Pointer); ok { typ = ptr.Elem() } - str, isStruct := types2.StructuralType(typ).(*types2.Struct) + str, isStruct := types2.CoreType(typ).(*types2.Struct) w.len(len(lit.ElemList)) for i, elem := range lit.ElemList { diff --git a/src/cmd/compile/internal/reflectdata/reflect.go b/src/cmd/compile/internal/reflectdata/reflect.go index e76f9c5f3c..28afb7212c 100644 --- a/src/cmd/compile/internal/reflectdata/reflect.go +++ b/src/cmd/compile/internal/reflectdata/reflect.go @@ -1457,7 +1457,7 @@ func WriteBasicTypes() { type typeAndStr struct { t *types.Type - short string // "short" here means NameString + short string // "short" here means TypeSymName regular string } diff --git a/src/cmd/compile/internal/ssa/gen/RISCV64.rules b/src/cmd/compile/internal/ssa/gen/RISCV64.rules index 3379e1dac5..96b24a6380 100644 --- a/src/cmd/compile/internal/ssa/gen/RISCV64.rules +++ b/src/cmd/compile/internal/ssa/gen/RISCV64.rules @@ -256,7 +256,7 @@ (Leq64F ...) => (FLED ...) (Leq32F ...) => (FLES ...) -(EqPtr x y) => (SEQZ (SUB x y)) +(EqPtr x y) => (SEQZ (SUB x y)) (Eq64 x y) => (SEQZ (SUB x y)) (Eq32 x y) => (SEQZ (SUB (ZeroExt32to64 x) (ZeroExt32to64 y))) (Eq16 x y) => (SEQZ (SUB (ZeroExt16to64 x) (ZeroExt16to64 y))) @@ -264,7 +264,7 @@ (Eq64F ...) => (FEQD ...) (Eq32F ...) => (FEQS ...) -(NeqPtr x y) => (SNEZ (SUB x y)) +(NeqPtr x y) => (SNEZ (SUB x y)) (Neq64 x y) => (SNEZ (SUB x y)) (Neq32 x y) => (SNEZ (SUB (ZeroExt32to64 x) (ZeroExt32to64 y))) (Neq16 x y) => (SNEZ (SUB (ZeroExt16to64 x) (ZeroExt16to64 y))) diff --git a/src/cmd/compile/internal/ssa/poset.go b/src/cmd/compile/internal/ssa/poset.go index d2719eb8a1..ee884ca761 100644 --- a/src/cmd/compile/internal/ssa/poset.go +++ b/src/cmd/compile/internal/ssa/poset.go @@ -906,7 +906,7 @@ func (po *poset) Ordered(n1, n2 *Value) bool { return i1 != i2 && po.reaches(i1, i2, true) } -// Ordered reports whether n1<=n2. It returns false either when it is +// OrderedOrEqual reports whether n1<=n2. It returns false either when it is // certain that n1<=n2 is false, or if there is not enough information // to tell. // Complexity is O(n). diff --git a/src/cmd/compile/internal/ssa/rewriteRISCV64.go b/src/cmd/compile/internal/ssa/rewriteRISCV64.go index 885bbaf4a1..a67d13e0da 100644 --- a/src/cmd/compile/internal/ssa/rewriteRISCV64.go +++ b/src/cmd/compile/internal/ssa/rewriteRISCV64.go @@ -1124,13 +1124,14 @@ func rewriteValueRISCV64_OpEqPtr(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] b := v.Block + typ := &b.Func.Config.Types // match: (EqPtr x y) - // result: (SEQZ (SUB x y)) + // result: (SEQZ (SUB x y)) for { x := v_0 y := v_1 v.reset(OpRISCV64SEQZ) - v0 := b.NewValue0(v.Pos, OpRISCV64SUB, x.Type) + v0 := b.NewValue0(v.Pos, OpRISCV64SUB, typ.Uintptr) v0.AddArg2(x, y) v.AddArg(v0) return true @@ -2673,13 +2674,14 @@ func rewriteValueRISCV64_OpNeqPtr(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] b := v.Block + typ := &b.Func.Config.Types // match: (NeqPtr x y) - // result: (SNEZ (SUB x y)) + // result: (SNEZ (SUB x y)) for { x := v_0 y := v_1 v.reset(OpRISCV64SNEZ) - v0 := b.NewValue0(v.Pos, OpRISCV64SUB, x.Type) + v0 := b.NewValue0(v.Pos, OpRISCV64SUB, typ.Uintptr) v0.AddArg2(x, y) v.AddArg(v0) return true diff --git a/src/cmd/compile/internal/ssagen/ssa.go b/src/cmd/compile/internal/ssagen/ssa.go index 0b54925696..364e0c8197 100644 --- a/src/cmd/compile/internal/ssagen/ssa.go +++ b/src/cmd/compile/internal/ssagen/ssa.go @@ -6768,6 +6768,34 @@ func EmitArgInfo(f *ir.Func, abiInfo *abi.ABIParamResultInfo) *obj.LSym { return x } +// for wrapper, emit info of wrapped function. +func emitWrappedFuncInfo(e *ssafn, pp *objw.Progs) { + if base.Ctxt.Flag_linkshared { + // Relative reference (SymPtrOff) to another shared object doesn't work. + // Unfortunate. + return + } + + wfn := e.curfn.WrappedFunc + if wfn == nil { + return + } + + wsym := wfn.Linksym() + x := base.Ctxt.LookupInit(fmt.Sprintf("%s.wrapinfo", wsym.Name), func(x *obj.LSym) { + objw.SymPtrOff(x, 0, wsym) + x.Set(obj.AttrContentAddressable, true) + }) + e.curfn.LSym.Func().WrapInfo = x + + // Emit a funcdata pointing at the wrap info data. + p := pp.Prog(obj.AFUNCDATA) + p.From.SetConst(objabi.FUNCDATA_WrapInfo) + p.To.Type = obj.TYPE_MEM + p.To.Name = obj.NAME_EXTERN + p.To.Sym = x +} + // genssa appends entries to pp for each instruction in f. func genssa(f *ssa.Func, pp *objw.Progs) { var s State @@ -6790,6 +6818,8 @@ func genssa(f *ssa.Func, pp *objw.Progs) { p.To.Sym = openDeferInfo } + emitWrappedFuncInfo(e, pp) + // Remember where each block starts. s.bstart = make([]*obj.Prog, f.NumBlocks()) s.pp = pp diff --git a/src/cmd/compile/internal/test/testdata/ptrsort.go b/src/cmd/compile/internal/test/testdata/ptrsort.go index 6cc7ba4851..d26ba581d9 100644 --- a/src/cmd/compile/internal/test/testdata/ptrsort.go +++ b/src/cmd/compile/internal/test/testdata/ptrsort.go @@ -6,7 +6,7 @@ package main import ( "fmt" - "./mysort" + "cmd/compile/internal/test/testdata/mysort" ) type MyString struct { diff --git a/src/cmd/compile/internal/typecheck/iexport.go b/src/cmd/compile/internal/typecheck/iexport.go index ae3c41ca04..b7a251c71f 100644 --- a/src/cmd/compile/internal/typecheck/iexport.go +++ b/src/cmd/compile/internal/typecheck/iexport.go @@ -1851,7 +1851,10 @@ func (w *exportWriter) expr(n ir.Node) { n := n.(*ir.ClosureExpr) w.op(ir.OCLOSURE) w.pos(n.Pos()) + old := w.currPkg + w.setPkg(n.Type().Pkg(), true) w.signature(n.Type()) + w.setPkg(old, true) // Write out id for the Outer of each conditional variable. The // conditional variable itself for this closure will be re-created diff --git a/src/cmd/compile/internal/typecheck/iimport.go b/src/cmd/compile/internal/typecheck/iimport.go index bc34d3933a..28a50605aa 100644 --- a/src/cmd/compile/internal/typecheck/iimport.go +++ b/src/cmd/compile/internal/typecheck/iimport.go @@ -354,15 +354,18 @@ func (r *importReader) doDecl(sym *types.Sym) *ir.Name { // declaration before recursing. n := importtype(pos, sym) t := n.Type() + + // Because of recursion, we need to defer width calculations and + // instantiations on intermediate types until the top-level type is + // fully constructed. Note that we can have recursion via type + // constraints. + types.DeferCheckSize() + deferDoInst() if tag == 'U' { rparams := r.typeList() t.SetRParams(rparams) } - // We also need to defer width calculations until - // after the underlying type has been assigned. - types.DeferCheckSize() - deferDoInst() underlying := r.typ() t.SetUnderlying(underlying) @@ -1371,7 +1374,9 @@ func (r *importReader) node() ir.Node { case ir.OCLOSURE: //println("Importing CLOSURE") pos := r.pos() + r.setPkg() typ := r.signature(nil, nil) + r.setPkg() // All the remaining code below is similar to (*noder).funcLit(), but // with Dcls and ClosureVars lists already set up diff --git a/src/cmd/compile/internal/typecheck/subr.go b/src/cmd/compile/internal/typecheck/subr.go index 9892471142..5147ebbd2c 100644 --- a/src/cmd/compile/internal/typecheck/subr.go +++ b/src/cmd/compile/internal/typecheck/subr.go @@ -1424,6 +1424,68 @@ func genericTypeName(sym *types.Sym) string { return sym.Name[0:strings.Index(sym.Name, "[")] } +// getShapes appends the list of the shape types that are used within type t to +// listp. The type traversal is simplified for two reasons: (1) we can always stop a +// type traversal when t.HasShape() is false; and (2) shape types can't appear inside +// a named type, except for the type args of a generic type. So, the traversal will +// always stop before we have to deal with recursive types. +func getShapes(t *types.Type, listp *[]*types.Type) { + if !t.HasShape() { + return + } + if t.IsShape() { + *listp = append(*listp, t) + return + } + + if t.Sym() != nil { + // A named type can't have shapes in it, except for type args of a + // generic type. We will have to deal with this differently once we + // alloc local types in generic functions (#47631). + for _, rparam := range t.RParams() { + getShapes(rparam, listp) + } + return + } + + switch t.Kind() { + case types.TARRAY, types.TPTR, types.TSLICE, types.TCHAN: + getShapes(t.Elem(), listp) + + case types.TSTRUCT: + for _, f := range t.FieldSlice() { + getShapes(f.Type, listp) + } + + case types.TFUNC: + for _, f := range t.Recvs().FieldSlice() { + getShapes(f.Type, listp) + } + for _, f := range t.Params().FieldSlice() { + getShapes(f.Type, listp) + } + for _, f := range t.Results().FieldSlice() { + getShapes(f.Type, listp) + } + for _, f := range t.TParams().FieldSlice() { + getShapes(f.Type, listp) + } + + case types.TINTER: + for _, f := range t.Methods().Slice() { + getShapes(f.Type, listp) + } + + case types.TMAP: + getShapes(t.Key(), listp) + getShapes(t.Elem(), listp) + + default: + panic(fmt.Sprintf("Bad type in getShapes: %v", t.Kind())) + } + +} + // Shapify takes a concrete type and a type param index, and returns a GCshape type that can // be used in place of the input type and still generate identical code. // No methods are added - all methods calls directly on a shape should @@ -1432,9 +1494,9 @@ func genericTypeName(sym *types.Sym) string { // For now, we only consider two types to have the same shape, if they have exactly // the same underlying type or they are both pointer types. // -// tparam is the associated typeparam. If there is a structural type for -// the associated type param (not common), then a pointer type t is mapped to its -// underlying type, rather than being merged with other pointers. +// tparam is the associated typeparam - it must be TTYPEPARAM type. If there is a +// structural type for the associated type param (not common), then a pointer type t +// is mapped to its underlying type, rather than being merged with other pointers. // // Shape types are also distinguished by the index of the type in a type param/arg // list. We need to do this so we can distinguish and substitute properly for two @@ -1442,6 +1504,30 @@ func genericTypeName(sym *types.Sym) string { // instantiation. func Shapify(t *types.Type, index int, tparam *types.Type) *types.Type { assert(!t.IsShape()) + if t.HasShape() { + // We are sometimes dealing with types from a shape instantiation + // that were constructed from existing shape types, so t may + // sometimes have shape types inside it. In that case, we find all + // those shape types with getShapes() and replace them with their + // underlying type. + // + // If we don't do this, we may create extra unneeded shape types that + // have these other shape types embedded in them. This may lead to + // generating extra shape instantiations, and a mismatch between the + // instantiations that we used in generating dictionaries and the + // instantations that are actually called. (#51303). + list := []*types.Type{} + getShapes(t, &list) + list2 := make([]*types.Type, len(list)) + for i, shape := range list { + list2[i] = shape.Underlying() + } + ts := Tsubster{ + Tparams: list, + Targs: list2, + } + t = ts.Typ(t) + } // Map all types with the same underlying type to the same shape. u := t.Underlying() diff --git a/src/cmd/compile/internal/types/fmt.go b/src/cmd/compile/internal/types/fmt.go index e1b395559a..c7d0623d40 100644 --- a/src/cmd/compile/internal/types/fmt.go +++ b/src/cmd/compile/internal/types/fmt.go @@ -72,6 +72,7 @@ const ( fmtDebug fmtTypeID fmtTypeIDName + fmtTypeIDHash ) // Sym @@ -144,10 +145,21 @@ func symfmt(b *bytes.Buffer, s *Sym, verb rune, mode fmtMode) { if q := pkgqual(s.Pkg, verb, mode); q != "" { b.WriteString(q) b.WriteByte('.') - if mode == fmtTypeIDName { + switch mode { + case fmtTypeIDName: // If name is a generic instantiation, it might have local package placeholders // in it. Replace those placeholders with the package name. See issue 49547. name = strings.Replace(name, LocalPkg.Prefix, q, -1) + case fmtTypeIDHash: + // If name is a generic instantiation, don't hash the instantiating types. + // This isn't great, but it is safe. If we hash the instantiating types, then + // we need to make sure they have just the package name. At this point, they + // either have "", or the whole package path, and it is hard to reconcile + // the two without depending on -p (which we might do someday). + // See issue 51250. + if i := strings.Index(name, "["); i >= 0 { + name = name[:i] + } } } b.WriteString(name) @@ -173,7 +185,7 @@ func pkgqual(pkg *Pkg, verb rune, mode fmtMode) string { case fmtDebug: return pkg.Name - case fmtTypeIDName: + case fmtTypeIDName, fmtTypeIDHash: // dcommontype, typehash return pkg.Name @@ -331,7 +343,7 @@ func tconv2(b *bytes.Buffer, t *Type, verb rune, mode fmtMode, visited map[*Type if t == AnyType || t == ByteType || t == RuneType { // in %-T mode collapse predeclared aliases with their originals. switch mode { - case fmtTypeIDName, fmtTypeID: + case fmtTypeIDName, fmtTypeIDHash, fmtTypeID: t = Types[t.Kind()] default: sconv2(b, t.Sym(), 'S', mode) @@ -422,7 +434,7 @@ func tconv2(b *bytes.Buffer, t *Type, verb rune, mode fmtMode, visited map[*Type case TPTR: b.WriteByte('*') switch mode { - case fmtTypeID, fmtTypeIDName: + case fmtTypeID, fmtTypeIDName, fmtTypeIDHash: if verb == 'S' { tconv2(b, t.Elem(), 'S', mode, visited) return @@ -484,7 +496,7 @@ func tconv2(b *bytes.Buffer, t *Type, verb rune, mode fmtMode, visited map[*Type case IsExported(f.Sym.Name): sconv2(b, f.Sym, 'S', mode) default: - if mode != fmtTypeIDName { + if mode != fmtTypeIDName && mode != fmtTypeIDHash { mode = fmtTypeID } sconv2(b, f.Sym, 'v', mode) @@ -554,7 +566,7 @@ func tconv2(b *bytes.Buffer, t *Type, verb rune, mode fmtMode, visited map[*Type b.WriteByte(byte(open)) fieldVerb := 'v' switch mode { - case fmtTypeID, fmtTypeIDName, fmtGo: + case fmtTypeID, fmtTypeIDName, fmtTypeIDHash, fmtGo: // no argument names on function signature, and no "noescape"/"nosplit" tags fieldVerb = 'S' } @@ -657,7 +669,7 @@ func fldconv(b *bytes.Buffer, f *Field, verb rune, mode fmtMode, visited map[*Ty // Compute tsym, the symbol that would normally be used as // the field name when embedding f.Type. - // TODO(mdempsky): Check for other occurences of this logic + // TODO(mdempsky): Check for other occurrences of this logic // and deduplicate. typ := f.Type if typ.IsPtr() { @@ -688,7 +700,7 @@ func fldconv(b *bytes.Buffer, f *Field, verb rune, mode fmtMode, visited map[*Ty if name == ".F" { name = "F" // Hack for toolstash -cmp. } - if !IsExported(name) && mode != fmtTypeIDName { + if !IsExported(name) && mode != fmtTypeIDName && mode != fmtTypeIDHash { name = sconv(s, 0, mode) // qualify non-exported names (used on structs, not on funarg) } } else { @@ -756,7 +768,7 @@ func FmtConst(v constant.Value, sharp bool) string { // TypeHash computes a hash value for type t to use in type switch statements. func TypeHash(t *Type) uint32 { - p := t.NameString() + p := tconv(t, 0, fmtTypeIDHash) // Using MD5 is overkill, but reduces accidental collisions. h := md5.Sum([]byte(p)) diff --git a/src/cmd/compile/internal/types2/api.go b/src/cmd/compile/internal/types2/api.go index ee4f275bc0..d864c96fb6 100644 --- a/src/cmd/compile/internal/types2/api.go +++ b/src/cmd/compile/internal/types2/api.go @@ -204,12 +204,12 @@ type Info struct { // qualified identifiers are collected in the Uses map. Types map[syntax.Expr]TypeAndValue - // Instances maps identifiers denoting parameterized types or functions to - // their type arguments and instantiated type. + // Instances maps identifiers denoting generic types or functions to their + // type arguments and instantiated type. // // For example, Instances will map the identifier for 'T' in the type // instantiation T[int, string] to the type arguments [int, string] and - // resulting instantiated *Named type. Given a parameterized function + // resulting instantiated *Named type. Given a generic function // func F[A any](A), Instances will map the identifier for 'F' in the call // expression F(int(1)) to the inferred type arguments [int], and resulting // instantiated *Signature. @@ -421,25 +421,45 @@ func (conf *Config) Check(path string, files []*syntax.File, info *Info) (*Packa } // AssertableTo reports whether a value of type V can be asserted to have type T. +// +// The behavior of AssertableTo is undefined in two cases: +// - if V is a generalized interface; i.e., an interface that may only be used +// as a type constraint in Go code +// - if T is an uninstantiated generic type func AssertableTo(V *Interface, T Type) bool { - m, _ := (*Checker)(nil).assertableTo(V, T) - return m == nil + // Checker.newAssertableTo suppresses errors for invalid types, so we need special + // handling here. + if T.Underlying() == Typ[Invalid] { + return false + } + return (*Checker)(nil).newAssertableTo(V, T) == nil } -// AssignableTo reports whether a value of type V is assignable to a variable of type T. +// AssignableTo reports whether a value of type V is assignable to a variable +// of type T. +// +// The behavior of AssignableTo is undefined if V or T is an uninstantiated +// generic type. func AssignableTo(V, T Type) bool { x := operand{mode: value, typ: V} ok, _ := x.assignableTo(nil, T, nil) // check not needed for non-constant x return ok } -// ConvertibleTo reports whether a value of type V is convertible to a value of type T. +// ConvertibleTo reports whether a value of type V is convertible to a value of +// type T. +// +// The behavior of ConvertibleTo is undefined if V or T is an uninstantiated +// generic type. func ConvertibleTo(V, T Type) bool { x := operand{mode: value, typ: V} return x.convertibleTo(nil, T, nil) // check not needed for non-constant x } // Implements reports whether type V implements interface T. +// +// The behavior of Implements is undefined if V is an uninstantiated generic +// type. func Implements(V Type, T *Interface) bool { if T.Empty() { // All types (even Typ[Invalid]) implement the empty interface. diff --git a/src/cmd/compile/internal/types2/api_test.go b/src/cmd/compile/internal/types2/api_test.go index 094374f7f1..5bb551798e 100644 --- a/src/cmd/compile/internal/types2/api_test.go +++ b/src/cmd/compile/internal/types2/api_test.go @@ -12,6 +12,7 @@ import ( "internal/testenv" "reflect" "regexp" + "sort" "strings" "testing" @@ -403,69 +404,61 @@ func TestTypesInfo(t *testing.T) { } func TestInstanceInfo(t *testing.T) { - var tests = []struct { - src string + const lib = `package lib + +func F[P any](P) {} + +type T[P any] []P +` + + type testInst struct { name string targs []string typ string + } + + var tests = []struct { + src string + instances []testInst // recorded instances in source order }{ {`package p0; func f[T any](T) {}; func _() { f(42) }`, - `f`, - []string{`int`}, - `func(int)`, + []testInst{{`f`, []string{`int`}, `func(int)`}}, }, {`package p1; func f[T any](T) T { panic(0) }; func _() { f('@') }`, - `f`, - []string{`rune`}, - `func(rune) rune`, + []testInst{{`f`, []string{`rune`}, `func(rune) rune`}}, }, {`package p2; func f[T any](...T) T { panic(0) }; func _() { f(0i) }`, - `f`, - []string{`complex128`}, - `func(...complex128) complex128`, + []testInst{{`f`, []string{`complex128`}, `func(...complex128) complex128`}}, }, {`package p3; func f[A, B, C any](A, *B, []C) {}; func _() { f(1.2, new(string), []byte{}) }`, - `f`, - []string{`float64`, `string`, `byte`}, - `func(float64, *string, []byte)`, + []testInst{{`f`, []string{`float64`, `string`, `byte`}, `func(float64, *string, []byte)`}}, }, {`package p4; func f[A, B any](A, *B, ...[]B) {}; func _() { f(1.2, new(byte)) }`, - `f`, - []string{`float64`, `byte`}, - `func(float64, *byte, ...[]byte)`, + []testInst{{`f`, []string{`float64`, `byte`}, `func(float64, *byte, ...[]byte)`}}, }, - // we don't know how to translate these but we can type-check them {`package q0; type T struct{}; func (T) m[P any](P) {}; func _(x T) { x.m(42) }`, - `m`, - []string{`int`}, - `func(int)`, + []testInst{{`m`, []string{`int`}, `func(int)`}}, }, {`package q1; type T struct{}; func (T) m[P any](P) P { panic(0) }; func _(x T) { x.m(42) }`, - `m`, - []string{`int`}, - `func(int) int`, + []testInst{{`m`, []string{`int`}, `func(int) int`}}, }, {`package q2; type T struct{}; func (T) m[P any](...P) P { panic(0) }; func _(x T) { x.m(42) }`, - `m`, - []string{`int`}, - `func(...int) int`, + []testInst{{`m`, []string{`int`}, `func(...int) int`}}, }, {`package q3; type T struct{}; func (T) m[A, B, C any](A, *B, []C) {}; func _(x T) { x.m(1.2, new(string), []byte{}) }`, - `m`, - []string{`float64`, `string`, `byte`}, - `func(float64, *string, []byte)`, + []testInst{{`m`, []string{`float64`, `string`, `byte`}, `func(float64, *string, []byte)`}}, }, {`package q4; type T struct{}; func (T) m[A, B any](A, *B, ...[]B) {}; func _(x T) { x.m(1.2, new(byte)) }`, - `m`, - []string{`float64`, `byte`}, - `func(float64, *byte, ...[]byte)`, + []testInst{{`m`, []string{`float64`, `byte`}, `func(float64, *byte, ...[]byte)`}}, }, - {`package r0; type T[P any] struct{}; func (_ T[P]) m[Q any](Q) {}; func _[P any](x T[P]) { x.m(42) }`, - `m`, - []string{`int`}, - `func(int)`, + {`package r0; type T[P1 any] struct{}; func (_ T[P2]) m[Q any](Q) {}; func _[P3 any](x T[P3]) { x.m(42) }`, + []testInst{ + {`T`, []string{`P2`}, `struct{}`}, + {`T`, []string{`P3`}, `struct{}`}, + {`m`, []string{`int`}, `func(int)`}, + }, }, // TODO(gri) record method type parameters in syntax.FuncType so we can check this // {`package r1; type T interface{ m[P any](P) }; func _(x T) { x.m(4.2) }`, @@ -474,97 +467,113 @@ func TestInstanceInfo(t *testing.T) { // `func(float64)`, // }, - {`package s1; func f[T any, P interface{~*T}](x T) {}; func _(x string) { f(x) }`, - `f`, - []string{`string`, `*string`}, - `func(x string)`, + {`package s1; func f[T any, P interface{*T}](x T) {}; func _(x string) { f(x) }`, + []testInst{{`f`, []string{`string`, `*string`}, `func(x string)`}}, }, - {`package s2; func f[T any, P interface{~*T}](x []T) {}; func _(x []int) { f(x) }`, - `f`, - []string{`int`, `*int`}, - `func(x []int)`, + {`package s2; func f[T any, P interface{*T}](x []T) {}; func _(x []int) { f(x) }`, + []testInst{{`f`, []string{`int`, `*int`}, `func(x []int)`}}, }, - {`package s3; type C[T any] interface{~chan<- T}; func f[T any, P C[T]](x []T) {}; func _(x []int) { f(x) }`, - `f`, - []string{`int`, `chan<- int`}, - `func(x []int)`, + {`package s3; type C[T any] interface{chan<- T}; func f[T any, P C[T]](x []T) {}; func _(x []int) { f(x) }`, + []testInst{ + {`C`, []string{`T`}, `interface{chan<- T}`}, + {`f`, []string{`int`, `chan<- int`}, `func(x []int)`}, + }, }, - {`package s4; type C[T any] interface{~chan<- T}; func f[T any, P C[T], Q C[[]*P]](x []T) {}; func _(x []int) { f(x) }`, - `f`, - []string{`int`, `chan<- int`, `chan<- []*chan<- int`}, - `func(x []int)`, + {`package s4; type C[T any] interface{chan<- T}; func f[T any, P C[T], Q C[[]*P]](x []T) {}; func _(x []int) { f(x) }`, + []testInst{ + {`C`, []string{`T`}, `interface{chan<- T}`}, + {`C`, []string{`[]*P`}, `interface{chan<- []*P}`}, + {`f`, []string{`int`, `chan<- int`, `chan<- []*chan<- int`}, `func(x []int)`}, + }, }, - {`package t1; func f[T any, P interface{~*T}]() T { panic(0) }; func _() { _ = f[string] }`, - `f`, - []string{`string`, `*string`}, - `func() string`, + {`package t1; func f[T any, P interface{*T}]() T { panic(0) }; func _() { _ = f[string] }`, + []testInst{{`f`, []string{`string`, `*string`}, `func() string`}}, }, - {`package t2; func f[T any, P interface{~*T}]() T { panic(0) }; func _() { _ = (f[string]) }`, - `f`, - []string{`string`, `*string`}, - `func() string`, + {`package t2; func f[T any, P interface{*T}]() T { panic(0) }; func _() { _ = (f[string]) }`, + []testInst{{`f`, []string{`string`, `*string`}, `func() string`}}, }, - {`package t3; type C[T any] interface{~chan<- T}; func f[T any, P C[T], Q C[[]*P]]() []T { return nil }; func _() { _ = f[int] }`, - `f`, - []string{`int`, `chan<- int`, `chan<- []*chan<- int`}, - `func() []int`, + {`package t3; type C[T any] interface{chan<- T}; func f[T any, P C[T], Q C[[]*P]]() []T { return nil }; func _() { _ = f[int] }`, + []testInst{ + {`C`, []string{`T`}, `interface{chan<- T}`}, + {`C`, []string{`[]*P`}, `interface{chan<- []*P}`}, + {`f`, []string{`int`, `chan<- int`, `chan<- []*chan<- int`}, `func() []int`}, + }, }, - {`package t4; type C[T any] interface{~chan<- T}; func f[T any, P C[T], Q C[[]*P]]() []T { return nil }; func _() { _ = f[int] }`, - `f`, - []string{`int`, `chan<- int`, `chan<- []*chan<- int`}, - `func() []int`, + {`package t4; type C[T any] interface{chan<- T}; func f[T any, P C[T], Q C[[]*P]]() []T { return nil }; func _() { _ = (f[int]) }`, + []testInst{ + {`C`, []string{`T`}, `interface{chan<- T}`}, + {`C`, []string{`[]*P`}, `interface{chan<- []*P}`}, + {`f`, []string{`int`, `chan<- int`, `chan<- []*chan<- int`}, `func() []int`}, + }, }, - {`package i0; import lib "generic_lib"; func _() { lib.F(42) }`, - `F`, - []string{`int`}, - `func(int)`, + {`package i0; import "lib"; func _() { lib.F(42) }`, + []testInst{{`F`, []string{`int`}, `func(int)`}}, }, + + {`package duplfunc0; func f[T any](T) {}; func _() { f(42); f("foo"); f[int](3) }`, + []testInst{ + {`f`, []string{`int`}, `func(int)`}, + {`f`, []string{`string`}, `func(string)`}, + {`f`, []string{`int`}, `func(int)`}, + }, + }, + {`package duplfunc1; import "lib"; func _() { lib.F(42); lib.F("foo"); lib.F(3) }`, + []testInst{ + {`F`, []string{`int`}, `func(int)`}, + {`F`, []string{`string`}, `func(string)`}, + {`F`, []string{`int`}, `func(int)`}, + }, + }, + {`package type0; type T[P interface{~int}] struct{ x P }; var _ T[int]`, - `T`, - []string{`int`}, - `struct{x int}`, + []testInst{{`T`, []string{`int`}, `struct{x int}`}}, }, {`package type1; type T[P interface{~int}] struct{ x P }; var _ (T[int])`, - `T`, - []string{`int`}, - `struct{x int}`, + []testInst{{`T`, []string{`int`}, `struct{x int}`}}, }, {`package type2; type T[P interface{~int}] struct{ x P }; var _ T[(int)]`, - `T`, - []string{`int`}, - `struct{x int}`, + []testInst{{`T`, []string{`int`}, `struct{x int}`}}, }, {`package type3; type T[P1 interface{~[]P2}, P2 any] struct{ x P1; y P2 }; var _ T[[]int, int]`, - `T`, - []string{`[]int`, `int`}, - `struct{x []int; y int}`, + []testInst{{`T`, []string{`[]int`, `int`}, `struct{x []int; y int}`}}, }, - {`package type4; import lib "generic_lib"; var _ lib.T[int]`, - `T`, - []string{`int`}, - `[]int`, + {`package type4; import "lib"; var _ lib.T[int]`, + []testInst{{`T`, []string{`int`}, `[]int`}}, + }, + + {`package dupltype0; type T[P interface{~int}] struct{ x P }; var x T[int]; var y T[int]`, + []testInst{ + {`T`, []string{`int`}, `struct{x int}`}, + {`T`, []string{`int`}, `struct{x int}`}, + }, + }, + {`package dupltype1; type T[P ~int] struct{ x P }; func (r *T[Q]) add(z T[Q]) { r.x += z.x }`, + []testInst{ + {`T`, []string{`Q`}, `struct{x Q}`}, + {`T`, []string{`Q`}, `struct{x Q}`}, + }, + }, + {`package dupltype1; import "lib"; var x lib.T[int]; var y lib.T[int]; var z lib.T[string]`, + []testInst{ + {`T`, []string{`int`}, `[]int`}, + {`T`, []string{`int`}, `[]int`}, + {`T`, []string{`string`}, `[]string`}, + }, }, } for _, test := range tests { - const lib = `package generic_lib - -func F[P any](P) {} - -type T[P any] []P -` - imports := make(testImporter) conf := Config{Importer: imports} - instances := make(map[*syntax.Name]Instance) - uses := make(map[*syntax.Name]Object) + instMap := make(map[*syntax.Name]Instance) + useMap := make(map[*syntax.Name]Object) makePkg := func(src string) *Package { f, err := parseSrc("p.go", src) if err != nil { t.Fatal(err) } - pkg, err := conf.Check("", []*syntax.File{f}, &Info{Instances: instances, Uses: uses}) + pkg, err := conf.Check("", []*syntax.File{f}, &Info{Instances: instMap, Uses: useMap}) if err != nil { t.Fatal(err) } @@ -574,60 +583,72 @@ type T[P any] []P makePkg(lib) pkg := makePkg(test.src) - // look for instance information - var targs []Type - var typ Type - for ident, inst := range instances { - if syntax.String(ident) == test.name { - for i := 0; i < inst.TypeArgs.Len(); i++ { - targs = append(targs, inst.TypeArgs.At(i)) - } - typ = inst.Type + t.Run(pkg.Name(), func(t *testing.T) { + // Sort instances in source order for stability. + instances := sortedInstances(instMap) + if got, want := len(instances), len(test.instances); got != want { + t.Fatalf("got %d instances, want %d", got, want) + } - // Check that we can find the corresponding parameterized type. - ptype := uses[ident].Type() + // Pairwise compare with the expected instances. + for ii, inst := range instances { + var targs []Type + for i := 0; i < inst.Inst.TypeArgs.Len(); i++ { + targs = append(targs, inst.Inst.TypeArgs.At(i)) + } + typ := inst.Inst.Type + + testInst := test.instances[ii] + if got := inst.Name.Value; got != testInst.name { + t.Fatalf("got name %s, want %s", got, testInst.name) + } + + if len(targs) != len(testInst.targs) { + t.Fatalf("got %d type arguments; want %d", len(targs), len(testInst.targs)) + } + for i, targ := range targs { + if got := targ.String(); got != testInst.targs[i] { + t.Errorf("type argument %d: got %s; want %s", i, got, testInst.targs[i]) + } + } + if got := typ.Underlying().String(); got != testInst.typ { + t.Errorf("package %s: got %s; want %s", pkg.Name(), got, testInst.typ) + } + + // Verify the invariant that re-instantiating the corresponding generic + // type with TypeArgs results in an identical instance. + ptype := useMap[inst.Name].Type() lister, _ := ptype.(interface{ TypeParams() *TypeParamList }) if lister == nil || lister.TypeParams().Len() == 0 { - t.Errorf("package %s: info.Types[%v] = %v, want parameterized type", pkg.Name(), ident, ptype) - continue + t.Fatalf("info.Types[%v] = %v, want parameterized type", inst.Name, ptype) } - - // Verify the invariant that re-instantiating the generic type with - // TypeArgs results in an equivalent type. inst2, err := Instantiate(nil, ptype, targs, true) if err != nil { t.Errorf("Instantiate(%v, %v) failed: %v", ptype, targs, err) } - if !Identical(inst.Type, inst2) { - t.Errorf("%v and %v are not identical", inst.Type, inst2) + if !Identical(inst.Inst.Type, inst2) { + t.Errorf("%v and %v are not identical", inst.Inst.Type, inst2) } - break } - } - if targs == nil { - t.Errorf("package %s: no instance information found for %s", pkg.Name(), test.name) - continue - } - - // check that type arguments are correct - if len(targs) != len(test.targs) { - t.Errorf("package %s: got %d type arguments; want %d", pkg.Name(), len(targs), len(test.targs)) - continue - } - for i, targ := range targs { - if got := targ.String(); got != test.targs[i] { - t.Errorf("package %s, %d. type argument: got %s; want %s", pkg.Name(), i, got, test.targs[i]) - continue - } - } - - // check that the types match - if got := typ.Underlying().String(); got != test.typ { - t.Errorf("package %s: got %s; want %s", pkg.Name(), got, test.typ) - } + }) } } +type recordedInstance struct { + Name *syntax.Name + Inst Instance +} + +func sortedInstances(m map[*syntax.Name]Instance) (instances []recordedInstance) { + for id, inst := range m { + instances = append(instances, recordedInstance{id, inst}) + } + sort.Slice(instances, func(i, j int) bool { + return instances[i].Name.Pos().Cmp(instances[j].Name.Pos()) < 0 + }) + return instances +} + func TestDefsInfo(t *testing.T) { var tests = []struct { src string @@ -1697,7 +1718,7 @@ func F(){ var F = /*F=func:12*/ F /*F=var:17*/ ; _ = F var a []int - for i, x := range /*i=undef*/ /*x=var:16*/ a /*i=var:20*/ /*x=var:20*/ { _ = i; _ = x } + for i, x := range a /*i=undef*/ /*x=var:16*/ { _ = i; _ = x } var i interface{} switch y := i.(type) { /*y=undef*/ @@ -2313,27 +2334,27 @@ type Bad Bad // invalid type conf := Config{Error: func(error) {}} pkg, _ := conf.Check(f.PkgName.Value, []*syntax.File{f}, nil) - scope := pkg.Scope() + lookup := func(tname string) Type { return pkg.Scope().Lookup(tname).Type() } var ( - EmptyIface = scope.Lookup("EmptyIface").Type().Underlying().(*Interface) - I = scope.Lookup("I").Type().(*Named) + EmptyIface = lookup("EmptyIface").Underlying().(*Interface) + I = lookup("I").(*Named) II = I.Underlying().(*Interface) - C = scope.Lookup("C").Type().(*Named) + C = lookup("C").(*Named) CI = C.Underlying().(*Interface) - Integer = scope.Lookup("Integer").Type().Underlying().(*Interface) - EmptyTypeSet = scope.Lookup("EmptyTypeSet").Type().Underlying().(*Interface) - N1 = scope.Lookup("N1").Type() + Integer = lookup("Integer").Underlying().(*Interface) + EmptyTypeSet = lookup("EmptyTypeSet").Underlying().(*Interface) + N1 = lookup("N1") N1p = NewPointer(N1) - N2 = scope.Lookup("N2").Type() + N2 = lookup("N2") N2p = NewPointer(N2) - N3 = scope.Lookup("N3").Type() - N4 = scope.Lookup("N4").Type() - Bad = scope.Lookup("Bad").Type() + N3 = lookup("N3") + N4 = lookup("N4") + Bad = lookup("Bad") ) tests := []struct { - t Type - i *Interface + V Type + T *Interface want bool }{ {I, II, true}, @@ -2364,8 +2385,20 @@ type Bad Bad // invalid type } for _, test := range tests { - if got := Implements(test.t, test.i); got != test.want { - t.Errorf("Implements(%s, %s) = %t, want %t", test.t, test.i, got, test.want) + if got := Implements(test.V, test.T); got != test.want { + t.Errorf("Implements(%s, %s) = %t, want %t", test.V, test.T, got, test.want) + } + + // The type assertion x.(T) is valid if T is an interface or if T implements the type of x. + // The assertion is never valid if T is a bad type. + V := test.T + T := test.V + want := false + if _, ok := T.Underlying().(*Interface); (ok || Implements(T, V)) && T != Bad { + want = true + } + if got := AssertableTo(V, T); got != want { + t.Errorf("AssertableTo(%s, %s) = %t, want %t", V, T, got, want) } } } diff --git a/src/cmd/compile/internal/types2/assignments.go b/src/cmd/compile/internal/types2/assignments.go index 936930f0b1..d88b03748f 100644 --- a/src/cmd/compile/internal/types2/assignments.go +++ b/src/cmd/compile/internal/types2/assignments.go @@ -294,15 +294,14 @@ func (check *Checker) typesSummary(list []Type, variadic bool) string { return "(" + strings.Join(res, ", ") + ")" } -func (check *Checker) assignError(rhs []syntax.Expr, nvars, nvals int) { - measure := func(x int, unit string) string { - s := fmt.Sprintf("%d %s", x, unit) - if x != 1 { - s += "s" - } - return s +func measure(x int, unit string) string { + if x != 1 { + unit += "s" } + return fmt.Sprintf("%d %s", x, unit) +} +func (check *Checker) assignError(rhs []syntax.Expr, nvars, nvals int) { vars := measure(nvars, "variable") vals := measure(nvals, "value") rhs0 := rhs[0] diff --git a/src/cmd/compile/internal/types2/builtins.go b/src/cmd/compile/internal/types2/builtins.go index f9db07fdea..428897c628 100644 --- a/src/cmd/compile/internal/types2/builtins.go +++ b/src/cmd/compile/internal/types2/builtins.go @@ -82,10 +82,24 @@ func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) ( // of S and the respective parameter passing rules apply." S := x.typ var T Type - if s, _ := structuralType(S).(*Slice); s != nil { + if s, _ := coreType(S).(*Slice); s != nil { T = s.elem } else { - check.errorf(x, invalidArg+"%s is not a slice", x) + var cause string + switch { + case x.isNil(): + cause = "have untyped nil" + case isTypeParam(S): + if u := coreType(S); u != nil { + cause = check.sprintf("%s has core type %s", x, u) + } else { + cause = check.sprintf("%s has no core type", x) + } + default: + cause = check.sprintf("have %s", x) + } + // don't use invalidArg prefix here as it would repeat "argument" in the error message + check.errorf(x, "first argument to append must be a slice; %s", cause) return } @@ -101,7 +115,7 @@ func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) ( if x.mode == invalid { return } - if t := structuralString(x.typ); t != nil && isString(t) { + if t := coreString(x.typ); t != nil && isString(t) { if check.Types != nil { sig := makeSig(S, S, x.typ) sig.variadic = true @@ -331,14 +345,14 @@ func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) ( case _Copy: // copy(x, y []T) int - dst, _ := structuralType(x.typ).(*Slice) + dst, _ := coreType(x.typ).(*Slice) var y operand arg(&y, 1) if y.mode == invalid { return } - src0 := structuralString(y.typ) + src0 := coreString(y.typ) if src0 != nil && isString(src0) { src0 = NewSlice(universeByte) } @@ -472,13 +486,13 @@ func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) ( } var min int // minimum number of arguments - switch structuralType(T).(type) { + switch coreType(T).(type) { case *Slice: min = 2 case *Map, *Chan: min = 1 case nil: - check.errorf(arg0, invalidArg+"cannot make %s: no structural type", arg0) + check.errorf(arg0, invalidArg+"cannot make %s: no core type", arg0) return default: check.errorf(arg0, invalidArg+"cannot make %s; type must be slice, map, or channel", arg0) diff --git a/src/cmd/compile/internal/types2/call.go b/src/cmd/compile/internal/types2/call.go index 15a42ca3dc..6cc30a7015 100644 --- a/src/cmd/compile/internal/types2/call.go +++ b/src/cmd/compile/internal/types2/call.go @@ -168,7 +168,7 @@ func (check *Checker) callExpr(x *operand, call *syntax.CallExpr) exprKind { cgocall := x.mode == cgofunc // a type parameter may be "called" if all types have the same signature - sig, _ := structuralType(x.typ).(*Signature) + sig, _ := coreType(x.typ).(*Signature) if sig == nil { check.errorf(x, invalidOp+"cannot call non-function %s", x) x.mode = invalid @@ -423,7 +423,7 @@ var cgoPrefixes = [...]string{ "_Cmacro_", // function to evaluate the expanded expression } -func (check *Checker) selector(x *operand, e *syntax.SelectorExpr) { +func (check *Checker) selector(x *operand, e *syntax.SelectorExpr, def *Named) { // these must be declared before the "goto Error" statements var ( obj Object @@ -525,7 +525,17 @@ func (check *Checker) selector(x *operand, e *syntax.SelectorExpr) { } check.exprOrType(x, e.X, false) - if x.mode == invalid { + switch x.mode { + case typexpr: + // don't crash for "type T T.x" (was issue #51509) + if def != nil && x.typ == def { + check.cycleError([]Object{def.obj}) + goto Error + } + case builtin: + check.errorf(e.Pos(), "cannot select on %s", x) + goto Error + case invalid: goto Error } diff --git a/src/cmd/compile/internal/types2/check.go b/src/cmd/compile/internal/types2/check.go index bfed16993b..4ec6a7b4fd 100644 --- a/src/cmd/compile/internal/types2/check.go +++ b/src/cmd/compile/internal/types2/check.go @@ -18,19 +18,6 @@ var nopos syntax.Pos // debugging/development support const debug = false // leave on during development -// If forceStrict is set, the type-checker enforces additional -// rules not specified by the Go 1 spec, but which will -// catch guaranteed run-time errors if the respective -// code is executed. In other words, programs passing in -// strict mode are Go 1 compliant, but not all Go 1 programs -// will pass in strict mode. The additional rules are: -// -// - A type assertion x.(T) where T is an interface type -// is invalid if any (statically known) method that exists -// for both x and T have different signatures. -// -const forceStrict = false - // exprInfo stores information about an untyped expression. type exprInfo struct { isLhs bool // expression is lhs operand of a shift with delayed type-check @@ -139,7 +126,7 @@ type Checker struct { untyped map[syntax.Expr]exprInfo // map of expressions without final type delayed []action // stack of delayed action segments; segments are processed in FIFO order objPath []Object // path of object dependencies during type inference (for cycle reporting) - defTypes []*Named // defined types created during type checking, for final validation. + cleaners []cleaner // list of types that may need a final cleanup at the end of type-checking // environment within which the current object is type-checked (valid only // for the duration of type-checking a specific object) @@ -218,6 +205,16 @@ func (check *Checker) pop() Object { return obj } +type cleaner interface { + cleanup() +} + +// needsCleanup records objects/types that implement the cleanup method +// which will be called at the end of type-checking. +func (check *Checker) needsCleanup(c cleaner) { + check.cleaners = append(check.cleaners, c) +} + // NewChecker returns a new Checker instance for a given package. // Package files may be added incrementally via checker.Files. func NewChecker(conf *Config, pkg *Package, info *Info) *Checker { @@ -260,6 +257,8 @@ func (check *Checker) initFiles(files []*syntax.File) { check.methods = nil check.untyped = nil check.delayed = nil + check.objPath = nil + check.cleaners = nil // determine package name and collect valid files pkg := check.pkg @@ -328,8 +327,8 @@ func (check *Checker) checkFiles(files []*syntax.File) (err error) { print("== processDelayed ==") check.processDelayed(0) // incl. all functions - print("== expandDefTypes ==") - check.expandDefTypes() + print("== cleanup ==") + check.cleanup() print("== initOrder ==") check.initOrder() @@ -357,7 +356,6 @@ func (check *Checker) checkFiles(files []*syntax.File) (err error) { check.recvTParamMap = nil check.brokenAliases = nil check.unionTypeSets = nil - check.defTypes = nil check.ctxt = nil // TODO(gri) There's more memory we should release at this point. @@ -385,27 +383,13 @@ func (check *Checker) processDelayed(top int) { check.delayed = check.delayed[:top] } -func (check *Checker) expandDefTypes() { - // Ensure that every defined type created in the course of type-checking has - // either non-*Named underlying, or is unresolved. - // - // This guarantees that we don't leak any types whose underlying is *Named, - // because any unresolved instances will lazily compute their underlying by - // substituting in the underlying of their origin. The origin must have - // either been imported or type-checked and expanded here, and in either case - // its underlying will be fully expanded. - for i := 0; i < len(check.defTypes); i++ { - n := check.defTypes[i] - switch n.underlying.(type) { - case nil: - if n.resolver == nil { - panic("nil underlying") - } - case *Named: - n.under() // n.under may add entries to check.defTypes - } - n.check = nil +// cleanup runs cleanup for all collected cleaners. +func (check *Checker) cleanup() { + // Don't use a range clause since Named.cleanup may add more cleaners. + for i := 0; i < len(check.cleaners); i++ { + check.cleaners[i].cleanup() } + check.cleaners = nil } func (check *Checker) record(x *operand) { diff --git a/src/cmd/compile/internal/types2/compilersupport.go b/src/cmd/compile/internal/types2/compilersupport.go index b35e752b8f..33dd8e8baa 100644 --- a/src/cmd/compile/internal/types2/compilersupport.go +++ b/src/cmd/compile/internal/types2/compilersupport.go @@ -19,12 +19,12 @@ func AsSignature(t Type) *Signature { return u } -// If typ is a type parameter, structuralType returns the single underlying +// If typ is a type parameter, CoreType returns the single underlying // type of all types in the corresponding type constraint if it exists, or // nil otherwise. If the type set contains only unrestricted and restricted // channel types (with identical element types), the single underlying type // is the restricted channel type if the restrictions are always the same. -// If typ is not a type parameter, structuralType returns the underlying type. -func StructuralType(t Type) Type { - return structuralType(t) +// If typ is not a type parameter, CoreType returns the underlying type. +func CoreType(t Type) Type { + return coreType(t) } diff --git a/src/cmd/compile/internal/types2/conversions.go b/src/cmd/compile/internal/types2/conversions.go index 7fe1d5056b..08b3cbff29 100644 --- a/src/cmd/compile/internal/types2/conversions.go +++ b/src/cmd/compile/internal/types2/conversions.go @@ -49,11 +49,14 @@ func (check *Checker) conversion(x *operand, T Type) { // have specific types, constant x cannot be // converted. ok = T.(*TypeParam).underIs(func(u Type) bool { - // t is nil if there are no specific type terms + // u is nil if there are no specific type terms if u == nil { cause = check.sprintf("%s does not contain specific types", T) return false } + if isString(x.typ) && isBytesOrRunes(u) { + return true + } if !constConvertibleTo(u, nil) { cause = check.sprintf("cannot convert %s to %s (in %s)", x, u, T) return false diff --git a/src/cmd/compile/internal/types2/decl.go b/src/cmd/compile/internal/types2/decl.go index 0e8f5085ba..579fa55e59 100644 --- a/src/cmd/compile/internal/types2/decl.go +++ b/src/cmd/compile/internal/types2/decl.go @@ -569,7 +569,6 @@ func (check *Checker) collectTypeParams(dst **TypeParamList, list []*syntax.Fiel // Keep track of bounds for later validation. var bound Type - var bounds []Type for i, f := range list { // Optimization: Re-use the previous type bound if it hasn't changed. // This also preserves the grouped output of type parameter lists @@ -584,7 +583,6 @@ func (check *Checker) collectTypeParams(dst **TypeParamList, list []*syntax.Fiel check.error(f.Type, "cannot use a type parameter as constraint") bound = Typ[Invalid] } - bounds = append(bounds, bound) } tparams[i].bound = bound } diff --git a/src/cmd/compile/internal/types2/errors.go b/src/cmd/compile/internal/types2/errors.go index 77ae75a0a2..422f520795 100644 --- a/src/cmd/compile/internal/types2/errors.go +++ b/src/cmd/compile/internal/types2/errors.go @@ -124,6 +124,17 @@ func sprintf(qf Qualifier, debug bool, format string, args ...interface{}) strin } buf.WriteByte(']') arg = buf.String() + case []*TypeParam: + var buf bytes.Buffer + buf.WriteByte('[') + for i, x := range a { + if i > 0 { + buf.WriteString(", ") + } + buf.WriteString(typeString(x, qf, debug)) // use typeString so we get subscripts when debugging + } + buf.WriteByte(']') + arg = buf.String() } args[i] = arg } diff --git a/src/cmd/compile/internal/types2/expr.go b/src/cmd/compile/internal/types2/expr.go index 4fdabe754e..05cf1d0b33 100644 --- a/src/cmd/compile/internal/types2/expr.go +++ b/src/cmd/compile/internal/types2/expr.go @@ -182,9 +182,9 @@ func (check *Checker) unary(x *operand, e *syntax.Operation) { return case syntax.Recv: - u := structuralType(x.typ) + u := coreType(x.typ) if u == nil { - check.errorf(x, invalidOp+"cannot receive from %s: no structural type", x) + check.errorf(x, invalidOp+"cannot receive from %s: no core type", x) x.mode = invalid return } @@ -899,7 +899,7 @@ func (check *Checker) incomparableCause(typ Type) string { } // see if we can extract a more specific error var cause string - comparable(typ, nil, func(format string, args ...interface{}) { + comparable(typ, true, nil, func(format string, args ...interface{}) { cause = check.sprintf(format, args...) }) return cause @@ -1359,7 +1359,11 @@ func (check *Checker) exprInternal(x *operand, e syntax.Expr, hint Type) exprKin case hint != nil: // no composite literal type present - use hint (element type of enclosing type) typ = hint - base, _ = deref(structuralType(typ)) // *T implies &T{} + base, _ = deref(coreType(typ)) // *T implies &T{} + if base == nil { + check.errorf(e, "invalid composite literal element type %s: no core type", typ) + goto Error + } default: // TODO(gri) provide better error messages depending on context @@ -1367,7 +1371,7 @@ func (check *Checker) exprInternal(x *operand, e syntax.Expr, hint Type) exprKin goto Error } - switch utyp := structuralType(base).(type) { + switch utyp := coreType(base).(type) { case *Struct: // Prevent crash if the struct referred to is not yet set up. // See analogous comment for *Array. @@ -1552,7 +1556,7 @@ func (check *Checker) exprInternal(x *operand, e syntax.Expr, hint Type) exprKin return kind case *syntax.SelectorExpr: - check.selector(x, e) + check.selector(x, e, nil) case *syntax.IndexExpr: if check.indexExpr(x, e) { @@ -1638,6 +1642,7 @@ func (check *Checker) exprInternal(x *operand, e syntax.Expr, hint Type) exprKin case invalid: goto Error case typexpr: + check.validVarType(e.X, x.typ) x.typ = &Pointer{base: x.typ} default: var base Type diff --git a/src/cmd/compile/internal/types2/index.go b/src/cmd/compile/internal/types2/index.go index 4995d2d730..61009c121e 100644 --- a/src/cmd/compile/internal/types2/index.go +++ b/src/cmd/compile/internal/types2/index.go @@ -182,7 +182,7 @@ func (check *Checker) indexExpr(x *operand, e *syntax.IndexExpr) (isFuncInst boo } if !valid { - check.errorf(x, invalidOp+"cannot index %s", x) + check.errorf(e.Pos(), invalidOp+"cannot index %s", x) x.mode = invalid return false } @@ -213,9 +213,9 @@ func (check *Checker) sliceExpr(x *operand, e *syntax.SliceExpr) { valid := false length := int64(-1) // valid if >= 0 - switch u := structuralString(x.typ).(type) { + switch u := coreString(x.typ).(type) { case nil: - check.errorf(x, invalidOp+"cannot slice %s: %s has no structural type", x, x.typ) + check.errorf(x, invalidOp+"cannot slice %s: %s has no core type", x, x.typ) x.mode = invalid return diff --git a/src/cmd/compile/internal/types2/infer.go b/src/cmd/compile/internal/types2/infer.go index 51b26eb2aa..e131077371 100644 --- a/src/cmd/compile/internal/types2/infer.go +++ b/src/cmd/compile/internal/types2/infer.go @@ -41,6 +41,13 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type, }() } + if traceInference { + check.dump("-- inferA %s%s ➞ %s", tparams, params, targs) + defer func() { + check.dump("=> inferA %s ➞ %s", tparams, result) + }() + } + // There must be at least one type parameter, and no more type arguments than type parameters. n := len(tparams) assert(n > 0 && len(targs) <= n) @@ -54,6 +61,64 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type, } // len(targs) < n + const enableTparamRenaming = true + if enableTparamRenaming { + // For the purpose of type inference we must differentiate type parameters + // occurring in explicit type or value function arguments from the type + // parameters we are solving for via unification, because they may be the + // same in self-recursive calls. For example: + // + // func f[P *Q, Q any](p P, q Q) { + // f(p) + // } + // + // In this example, the fact that the P used in the instantation f[P] has + // the same pointer identity as the P we are trying to solve for via + // unification is coincidental: there is nothing special about recursive + // calls that should cause them to conflate the identity of type arguments + // with type parameters. To put it another way: any such self-recursive + // call is equivalent to a mutually recursive call, which does not run into + // any problems of type parameter identity. For example, the following code + // is equivalent to the code above. + // + // func f[P interface{*Q}, Q any](p P, q Q) { + // f2(p) + // } + // + // func f2[P interface{*Q}, Q any](p P, q Q) { + // f(p) + // } + // + // We can turn the first example into the second example by renaming type + // parameters in the original signature to give them a new identity. As an + // optimization, we do this only for self-recursive calls. + + // We can detect if we are in a self-recursive call by comparing the + // identity of the first type parameter in the current function with the + // first type parameter in tparams. This works because type parameters are + // unique to their type parameter list. + selfRecursive := check.sig != nil && check.sig.tparams.Len() > 0 && tparams[0] == check.sig.tparams.At(0) + + if selfRecursive { + // In self-recursive inference, rename the type parameters with new type + // parameters that are the same but for their pointer identity. + tparams2 := make([]*TypeParam, len(tparams)) + for i, tparam := range tparams { + tname := NewTypeName(tparam.Obj().Pos(), tparam.Obj().Pkg(), tparam.Obj().Name(), nil) + tparams2[i] = NewTypeParam(tname, nil) + tparams2[i].index = tparam.index // == i + } + + renameMap := makeRenameMap(tparams, tparams2) + for i, tparam := range tparams { + tparams2[i].bound = check.subst(pos, tparam.bound, renameMap, nil) + } + + tparams = tparams2 + params = check.subst(pos, params, renameMap, nil).(*Tuple) + } + } + // If we have more than 2 arguments, we may have arguments with named and unnamed types. // If that is the case, permutate params and args such that the arguments with named // types are first in the list. This doesn't affect type inference if all types are taken @@ -403,6 +468,13 @@ func (w *tpWalker) isParameterizedTypeList(list []Type) bool { func (check *Checker) inferB(pos syntax.Pos, tparams []*TypeParam, targs []Type) (types []Type, index int) { assert(len(tparams) >= len(targs) && len(targs) > 0) + if traceInference { + check.dump("-- inferB %s ➞ %s", tparams, targs) + defer func() { + check.dump("=> inferB %s ➞ %s", tparams, types) + }() + } + // Setup bidirectional unification between constraints // and the corresponding type arguments (which may be nil!). u := newUnifier(false) @@ -416,27 +488,88 @@ func (check *Checker) inferB(pos syntax.Pos, tparams []*TypeParam, targs []Type) } } - // If a constraint has a structural type, unify the corresponding type parameter with it. - for _, tpar := range tparams { - sbound := structuralType(tpar) - if sbound != nil { - // If the structural type is the underlying type of a single - // defined type in the constraint, use that defined type instead. - if named, _ := tpar.singleType().(*Named); named != nil { - sbound = named - } - if !u.unify(tpar, sbound) { - // TODO(gri) improve error message by providing the type arguments - // which we know already - check.errorf(pos, "%s does not match %s", tpar, sbound) - return nil, 0 + // Repeatedly apply constraint type inference as long as + // there are still unknown type arguments and progress is + // being made. + // + // This is an O(n^2) algorithm where n is the number of + // type parameters: if there is progress (and iteration + // continues), at least one type argument is inferred + // per iteration and we have a doubly nested loop. + // In practice this is not a problem because the number + // of type parameters tends to be very small (< 5 or so). + // (It should be possible for unification to efficiently + // signal newly inferred type arguments; then the loops + // here could handle the respective type parameters only, + // but that will come at a cost of extra complexity which + // may not be worth it.) + for n := u.x.unknowns(); n > 0; { + nn := n + + for i, tpar := range tparams { + // If there is a core term (i.e., a core type with tilde information) + // unify the type parameter with the core type. + if core, single := coreTerm(tpar); core != nil { + // A type parameter can be unified with its core type in two cases. + tx := u.x.at(i) + switch { + case tx != nil: + // The corresponding type argument tx is known. + // In this case, if the core type has a tilde, the type argument's underlying + // type must match the core type, otherwise the type argument and the core type + // must match. + // If tx is an external type parameter, don't consider its underlying type + // (which is an interface). Core type unification will attempt to unify against + // core.typ. + // Note also that even with inexact unification we cannot leave away the under + // call here because it's possible that both tx and core.typ are named types, + // with under(tx) being a (named) basic type matching core.typ. Such cases do + // not match with inexact unification. + if core.tilde && !isTypeParam(tx) { + tx = under(tx) + } + if !u.unify(tx, core.typ) { + // TODO(gri) improve error message by providing the type arguments + // which we know already + // Don't use term.String() as it always qualifies types, even if they + // are in the current package. + tilde := "" + if core.tilde { + tilde = "~" + } + check.errorf(pos, "%s does not match %s%s", tpar, tilde, core.typ) + return nil, 0 + } + + case single && !core.tilde: + // The corresponding type argument tx is unknown and there's a single + // specific type and no tilde. + // In this case the type argument must be that single type; set it. + u.x.set(i, core.typ) + + default: + // Unification is not possible and no progress was made. + continue + } + + // The number of known type arguments may have changed. + nn = u.x.unknowns() + if nn == 0 { + break // all type arguments are known + } } } + + assert(nn <= n) + if nn == n { + break // no progress + } + n = nn } // u.x.types() now contains the incoming type arguments plus any additional type - // arguments which were inferred from structural types. The newly inferred non- - // nil entries may still contain references to other type parameters. + // arguments which were inferred from core terms. The newly inferred non-nil + // entries may still contain references to other type parameters. // For instance, for [A any, B interface{ []C }, C interface{ *A }], if A == int // was given, unification produced the type list [int, []C, *A]. We eliminate the // remaining type parameters by substituting the type parameters in this type list @@ -504,8 +637,8 @@ func (check *Checker) inferB(pos syntax.Pos, tparams []*TypeParam, targs []Type) } // Once nothing changes anymore, we may still have type parameters left; - // e.g., a structural constraint *P may match a type parameter Q but we - // don't have any type arguments to fill in for *P or Q (issue #45548). + // e.g., a constraint with core type *P may match a type parameter Q but + // we don't have any type arguments to fill in for *P or Q (issue #45548). // Don't let such inferences escape, instead nil them out. for i, typ := range types { if typ != nil && isParameterized(tparams, typ) { @@ -525,6 +658,42 @@ func (check *Checker) inferB(pos syntax.Pos, tparams []*TypeParam, targs []Type) return } +// If the type parameter has a single specific type S, coreTerm returns (S, true). +// Otherwise, if tpar has a core type T, it returns a term corresponding to that +// core type and false. In that case, if any term of tpar has a tilde, the core +// term has a tilde. In all other cases coreTerm returns (nil, false). +func coreTerm(tpar *TypeParam) (*term, bool) { + n := 0 + var single *term // valid if n == 1 + var tilde bool + tpar.is(func(t *term) bool { + if t == nil { + assert(n == 0) + return false // no terms + } + n++ + single = t + if t.tilde { + tilde = true + } + return true + }) + if n == 1 { + if debug { + assert(debug && under(single.typ) == coreType(tpar)) + } + return single, true + } + if typ := coreType(tpar); typ != nil { + // A core type is always an underlying type. + // If any term of tpar has a tilde, we don't + // have a precise core type and we must return + // a tilde as well. + return &term{tilde, typ}, false + } + return nil, false +} + type cycleFinder struct { tparams []*TypeParam types []Type @@ -570,8 +739,6 @@ func (w *cycleFinder) typ(typ Type) { // in signatures where they are handled explicitly. case *Signature: - // There are no "method types" so we should never see a recv. - assert(t.recv == nil) if t.params != nil { w.varList(t.params.vars) } diff --git a/src/cmd/compile/internal/types2/instantiate.go b/src/cmd/compile/internal/types2/instantiate.go index f54938b6e1..9eced489dc 100644 --- a/src/cmd/compile/internal/types2/instantiate.go +++ b/src/cmd/compile/internal/types2/instantiate.go @@ -15,10 +15,10 @@ import ( // Instantiate instantiates the type orig with the given type arguments targs. // orig must be a *Named or a *Signature type. If there is no error, the -// resulting Type is a new, instantiated (not parameterized) type of the same -// kind (either a *Named or a *Signature). Methods attached to a *Named type -// are also instantiated, and associated with a new *Func that has the same -// position as the original method, but nil function scope. +// resulting Type is an instantiated type of the same kind (either a *Named or +// a *Signature). Methods attached to a *Named type are also instantiated, and +// associated with a new *Func that has the same position as the original +// method, but nil function scope. // // If ctxt is non-nil, it may be used to de-duplicate the instance against // previous instances with the same identity. As a special case, generic @@ -204,7 +204,7 @@ func (check *Checker) implements(V, T Type) error { // If T is comparable, V must be comparable. // Remember as a pending error and report only if we don't have a more specific error. var pending error - if Ti.IsComparable() && ((Vi != nil && !Vi.IsComparable()) || (Vi == nil && !Comparable(V))) { + if Ti.IsComparable() && !comparable(V, false, nil, nil) { pending = errorf("%s does not implement comparable", V) } diff --git a/src/cmd/compile/internal/types2/interface.go b/src/cmd/compile/internal/types2/interface.go index ca5140d092..75597abaf9 100644 --- a/src/cmd/compile/internal/types2/interface.go +++ b/src/cmd/compile/internal/types2/interface.go @@ -37,7 +37,7 @@ func NewInterfaceType(methods []*Func, embeddeds []Type) *Interface { } // set method receivers if necessary - typ := new(Interface) + typ := (*Checker)(nil).newInterface() for _, m := range methods { if sig := m.typ.(*Signature); sig.recv == nil { sig.recv = NewVar(m.pos, m.pkg, "", typ) @@ -54,6 +54,15 @@ func NewInterfaceType(methods []*Func, embeddeds []Type) *Interface { return typ } +// check may be nil +func (check *Checker) newInterface() *Interface { + typ := &Interface{check: check} + if check != nil { + check.needsCleanup(typ) + } + return typ +} + // MarkImplicit marks the interface t as implicit, meaning this interface // corresponds to a constraint literal such as ~T or A|B without explicit // interface embedding. MarkImplicit should be called before any concurrent use @@ -100,6 +109,11 @@ func (t *Interface) String() string { return TypeString(t, nil) } // ---------------------------------------------------------------------------- // Implementation +func (t *Interface) cleanup() { + t.check = nil + t.embedPos = nil +} + func (check *Checker) interfaceType(ityp *Interface, iface *syntax.InterfaceType, def *Named) { addEmbedded := func(pos syntax.Pos, typ Type) { ityp.embeddeds = append(ityp.embeddeds, typ) @@ -162,16 +176,10 @@ func (check *Checker) interfaceType(ityp *Interface, iface *syntax.InterfaceType // (don't sort embeddeds: they must correspond to *embedPos entries) sortMethods(ityp.methods) - // Compute type set with a non-nil *Checker as soon as possible - // to report any errors. Subsequent uses of type sets will use - // this computed type set and won't need to pass in a *Checker. - // - // Pin the checker to the interface type in the interim, in case the type set - // must be used before delayed funcs are processed (see issue #48234). - // TODO(rfindley): clean up use of *Checker with computeInterfaceTypeSet - ityp.check = check + // Compute type set as soon as possible to report any errors. + // Subsequent uses of type sets will use this computed type + // set and won't need to pass in a *Checker. check.later(func() { computeInterfaceTypeSet(check, iface.Pos(), ityp) - ityp.check = nil }).describef(iface, "compute type set for %s", ityp) } diff --git a/src/cmd/compile/internal/types2/lookup.go b/src/cmd/compile/internal/types2/lookup.go index b8ddd94cd7..0832877226 100644 --- a/src/cmd/compile/internal/types2/lookup.go +++ b/src/cmd/compile/internal/types2/lookup.go @@ -66,12 +66,13 @@ func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o obj, index, indirect = lookupFieldOrMethod(T, addressable, pkg, name, false) - // If we didn't find anything and if we have a type parameter with a structural constraint, - // see if there is a matching field (but not a method, those need to be declared explicitly - // in the constraint). If the structural constraint is a named pointer type (see above), we - // are ok here because only fields are accepted as results. - if obj == nil && isTypeParam(T) { - if t := structuralType(T); t != nil { + // If we didn't find anything and if we have a type parameter with a core type, + // see if there is a matching field (but not a method, those need to be declared + // explicitly in the constraint). If the constraint is a named pointer type (see + // above), we are ok here because only fields are accepted as results. + const enableTParamFieldLookup = false // see issue #51576 + if enableTParamFieldLookup && obj == nil && isTypeParam(T) { + if t := coreType(T); t != nil { obj, index, indirect = lookupFieldOrMethod(t, addressable, pkg, name, false) if _, ok := obj.(*Var); !ok { obj, index, indirect = nil, nil, false // accept fields (variables) only @@ -425,18 +426,31 @@ func (check *Checker) funcString(f *Func) string { // method required by V and whether it is missing or just has the wrong type. // The receiver may be nil if assertableTo is invoked through an exported API call // (such as AssertableTo), i.e., when all methods have been type-checked. -// If the global constant forceStrict is set, assertions that are known to fail -// are not permitted. +// TODO(gri) replace calls to this function with calls to newAssertableTo. func (check *Checker) assertableTo(V *Interface, T Type) (method, wrongType *Func) { // no static check is required if T is an interface // spec: "If T is an interface type, x.(T) asserts that the // dynamic type of x implements the interface T." - if IsInterface(T) && !forceStrict { + if IsInterface(T) { return } + // TODO(gri) fix this for generalized interfaces return check.missingMethod(T, V, false) } +// newAssertableTo reports whether a value of type V can be asserted to have type T. +// It also implements behavior for interfaces that currently are only permitted +// in constraint position (we have not yet defined that behavior in the spec). +func (check *Checker) newAssertableTo(V *Interface, T Type) error { + // no static check is required if T is an interface + // spec: "If T is an interface type, x.(T) asserts that the + // dynamic type of x implements the interface T." + if IsInterface(T) { + return nil + } + return check.implements(T, V) +} + // deref dereferences typ if it is a *Pointer and returns its base and true. // Otherwise it returns (typ, false). func deref(typ Type) (Type, bool) { diff --git a/src/cmd/compile/internal/types2/named.go b/src/cmd/compile/internal/types2/named.go index bb522e8fe3..daf8fdc986 100644 --- a/src/cmd/compile/internal/types2/named.go +++ b/src/cmd/compile/internal/types2/named.go @@ -72,16 +72,36 @@ func (check *Checker) newNamed(obj *TypeName, orig *Named, underlying Type, tpar } // Ensure that typ is always expanded and sanity-checked. if check != nil { - check.defTypes = append(check.defTypes, typ) + check.needsCleanup(typ) } return typ } +func (t *Named) cleanup() { + // Ensure that every defined type created in the course of type-checking has + // either non-*Named underlying, or is unresolved. + // + // This guarantees that we don't leak any types whose underlying is *Named, + // because any unresolved instances will lazily compute their underlying by + // substituting in the underlying of their origin. The origin must have + // either been imported or type-checked and expanded here, and in either case + // its underlying will be fully expanded. + switch t.underlying.(type) { + case nil: + if t.resolver == nil { + panic("nil underlying") + } + case *Named: + t.under() // t.under may add entries to check.cleaners + } + t.check = nil +} + // Obj returns the type name for the declaration defining the named type t. For -// instantiated types, this is the type name of the base type. +// instantiated types, this is same as the type name of the origin type. func (t *Named) Obj() *TypeName { return t.orig.obj } // for non-instances this is the same as t.obj -// Origin returns the parameterized type from which the named type t is +// Origin returns the generic type from which the named type t is // instantiated. If t is not an instantiated type, the result is t. func (t *Named) Origin() *Named { return t.orig } @@ -89,7 +109,7 @@ func (t *Named) Origin() *Named { return t.orig } // between parameterized instantiated and non-instantiated types. // TypeParams returns the type parameters of the named type t, or nil. -// The result is non-nil for an (originally) parameterized type even if it is instantiated. +// The result is non-nil for an (originally) generic type even if it is instantiated. func (t *Named) TypeParams() *TypeParamList { return t.resolve(nil).tparams } // SetTypeParams sets the type parameters of the named type t. @@ -102,7 +122,11 @@ func (t *Named) SetTypeParams(tparams []*TypeParam) { // TypeArgs returns the type arguments used to instantiate the named type t. func (t *Named) TypeArgs() *TypeList { return t.targs } -// NumMethods returns the number of explicit methods whose receiver is named type t. +// NumMethods returns the number of explicit methods defined for t. +// +// For an ordinary or instantiated type t, the receiver base type of these +// methods will be the named type t. For an uninstantiated generic type t, each +// method receiver will be instantiated with its receiver type parameters. func (t *Named) NumMethods() int { return t.resolve(nil).methods.Len() } // Method returns the i'th method of named type t for 0 <= i < t.NumMethods(). @@ -360,11 +384,11 @@ func expandNamed(ctxt *Context, n *Named, instPos syntax.Pos) (tparams *TypePara // that it wasn't substituted. In this case we need to create a new // *Interface before modifying receivers. if iface == n.orig.underlying { - iface = &Interface{ - embeddeds: iface.embeddeds, - complete: iface.complete, - implicit: iface.implicit, // should be false but be conservative - } + old := iface + iface = check.newInterface() + iface.embeddeds = old.embeddeds + iface.complete = old.complete + iface.implicit = old.implicit // should be false but be conservative underlying = iface } iface.methods = methods diff --git a/src/cmd/compile/internal/types2/predicates.go b/src/cmd/compile/internal/types2/predicates.go index 279d0775bd..ba259341f6 100644 --- a/src/cmd/compile/internal/types2/predicates.go +++ b/src/cmd/compile/internal/types2/predicates.go @@ -31,7 +31,7 @@ func isBasic(t Type, info BasicInfo) bool { // The allX predicates below report whether t is an X. // If t is a type parameter the result is true if isX is true // for all specified types of the type parameter's type set. -// allX is an optimized version of isX(structuralType(t)) (which +// allX is an optimized version of isX(coreType(t)) (which // is the same as underIs(t, isX)). func allBoolean(t Type) bool { return allBasic(t, IsBoolean) } @@ -45,7 +45,7 @@ func allNumericOrString(t Type) bool { return allBasic(t, IsNumeric|IsString) } // allBasic reports whether under(t) is a basic type with the specified info. // If t is a type parameter, the result is true if isBasic(t, info) is true // for all specific types of the type parameter's type set. -// allBasic(t, info) is an optimized version of isBasic(structuralType(t), info). +// allBasic(t, info) is an optimized version of isBasic(coreType(t), info). func allBasic(t Type, info BasicInfo) bool { if tpar, _ := t.(*TypeParam); tpar != nil { return tpar.is(func(t *term) bool { return t != nil && isBasic(t.typ, info) }) @@ -102,11 +102,12 @@ func isGeneric(t Type) bool { // Comparable reports whether values of type T are comparable. func Comparable(T Type) bool { - return comparable(T, nil, nil) + return comparable(T, true, nil, nil) } +// If dynamic is set, non-type parameter interfaces are always comparable. // If reportf != nil, it may be used to report why T is not comparable. -func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{})) bool { +func comparable(T Type, dynamic bool, seen map[Type]bool, reportf func(string, ...interface{})) bool { if seen[T] { return true } @@ -124,7 +125,7 @@ func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{}) return true case *Struct: for _, f := range t.fields { - if !comparable(f.typ, seen, nil) { + if !comparable(f.typ, dynamic, seen, nil) { if reportf != nil { reportf("struct containing %s cannot be compared", f.typ) } @@ -133,7 +134,7 @@ func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{}) } return true case *Array: - if !comparable(t.elem, seen, nil) { + if !comparable(t.elem, dynamic, seen, nil) { if reportf != nil { reportf("%s cannot be compared", t) } @@ -141,7 +142,7 @@ func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{}) } return true case *Interface: - return !isTypeParam(T) || t.typeSet().IsComparable(seen) + return dynamic && !isTypeParam(T) || t.typeSet().IsComparable(seen) } return false } diff --git a/src/cmd/compile/internal/types2/resolver.go b/src/cmd/compile/internal/types2/resolver.go index 05755f8cfd..61963cb043 100644 --- a/src/cmd/compile/internal/types2/resolver.go +++ b/src/cmd/compile/internal/types2/resolver.go @@ -413,7 +413,7 @@ func (check *Checker) collectObjects() { case *syntax.TypeDecl: if len(s.TParamList) != 0 && !check.allowVersion(pkg, 1, 18) { - check.softErrorf(s.TParamList[0], "type parameters require go1.18 or later") + check.versionErrorf(s.TParamList[0], "go1.18", "type parameter") } obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Value, nil) check.declarePkgObj(s.Name, obj, &declInfo{file: fileScope, tdecl: s}) @@ -458,7 +458,7 @@ func (check *Checker) collectObjects() { check.recordDef(s.Name, obj) } if len(s.TParamList) != 0 && !check.allowVersion(pkg, 1, 18) && !hasTParamError { - check.softErrorf(s.TParamList[0], "type parameters require go1.18 or later") + check.versionErrorf(s.TParamList[0], "go1.18", "type parameter") } info := &declInfo{file: fileScope, fdecl: s} // Methods are not package-level objects but we still track them in the diff --git a/src/cmd/compile/internal/types2/signature.go b/src/cmd/compile/internal/types2/signature.go index c87fab749c..c98024f924 100644 --- a/src/cmd/compile/internal/types2/signature.go +++ b/src/cmd/compile/internal/types2/signature.go @@ -116,11 +116,10 @@ func (check *Checker) funcType(sig *Signature, recvPar *syntax.Field, tparams [] // lookup in the scope. for i, p := range rparams { if p.Value == "_" { - tpar := sig.rparams.At(i) if check.recvTParamMap == nil { check.recvTParamMap = make(map[*syntax.Name]*TypeParam) } - check.recvTParamMap[p] = tpar + check.recvTParamMap[p] = tparams[i] } } // determine receiver type to get its type parameters @@ -136,22 +135,23 @@ func (check *Checker) funcType(sig *Signature, recvPar *syntax.Field, tparams [] } } // provide type parameter bounds - // - only do this if we have the right number (otherwise an error is reported elsewhere) - if sig.RecvTypeParams().Len() == len(recvTParams) { - // We have a list of *TypeNames but we need a list of Types. - list := make([]Type, sig.RecvTypeParams().Len()) - for i, t := range sig.RecvTypeParams().list() { - list[i] = t - check.mono.recordCanon(t, recvTParams[i]) - } - smap := makeSubstMap(recvTParams, list) - for i, tpar := range sig.RecvTypeParams().list() { - bound := recvTParams[i].bound - // bound is (possibly) parameterized in the context of the - // receiver type declaration. Substitute parameters for the - // current context. - tpar.bound = check.subst(tpar.obj.pos, bound, smap, nil) + if len(tparams) == len(recvTParams) { + smap := makeRenameMap(recvTParams, tparams) + for i, tpar := range tparams { + recvTPar := recvTParams[i] + check.mono.recordCanon(tpar, recvTPar) + // recvTPar.bound is (possibly) parameterized in the context of the + // receiver type declaration. Substitute parameters for the current + // context. + tpar.bound = check.subst(tpar.obj.pos, recvTPar.bound, smap, nil) } + } else if len(tparams) < len(recvTParams) { + // Reporting an error here is a stop-gap measure to avoid crashes in the + // compiler when a type parameter/argument cannot be inferred later. It + // may lead to follow-on errors (see issues #51339, #51343). + // TODO(gri) find a better solution + got := measure(len(tparams), "type parameter") + check.errorf(recvPar, "got %s, but receiver base type declares %d", got, len(recvTParams)) } } } @@ -194,66 +194,69 @@ func (check *Checker) funcType(sig *Signature, recvPar *syntax.Field, tparams [] case 1: recv = recvList[0] } + sig.recv = recv - // TODO(gri) We should delay rtyp expansion to when we actually need the - // receiver; thus all checks here should be delayed to later. - rtyp, _ := deref(recv.typ) + // Delay validation of receiver type as it may cause premature expansion + // of types the receiver type is dependent on (see issues #51232, #51233). + check.later(func() { + rtyp, _ := deref(recv.typ) - // spec: "The receiver type must be of the form T or *T where T is a type name." - // (ignore invalid types - error was reported before) - if rtyp != Typ[Invalid] { - var err string - switch T := rtyp.(type) { - case *Named: - T.resolve(check.bestContext(nil)) - // The receiver type may be an instantiated type referred to - // by an alias (which cannot have receiver parameters for now). - if T.TypeArgs() != nil && sig.RecvTypeParams() == nil { - check.errorf(recv.pos, "cannot define methods on instantiated type %s", recv.typ) - break - } - // spec: "The type denoted by T is called the receiver base type; it must not - // be a pointer or interface type and it must be declared in the same package - // as the method." - if T.obj.pkg != check.pkg { - err = "type not defined in this package" + // spec: "The receiver type must be of the form T or *T where T is a type name." + // (ignore invalid types - error was reported before) + if rtyp != Typ[Invalid] { + var err string + switch T := rtyp.(type) { + case *Named: + T.resolve(check.bestContext(nil)) + // The receiver type may be an instantiated type referred to + // by an alias (which cannot have receiver parameters for now). + if T.TypeArgs() != nil && sig.RecvTypeParams() == nil { + check.errorf(recv.pos, "cannot define methods on instantiated type %s", recv.typ) + break + } + // spec: "The type denoted by T is called the receiver base type; it must not + // be a pointer or interface type and it must be declared in the same package + // as the method." + if T.obj.pkg != check.pkg { + err = "type not defined in this package" + if check.conf.CompilerErrorMessages { + check.errorf(recv.pos, "cannot define new methods on non-local type %s", recv.typ) + err = "" + } + } else { + // The underlying type of a receiver base type can be a type parameter; + // e.g. for methods with a generic receiver T[P] with type T[P any] P. + // TODO(gri) Such declarations are currently disallowed. + // Revisit the need for underIs. + underIs(T, func(u Type) bool { + switch u := u.(type) { + case *Basic: + // unsafe.Pointer is treated like a regular pointer + if u.kind == UnsafePointer { + err = "unsafe.Pointer" + return false + } + case *Pointer, *Interface: + err = "pointer or interface type" + return false + } + return true + }) + } + case *Basic: + err = "basic or unnamed type" if check.conf.CompilerErrorMessages { check.errorf(recv.pos, "cannot define new methods on non-local type %s", recv.typ) err = "" } - } else { - // The underlying type of a receiver base type can be a type parameter; - // e.g. for methods with a generic receiver T[P] with type T[P any] P. - underIs(T, func(u Type) bool { - switch u := u.(type) { - case *Basic: - // unsafe.Pointer is treated like a regular pointer - if u.kind == UnsafePointer { - err = "unsafe.Pointer" - return false - } - case *Pointer, *Interface: - err = "pointer or interface type" - return false - } - return true - }) + default: + check.errorf(recv.pos, "invalid receiver type %s", recv.typ) } - case *Basic: - err = "basic or unnamed type" - if check.conf.CompilerErrorMessages { - check.errorf(recv.pos, "cannot define new methods on non-local type %s", recv.typ) - err = "" + if err != "" { + check.errorf(recv.pos, "invalid receiver type %s (%s)", recv.typ, err) } - default: - check.errorf(recv.pos, "invalid receiver type %s", recv.typ) } - if err != "" { - check.errorf(recv.pos, "invalid receiver type %s (%s)", recv.typ, err) - // ok to continue - } - } - sig.recv = recv + }).describef(recv, "validate receiver %s", recv) } sig.params = NewTuple(params...) diff --git a/src/cmd/compile/internal/types2/stmt.go b/src/cmd/compile/internal/types2/stmt.go index 03da98af34..4c8eac725f 100644 --- a/src/cmd/compile/internal/types2/stmt.go +++ b/src/cmd/compile/internal/types2/stmt.go @@ -409,9 +409,9 @@ func (check *Checker) stmt(ctxt stmtContext, s syntax.Stmt) { if ch.mode == invalid || val.mode == invalid { return } - u := structuralType(ch.typ) + u := coreType(ch.typ) if u == nil { - check.errorf(s, invalidOp+"cannot send to %s: no structural type", &ch) + check.errorf(s, invalidOp+"cannot send to %s: no core type", &ch) return } uch, _ := u.(*Chan) @@ -626,14 +626,15 @@ func (check *Checker) stmt(ctxt stmtContext, s syntax.Stmt) { case *syntax.ForStmt: inner |= breakOk | continueOk - check.openScope(s, "for") - defer check.closeScope() if rclause, _ := s.Init.(*syntax.RangeClause); rclause != nil { check.rangeStmt(inner, s, rclause) break } + check.openScope(s, "for") + defer check.closeScope() + check.simpleStmt(s.Init) if s.Cond != nil { var x operand @@ -809,8 +810,6 @@ func (check *Checker) typeSwitchStmt(inner stmtContext, s *syntax.SwitchStmt, gu } func (check *Checker) rangeStmt(inner stmtContext, s *syntax.ForStmt, rclause *syntax.RangeClause) { - // scope already opened - // determine lhs, if any sKey := rclause.Lhs // possibly nil var sValue, sExtra syntax.Expr @@ -835,9 +834,9 @@ func (check *Checker) rangeStmt(inner stmtContext, s *syntax.ForStmt, rclause *s // determine key/value types var key, val Type if x.mode != invalid { - // Ranging over a type parameter is permitted if it has a structural type. + // Ranging over a type parameter is permitted if it has a core type. var cause string - u := structuralType(x.typ) + u := coreType(x.typ) if t, _ := u.(*Chan); t != nil { if sValue != nil { check.softErrorf(sValue, "range over %s permits only one iteration variable", &x) @@ -852,7 +851,7 @@ func (check *Checker) rangeStmt(inner stmtContext, s *syntax.ForStmt, rclause *s // ok to continue } if u == nil { - cause = check.sprintf("%s has no structural type", x.typ) + cause = check.sprintf("%s has no core type", x.typ) } } key, val = rangeKeyVal(u) @@ -866,6 +865,11 @@ func (check *Checker) rangeStmt(inner stmtContext, s *syntax.ForStmt, rclause *s } } + // Open the for-statement block scope now, after the range clause. + // Iteration variables declared with := need to go in this scope (was issue #51437). + check.openScope(s, "range") + defer check.closeScope() + // check assignment to/declaration of iteration variables // (irregular assignment, cannot easily map to existing assignment checks) @@ -874,9 +878,7 @@ func (check *Checker) rangeStmt(inner stmtContext, s *syntax.ForStmt, rclause *s rhs := [2]Type{key, val} // key, val may be nil if rclause.Def { - // short variable declaration; variable scope starts after the range clause - // (the for loop opens a new scope, so variables on the lhs never redeclare - // previously declared variables) + // short variable declaration var vars []*Var for i, lhs := range lhs { if lhs == nil { @@ -913,12 +915,8 @@ func (check *Checker) rangeStmt(inner stmtContext, s *syntax.ForStmt, rclause *s // declare variables if len(vars) > 0 { - scopePos := syntax.EndPos(rclause.X) // TODO(gri) should this just be s.Body.Pos (spec clarification)? + scopePos := s.Body.Pos() for _, obj := range vars { - // spec: "The scope of a constant or variable identifier declared inside - // a function begins at the end of the ConstSpec or VarSpec (ShortVarDecl - // for short variable declarations) and ends at the end of the innermost - // containing block." check.declare(check.scope, nil /* recordDef already called */, obj, scopePos) } } else { diff --git a/src/cmd/compile/internal/types2/subst.go b/src/cmd/compile/internal/types2/subst.go index f2e8fecc05..037f04797b 100644 --- a/src/cmd/compile/internal/types2/subst.go +++ b/src/cmd/compile/internal/types2/subst.go @@ -21,6 +21,17 @@ func makeSubstMap(tpars []*TypeParam, targs []Type) substMap { return proj } +// makeRenameMap is like makeSubstMap, but creates a map used to rename type +// parameters in from with the type parameters in to. +func makeRenameMap(from, to []*TypeParam) substMap { + assert(len(from) == len(to)) + proj := make(substMap, len(from)) + for i, tpar := range from { + proj[tpar] = to[i] + } + return proj +} + func (m substMap) empty() bool { return len(m) == 0 } @@ -149,7 +160,10 @@ func (subst *subster) typ(typ Type) Type { methods, mcopied := subst.funcList(t.methods) embeddeds, ecopied := subst.typeList(t.embeddeds) if mcopied || ecopied { - iface := &Interface{embeddeds: embeddeds, implicit: t.implicit, complete: t.complete} + iface := subst.check.newInterface() + iface.embeddeds = embeddeds + iface.implicit = t.implicit + iface.complete = t.complete // If we've changed the interface type, we may need to replace its // receiver if the receiver type is the original interface. Receivers of // *Named type are replaced during named type expansion. diff --git a/src/cmd/compile/internal/types2/termlist.go b/src/cmd/compile/internal/types2/termlist.go index 844e39e3bf..a0108c4638 100644 --- a/src/cmd/compile/internal/types2/termlist.go +++ b/src/cmd/compile/internal/types2/termlist.go @@ -92,15 +92,6 @@ func (xl termlist) norm() termlist { return rl } -// If the type set represented by xl is specified by a single (non-𝓤) term, -// singleType returns that type. Otherwise it returns nil. -func (xl termlist) singleType() Type { - if nl := xl.norm(); len(nl) == 1 { - return nl[0].typ // if nl.isAll() then typ is nil, which is ok - } - return nil -} - // union returns the union xl ∪ yl. func (xl termlist) union(yl termlist) termlist { return append(xl, yl...).norm() diff --git a/src/cmd/compile/internal/types2/termlist_test.go b/src/cmd/compile/internal/types2/termlist_test.go index 1bdf9e1386..d1e3bdf88e 100644 --- a/src/cmd/compile/internal/types2/termlist_test.go +++ b/src/cmd/compile/internal/types2/termlist_test.go @@ -106,35 +106,6 @@ func TestTermlistNorm(t *testing.T) { } } -func TestTermlistSingleType(t *testing.T) { - // helper to deal with nil types - tstring := func(typ Type) string { - if typ == nil { - return "nil" - } - return typ.String() - } - - for test, want := range map[string]string{ - "∅": "nil", - "𝓤": "nil", - "int": "int", - "myInt": "myInt", - "~int": "int", - "~int ∪ string": "nil", - "~int ∪ myInt": "int", - "∅ ∪ int": "int", - "∅ ∪ ~int": "int", - "∅ ∪ ~int ∪ string": "nil", - } { - xl := maketl(test) - got := tstring(xl.singleType()) - if got != want { - t.Errorf("(%v).singleType() == %v; want %v", test, got, want) - } - } -} - func TestTermlistUnion(t *testing.T) { for _, test := range []struct { xl, yl, want string diff --git a/src/cmd/compile/internal/types2/testdata/check/builtins.go2 b/src/cmd/compile/internal/types2/testdata/check/builtins.go2 index 48a39891bf..7c3f0c96ad 100644 --- a/src/cmd/compile/internal/types2/testdata/check/builtins.go2 +++ b/src/cmd/compile/internal/types2/testdata/check/builtins.go2 @@ -148,7 +148,7 @@ func _[ _ = make /* ERROR expects 2 or 3 arguments */ (S1) _ = make(S1, 10, 20) _ = make /* ERROR expects 2 or 3 arguments */ (S1, 10, 20, 30) - _ = make(S2 /* ERROR cannot make S2: no structural type */ , 10) + _ = make(S2 /* ERROR cannot make S2: no core type */ , 10) type M0 map[string]int _ = make(map[string]int) @@ -156,7 +156,7 @@ func _[ _ = make(M1) _ = make(M1, 10) _ = make/* ERROR expects 1 or 2 arguments */(M1, 10, 20) - _ = make(M2 /* ERROR cannot make M2: no structural type */ ) + _ = make(M2 /* ERROR cannot make M2: no core type */ ) type C0 chan int _ = make(chan int) @@ -164,7 +164,7 @@ func _[ _ = make(C1) _ = make(C1, 10) _ = make/* ERROR expects 1 or 2 arguments */(C1, 10, 20) - _ = make(C2 /* ERROR cannot make C2: no structural type */ ) + _ = make(C2 /* ERROR cannot make C2: no core type */ ) _ = make(C3) } diff --git a/src/cmd/compile/internal/types2/testdata/check/builtins.src b/src/cmd/compile/internal/types2/testdata/check/builtins.src index de27f5c632..358e9c5c0d 100644 --- a/src/cmd/compile/internal/types2/testdata/check/builtins.src +++ b/src/cmd/compile/internal/types2/testdata/check/builtins.src @@ -15,9 +15,9 @@ func append1() { var x int var s []byte _ = append() // ERROR not enough arguments - _ = append("foo" /* ERROR not a slice */ ) - _ = append(nil /* ERROR not a slice */ , s) - _ = append(x /* ERROR not a slice */ , s) + _ = append("foo" /* ERROR must be a slice */ ) + _ = append(nil /* ERROR must be a slice */ , s) + _ = append(x /* ERROR must be a slice */ , s) _ = append(s) _ = append(s, nil...) append /* ERROR not used */ (s) @@ -77,7 +77,7 @@ func append3() { _ = append(f2()) _ = append(f3()) _ = append(f5()) - _ = append(ff /* ERROR not a slice */ ()) // TODO(gri) better error message + _ = append(ff /* ERROR must be a slice */ ()) // TODO(gri) better error message } func cap1() { diff --git a/src/cmd/compile/internal/types2/testdata/check/funcinference.go2 b/src/cmd/compile/internal/types2/testdata/check/funcinference.go2 index 7160e18b19..45d0781cd7 100644 --- a/src/cmd/compile/internal/types2/testdata/check/funcinference.go2 +++ b/src/cmd/compile/internal/types2/testdata/check/funcinference.go2 @@ -8,21 +8,21 @@ import "strconv" type any interface{} -func f0[A any, B interface{~*C}, C interface{~*D}, D interface{~*A}](A, B, C, D) {} +func f0[A any, B interface{*C}, C interface{*D}, D interface{*A}](A, B, C, D) {} func _() { f := f0[string] f("a", nil, nil, nil) f0("a", nil, nil, nil) } -func f1[A any, B interface{~*A}](A, B) {} +func f1[A any, B interface{*A}](A, B) {} func _() { f := f1[int] f(int(0), new(int)) f1(int(0), new(int)) } -func f2[A any, B interface{~[]A}](A, B) {} +func f2[A any, B interface{[]A}](A, B) {} func _() { f := f2[byte] f(byte(0), []byte{}) @@ -38,7 +38,7 @@ func _() { // f3(x, &x, &x) // } -func f4[A any, B interface{~[]C}, C interface{~*A}](A, B, C) {} +func f4[A any, B interface{[]C}, C interface{*A}](A, B, C) {} func _() { f := f4[int] var x int @@ -46,7 +46,7 @@ func _() { f4(x, []*int{}, &x) } -func f5[A interface{~struct{b B; c C}}, B any, C interface{~*B}](x B) A { panic(0) } +func f5[A interface{struct{b B; c C}}, B any, C interface{*B}](x B) A { panic(0) } func _() { x := f5(1.2) var _ float64 = x.b @@ -79,14 +79,14 @@ var _ = Double(MySlice{1}) type Setter[B any] interface { Set(string) - ~*B + *B } func FromStrings[T interface{}, PT Setter[T]](s []string) []T { result := make([]T, len(s)) for i, v := range s { // The type of &result[i] is *T which is in the type list - // of Setter2, so we can convert it to PT. + // of Setter, so we can convert it to PT. p := PT(&result[i]) // PT has a Set method. p.Set(v) diff --git a/src/cmd/compile/internal/types2/testdata/check/typeinference.go2 b/src/cmd/compile/internal/types2/testdata/check/typeinference.go2 index 8876ccaa4e..28f3e286b7 100644 --- a/src/cmd/compile/internal/types2/testdata/check/typeinference.go2 +++ b/src/cmd/compile/internal/types2/testdata/check/typeinference.go2 @@ -4,44 +4,46 @@ package typeInference +// As of issue #51527, type-type inference has been disabled. + // basic inference type Tb[P ~*Q, Q any] int func _() { - var x Tb[*int] + var x Tb /* ERROR got 1 arguments */ [*int] var y Tb[*int, int] - x = y + x = y /* ERROR cannot use y .* in assignment */ _ = x } // recursive inference -type Tr[A any, B ~*C, C ~*D, D ~*A] int +type Tr[A any, B *C, C *D, D *A] int func _() { - var x Tr[string] + var x Tr /* ERROR got 1 arguments */ [string] var y Tr[string, ***string, **string, *string] var z Tr[int, ***int, **int, *int] - x = y + x = y /* ERROR cannot use y .* in assignment */ x = z // ERROR cannot use z .* as Tr _ = x } // other patterns of inference -type To0[A any, B ~[]A] int -type To1[A any, B ~struct{a A}] int -type To2[A any, B ~[][]A] int -type To3[A any, B ~[3]*A] int -type To4[A any, B any, C ~struct{a A; b B}] int +type To0[A any, B []A] int +type To1[A any, B struct{a A}] int +type To2[A any, B [][]A] int +type To3[A any, B [3]*A] int +type To4[A any, B any, C struct{a A; b B}] int func _() { - var _ To0[int] - var _ To1[int] - var _ To2[int] - var _ To3[int] - var _ To4[int, string] + var _ To0 /* ERROR got 1 arguments */ [int] + var _ To1 /* ERROR got 1 arguments */ [int] + var _ To2 /* ERROR got 1 arguments */ [int] + var _ To3 /* ERROR got 1 arguments */ [int] + var _ To4 /* ERROR got 2 arguments */ [int, string] } // failed inference type Tf0[A, B any] int type Tf1[A any, B ~struct{a A; c C}, C any] int func _() { - var _ Tf0 /* ERROR cannot infer B */ /* ERROR got 1 arguments but 2 type parameters */ [int] - var _ Tf1 /* ERROR cannot infer B */ /* ERROR got 1 arguments but 3 type parameters */ [int] + var _ Tf0 /* ERROR got 1 arguments but 2 type parameters */ [int] + var _ Tf1 /* ERROR got 1 arguments but 3 type parameters */ [int] } diff --git a/src/cmd/compile/internal/types2/testdata/check/typeparams.go2 b/src/cmd/compile/internal/types2/testdata/check/typeparams.go2 index ef58241519..68b1f0f5c5 100644 --- a/src/cmd/compile/internal/types2/testdata/check/typeparams.go2 +++ b/src/cmd/compile/internal/types2/testdata/check/typeparams.go2 @@ -134,11 +134,11 @@ func _[T interface{ ~string }] (x T, i, j, k int) { var _ T = x[i:j:k /* ERROR 3 type myByte1 []byte type myByte2 []byte func _[T interface{ []byte | myByte1 | myByte2 }] (x T, i, j, k int) { var _ T = x[i:j:k] } -func _[T interface{ []byte | myByte1 | []int }] (x T, i, j, k int) { var _ T = x[ /* ERROR no structural type */ i:j:k] } +func _[T interface{ []byte | myByte1 | []int }] (x T, i, j, k int) { var _ T = x[ /* ERROR no core type */ i:j:k] } func _[T interface{ []byte | myByte1 | myByte2 | string }] (x T, i, j, k int) { var _ T = x[i:j] } func _[T interface{ []byte | myByte1 | myByte2 | string }] (x T, i, j, k int) { var _ T = x[i:j:k /* ERROR 3-index slice of string */ ] } -func _[T interface{ []byte | myByte1 | []int | string }] (x T, i, j, k int) { var _ T = x[ /* ERROR no structural type */ i:j] } +func _[T interface{ []byte | myByte1 | []int | string }] (x T, i, j, k int) { var _ T = x[ /* ERROR no core type */ i:j] } // len/cap built-ins @@ -230,7 +230,7 @@ func _[ for _, _ = range s1 {} var s2 S2 - for range s2 /* ERROR cannot range over s2.*no structural type */ {} + for range s2 /* ERROR cannot range over s2.*no core type */ {} var a0 []int for range a0 {} @@ -243,7 +243,7 @@ func _[ for _, _ = range a1 {} var a2 A2 - for range a2 /* ERROR cannot range over a2.*no structural type */ {} + for range a2 /* ERROR cannot range over a2.*no core type */ {} var p0 *[10]int for range p0 {} @@ -256,7 +256,7 @@ func _[ for _, _ = range p1 {} var p2 P2 - for range p2 /* ERROR cannot range over p2.*no structural type */ {} + for range p2 /* ERROR cannot range over p2.*no core type */ {} var m0 map[string]int for range m0 {} @@ -269,7 +269,7 @@ func _[ for _, _ = range m1 {} var m2 M2 - for range m2 /* ERROR cannot range over m2.*no structural type */ {} + for range m2 /* ERROR cannot range over m2.*no core type */ {} } // type inference checks diff --git a/src/cmd/compile/internal/types2/testdata/examples/inference.go2 b/src/cmd/compile/internal/types2/testdata/examples/inference.go2 index 0732f06a39..e3d6bfb212 100644 --- a/src/cmd/compile/internal/types2/testdata/examples/inference.go2 +++ b/src/cmd/compile/internal/types2/testdata/examples/inference.go2 @@ -78,7 +78,7 @@ func _() { related1(si, "foo" /* ERROR cannot use "foo" */ ) } -func related2[Elem any, Slice interface{~[]Elem}](e Elem, s Slice) {} +func related2[Elem any, Slice interface{[]Elem}](e Elem, s Slice) {} func _() { // related2 can be called with explicit instantiation. @@ -109,16 +109,8 @@ func _() { related3[int, []int]() related3[byte, List[byte]]() - // Alternatively, the 2nd type argument can be inferred - // from the first one through constraint type inference. - related3[int]() - - // The inferred type is the structural type of the Slice - // type parameter. - var _ []int = related3[int]() - - // It is not the defined parameterized type List. - type anotherList []float32 - var _ anotherList = related3[float32]() // valid - var _ anotherList = related3 /* ERROR cannot use .* \(value of type List\[float32\]\) as anotherList */ [float32, List[float32]]() + // The 2nd type argument cannot be inferred from the first + // one because there's two possible choices: []Elem and + // List[Elem]. + related3[int]( /* ERROR cannot infer Slice */ ) } diff --git a/src/cmd/compile/internal/types2/testdata/examples/methods.go2 b/src/cmd/compile/internal/types2/testdata/examples/methods.go2 index 1d76d553dc..a46f789d60 100644 --- a/src/cmd/compile/internal/types2/testdata/examples/methods.go2 +++ b/src/cmd/compile/internal/types2/testdata/examples/methods.go2 @@ -35,7 +35,7 @@ func (t T1[[ /* ERROR must be an identifier */ ]int]) m2() {} // style. In m3 below, int is the name of the local receiver type parameter // and it shadows the predeclared identifier int which then cannot be used // anymore as expected. -// This is no different from locally redelaring a predeclared identifier +// This is no different from locally re-declaring a predeclared identifier // and usually should be avoided. There are some notable exceptions; e.g., // sometimes it makes sense to use the identifier "copy" which happens to // also be the name of a predeclared built-in function. diff --git a/src/cmd/compile/internal/types2/testdata/examples/types.go2 b/src/cmd/compile/internal/types2/testdata/examples/types.go2 index 077fcfdbb7..ae9c0151d1 100644 --- a/src/cmd/compile/internal/types2/testdata/examples/types.go2 +++ b/src/cmd/compile/internal/types2/testdata/examples/types.go2 @@ -292,7 +292,7 @@ func _[T interface{~int|~float64}]() { // It is possible to create composite literals of type parameter // type as long as it's possible to create a composite literal -// of the structural type of the type parameter's constraint. +// of the core type of the type parameter's constraint. func _[P interface{ ~[]int }]() P { return P{} return P{1, 2, 3} @@ -307,7 +307,7 @@ func _[P interface{ ~[]E }, E interface{ map[string]P } ]() P { } // This is a degenerate case with a singleton type set, but we can create -// composite literals even if the structural type is a defined type. +// composite literals even if the core type is a defined type. type MyInts []int func _[P MyInts]() P { diff --git a/src/cmd/compile/internal/types2/testdata/examples/typesets.go2 b/src/cmd/compile/internal/types2/testdata/examples/typesets.go2 index e19dcf8da3..55ef02284b 100644 --- a/src/cmd/compile/internal/types2/testdata/examples/typesets.go2 +++ b/src/cmd/compile/internal/types2/testdata/examples/typesets.go2 @@ -35,7 +35,7 @@ func _() int { return deref(p) } -func addrOfCopy[V any, P ~*V](v V) P { +func addrOfCopy[V any, P *V](v V) P { return &v } diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue41124.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue41124.go2 index 7f55ba85a6..4550dd732c 100644 --- a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue41124.go2 +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue41124.go2 @@ -47,7 +47,7 @@ type _ struct{ } type _ struct{ - I3 // ERROR interface is .* comparable + I3 // ERROR interface contains type constraints } // General composite types. @@ -59,19 +59,19 @@ type ( _ []I1 // ERROR interface is .* comparable _ []I2 // ERROR interface contains type constraints - _ *I3 // ERROR interface is .* comparable + _ *I3 // ERROR interface contains type constraints _ map[I1 /* ERROR interface is .* comparable */ ]I2 // ERROR interface contains type constraints - _ chan I3 // ERROR interface is .* comparable + _ chan I3 // ERROR interface contains type constraints _ func(I1 /* ERROR interface is .* comparable */ ) _ func() I2 // ERROR interface contains type constraints ) // Other cases. -var _ = [...]I3 /* ERROR interface is .* comparable */ {} +var _ = [...]I3 /* ERROR interface contains type constraints */ {} func _(x interface{}) { - _ = x.(I3 /* ERROR interface is .* comparable */ ) + _ = x.(I3 /* ERROR interface contains type constraints */ ) } type T1[_ any] struct{} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue43527.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue43527.go2 index e4bcee51fe..2955c261f9 100644 --- a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue43527.go2 +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue43527.go2 @@ -9,7 +9,7 @@ const L = 10 type ( _ [L]struct{} _ [A /* ERROR undeclared name A for array length */ ]struct{} - _ [B /* ERROR not an expression */ ]struct{} + _ [B /* ERROR invalid array length B */ ]struct{} _[A any] struct{} B int diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue43671.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue43671.go2 index 46ac51ebdd..3c78f85aa4 100644 --- a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue43671.go2 +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue43671.go2 @@ -12,7 +12,7 @@ type C4 interface{ chan int | chan<- int } type C5[T any] interface{ ~chan T | <-chan T } func _[T any](ch T) { - <-ch // ERROR cannot receive from ch .* no structural type + <-ch // ERROR cannot receive from ch .* no core type } func _[T C0](ch T) { @@ -28,7 +28,7 @@ func _[T C2](ch T) { } func _[T C3](ch T) { - <-ch // ERROR cannot receive from ch .* no structural type + <-ch // ERROR cannot receive from ch .* no core type } func _[T C4](ch T) { diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue45548.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue45548.go2 index b8ba0ad4a7..01c9672745 100644 --- a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue45548.go2 +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue45548.go2 @@ -4,7 +4,7 @@ package p -func f[F interface{~*Q}, G interface{~*R}, Q, R any](q Q, r R) {} +func f[F interface{*Q}, G interface{*R}, Q, R any](q Q, r R) {} func _() { f[*float64, *int](1, 2) diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue47115.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue47115.go2 index 83a8f3a5da..5c1fa80b29 100644 --- a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue47115.go2 +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue47115.go2 @@ -12,7 +12,7 @@ type C4 interface{ chan int | chan<- int } type C5[T any] interface{ ~chan T | chan<- T } func _[T any](ch T) { - ch /* ERROR cannot send to ch .* no structural type */ <- 0 + ch /* ERROR cannot send to ch .* no core type */ <- 0 } func _[T C0](ch T) { @@ -28,7 +28,7 @@ func _[T C2](ch T) { } func _[T C3](ch T) { - ch /* ERROR cannot send to ch .* no structural type */ <- 0 + ch /* ERROR cannot send to ch .* no core type */ <- 0 } func _[T C4](ch T) { diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue47818.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue47818.go2 index 546de1ce31..6069f1f97b 100644 --- a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue47818.go2 +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue47818.go2 @@ -8,13 +8,13 @@ package go1_17 -type T[P /* ERROR type parameters require go1\.18 or later */ any /* ERROR undeclared name: any \(requires version go1\.18 or later\) */ ] struct{} +type T[P /* ERROR type parameter requires go1\.18 or later */ any /* ERROR undeclared name: any \(requires version go1\.18 or later\) */ ] struct{} // for init (and main, but we're not in package main) we should only get one error func init[P /* ERROR func init must have no type parameters */ any /* ERROR undeclared name: any \(requires version go1\.18 or later\) */ ]() {} -func main[P /* ERROR type parameters require go1\.18 or later */ any /* ERROR undeclared name: any \(requires version go1\.18 or later\) */ ]() {} +func main[P /* ERROR type parameter requires go1\.18 or later */ any /* ERROR undeclared name: any \(requires version go1\.18 or later\) */ ]() {} -func f[P /* ERROR type parameters require go1\.18 or later */ any /* ERROR undeclared name: any \(requires version go1\.18 or later\) */ ](x P) { +func f[P /* ERROR type parameter requires go1\.18 or later */ any /* ERROR undeclared name: any \(requires version go1\.18 or later\) */ ](x P) { var _ T[ /* ERROR type instantiation requires go1\.18 or later */ int] var _ (T[ /* ERROR type instantiation requires go1\.18 or later */ int]) _ = T[ /* ERROR type instantiation requires go1\.18 or later */ int]{} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue48619.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue48619.go2 index 3d4f1b4707..72eea1ef59 100644 --- a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue48619.go2 +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue48619.go2 @@ -2,24 +2,19 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// This issue is still open: -// - the error messages could be better or are incorrect -// - unification fails due to stack overflow that is caught - package p func f[P any](a, _ P) { var x int // TODO(gri) these error messages, while correct, could be better - f(a, x /* ERROR type int of x does not match P */) + f(a, x /* ERROR type int of x does not match inferred type P for P */) f(x, a /* ERROR type P of a does not match inferred type int for P */) } func g[P any](a, b P) { g(a, b) - // TODO(gri) these error messages are incorrect because the code is valid - g(&a, & /* ERROR type \*P of &b does not match inferred type \*P for P */ b) - g([]P{}, [ /* ERROR type \[\]P of \[\]P{} does not match inferred type \[\]P for P */ ]P{}) + g(&a, &b) + g([]P{}, []P{}) // work-around: provide type argument explicitly g[*P](&a, &b) diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue48656.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue48656.go2 index bea3dc14a0..0f60f47120 100644 --- a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue48656.go2 +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue48656.go2 @@ -2,14 +2,12 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// This issue is still open: -// - the error messages are unclear -// - unification fails due to stack overflow that is caught - package p func f[P *Q, Q any](P, Q) { - // TODO(gri) these error messages are unclear - _ = f[ /* ERROR P does not match \*Q */ P] - _ = f[ /* ERROR cannot infer P */ *P] + _ = f[P] +} + +func f2[P /* ERROR instantiation cycle */ *Q, Q any](P, Q) { + _ = f2[*P] } diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue49482.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue49482.go2 new file mode 100644 index 0000000000..f289d2e52d --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue49482.go2 @@ -0,0 +1,25 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file is tested when running "go test -run Manual" +// without source arguments. Use for one-off debugging. + +package p + +// The following is OK, per the special handling for type literals discussed in issue #49482. +type _[P *struct{}] struct{} +type _[P *int,] int +type _[P (*int),] int + +const P = 2 // declare P to avoid noisy 'undeclared name' errors below. + +// The following parse as invalid array types. +type _[P *int /* ERROR "int \(type\) is not an expression" */ ] int +type _[P /* ERROR non-function P */ (*int)] int + +// The following should be parsed as a generic type, but is instead parsed as an array type. +type _[P *struct /* ERROR "not an expression" */ {}| int /* ERROR "not an expression" */ ] struct{} + +// The following fails to parse, due to the '~' +type _[P *struct /* ERROR "not an expression" */ {}|~ /* ERROR "unexpected ~" */ int] struct{} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue49541.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue49541.go2 index b7bf12a186..c8499c1b61 100644 --- a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue49541.go2 +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue49541.go2 @@ -10,9 +10,10 @@ type S[A, B any] struct { func (S[A, B]) m() {} -// TODO(gri) We should only report one error below. See issue #50588. +// TODO(gri): with type-type inference enabled we should only report one error +// below. See issue #50588. -func _[A any](s S /* ERROR cannot infer B */ /* ERROR got 1 arguments but 2 type parameters */ [A]) { +func _[A any](s S /* ERROR got 1 arguments but 2 type parameters */ [A]) { // we should see no follow-on errors below s.f = 1 s.m() @@ -21,7 +22,7 @@ func _[A any](s S /* ERROR cannot infer B */ /* ERROR got 1 arguments but 2 type // another test case from the issue func _() { - X(Interface[*F /* ERROR cannot infer B */ /* ERROR got 1 arguments but 2 type parameters */ [string]](Impl{})) + X(Interface[*F /* ERROR got 1 arguments but 2 type parameters */ [string]](Impl{})) } func X[Q Qer](fs Interface[Q]) { diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue49735.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue49735.go2 new file mode 100644 index 0000000000..50870226e4 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue49735.go2 @@ -0,0 +1,11 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +func _[P1 any, P2 ~byte](s1 P1, s2 P2) { + _ = append(nil /* ERROR first argument to append must be a slice; have untyped nil */ , 0) + _ = append(s1 /* ERROR s1 .* has no core type */ , 0) + _ = append(s2 /* ERROR s2 .* has core type byte */ , 0) +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue50417.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue50417.go2 index b6454ab003..2caef1b986 100644 --- a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue50417.go2 +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue50417.go2 @@ -2,6 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// Field accesses through type parameters are disabled +// until we have a more thorough understanding of the +// implications on the spec. See issue #51576. + package p type Sf struct { @@ -9,13 +13,13 @@ type Sf struct { } func f0[P Sf](p P) { - _ = p.f - p.f = 0 + _ = p.f // ERROR p\.f undefined + p.f /* ERROR p\.f undefined */ = 0 } func f0t[P ~struct{f int}](p P) { - _ = p.f - p.f = 0 + _ = p.f // ERROR p\.f undefined + p.f /* ERROR p\.f undefined */ = 0 } var _ = f0[Sf] @@ -25,8 +29,8 @@ var _ = f0[Sm /* ERROR does not implement */ ] var _ = f0t[Sm /* ERROR does not implement */ ] func f1[P interface{ Sf; m() }](p P) { - _ = p.f - p.f = 0 + _ = p.f // ERROR p\.f undefined + p.f /* ERROR p\.f undefined */ = 0 p.m() } @@ -44,20 +48,20 @@ type Sfm struct { func (Sfm) m() {} func f2[P interface{ Sfm; m() }](p P) { - _ = p.f - p.f = 0 + _ = p.f // ERROR p\.f undefined + p.f /* ERROR p\.f undefined */ = 0 p.m() } var _ = f2[Sfm] -// special case: structural type is a named pointer type +// special case: core type is a named pointer type type PSfm *Sfm func f3[P interface{ PSfm }](p P) { - _ = p.f - p.f = 0 + _ = p.f // ERROR p\.f undefined + p.f /* ERROR p\.f undefined */ = 0 p.m /* ERROR type P has no field or method m */ () } diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue50755.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue50755.go2 new file mode 100644 index 0000000000..afc7b2414c --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue50755.go2 @@ -0,0 +1,47 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +// The core type of M2 unifies with the type of m1 +// during function argument type inference. +// M2's constraint is unnamed. +func f1[K1 comparable, E1 any](m1 map[K1]E1) {} + +func f2[M2 map[string]int](m2 M2) { + f1(m2) +} + +// The core type of M3 unifies with the type of m1 +// during function argument type inference. +// M3's constraint is named. +type Map3 map[string]int + +func f3[M3 Map3](m3 M3) { + f1(m3) +} + +// The core type of M5 unifies with the core type of M4 +// during constraint type inference. +func f4[M4 map[K4]int, K4 comparable](m4 M4) {} + +func f5[M5 map[K5]int, K5 comparable](m5 M5) { + f4(m5) +} + +// test case from issue + +func Copy[MC ~map[KC]VC, KC comparable, VC any](dst, src MC) { + for k, v := range src { + dst[k] = v + } +} + +func Merge[MM ~map[KM]VM, KM comparable, VM any](ms ...MM) MM { + result := MM{} + for _, m := range ms { + Copy(result, m) + } + return result +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue50782.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue50782.go2 index 8f41b84163..fd1ab11b8c 100644 --- a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue50782.go2 +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue50782.go2 @@ -2,6 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// Field accesses through type parameters are disabled +// until we have a more thorough understanding of the +// implications on the spec. See issue #51576. + package p // The first example from the issue. @@ -18,9 +22,12 @@ type numericAbs[T Numeric] interface { // AbsDifference computes the absolute value of the difference of // a and b, where the absolute value is determined by the Abs method. func absDifference[T numericAbs[T /* ERROR T does not implement Numeric */]](a, b T) T { - // TODO: the error below should probably be positioned on the '-'. - d := a /* ERROR "invalid operation: operator - not defined" */ .Value - b.Value - return d.Abs() + // Field accesses are not permitted for now. Keep an error so + // we can find and fix this code once the situation changes. + return a.Value // ERROR a\.Value undefined + // TODO: The error below should probably be positioned on the '-'. + // d := a /* ERROR "invalid operation: operator - not defined" */ .Value - b.Value + // return d.Abs() } // The second example from the issue. diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue50929.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue50929.go2 index 941dbaa3c1..3629ecf104 100644 --- a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue50929.go2 +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue50929.go2 @@ -16,7 +16,7 @@ func G[A, B any](F[A, B]) { func _() { // TODO(gri) only report one error below (issue #50932) - var x F /* ERROR cannot infer B */ /* ERROR got 1 arguments but 2 type parameters */ [int] + var x F /* ERROR got 1 arguments but 2 type parameters */ [int] G(x /* ERROR does not match */) } @@ -46,9 +46,9 @@ func NSG[G any](c RSC[G]) { fmt.Println(c) } -func MMD[Rc RC /* ERROR cannot infer RG */ /* ERROR got 1 arguments */ [RG], RG any, G any]() M /* ERROR got 2 arguments */ /* ERROR Rc does not match */ [Rc, RG] { +func MMD[Rc RC /* ERROR got 1 arguments */ [RG], RG any, G any]() M /* ERROR got 2 arguments */ [Rc, RG] { - var nFn NFn /* ERROR got 2 arguments */ /* ERROR Rc does not match */ [Rc, RG] + var nFn NFn /* ERROR got 2 arguments */ [Rc, RG] var empty Rc switch any(empty).(type) { diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51145.go b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51145.go new file mode 100644 index 0000000000..b84391df19 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51145.go @@ -0,0 +1,18 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +import "fmt" + +type ( + _ [fmt /* ERROR invalid array length fmt */ ]int + _ [float64 /* ERROR invalid array length float64 */ ]int + _ [f /* ERROR invalid array length f */ ]int + _ [nil /* ERROR invalid array length nil */ ]int +) + +func f() + +var _ fmt.Stringer // use fmt diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51158.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51158.go2 new file mode 100644 index 0000000000..3edc505382 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51158.go2 @@ -0,0 +1,18 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +// Type checking the following code should not cause an infinite recursion. +func f[M map[K]int, K comparable](m M) { + f(m) +} + +// Equivalent code using mutual recursion. +func f1[M map[K]int, K comparable](m M) { + f2(m) +} +func f2[M map[K]int, K comparable](m M) { + f1(m) +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51229.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51229.go2 new file mode 100644 index 0000000000..ef873e6ea8 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51229.go2 @@ -0,0 +1,164 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +// Constraint type inference should be independent of the +// ordering of the type parameter declarations. Try all +// permutations in the test case below. +// Permutations produced by https://go.dev/play/p/PHcZNGJTEBZ. + +func f00[S1 ~[]E1, S2 ~[]E2, E1 ~byte, E2 ~byte](S1, S2) {} +func f01[S2 ~[]E2, S1 ~[]E1, E1 ~byte, E2 ~byte](S1, S2) {} +func f02[E1 ~byte, S1 ~[]E1, S2 ~[]E2, E2 ~byte](S1, S2) {} +func f03[S1 ~[]E1, E1 ~byte, S2 ~[]E2, E2 ~byte](S1, S2) {} +func f04[S2 ~[]E2, E1 ~byte, S1 ~[]E1, E2 ~byte](S1, S2) {} +func f05[E1 ~byte, S2 ~[]E2, S1 ~[]E1, E2 ~byte](S1, S2) {} +func f06[E2 ~byte, S2 ~[]E2, S1 ~[]E1, E1 ~byte](S1, S2) {} +func f07[S2 ~[]E2, E2 ~byte, S1 ~[]E1, E1 ~byte](S1, S2) {} +func f08[S1 ~[]E1, E2 ~byte, S2 ~[]E2, E1 ~byte](S1, S2) {} +func f09[E2 ~byte, S1 ~[]E1, S2 ~[]E2, E1 ~byte](S1, S2) {} +func f10[S2 ~[]E2, S1 ~[]E1, E2 ~byte, E1 ~byte](S1, S2) {} +func f11[S1 ~[]E1, S2 ~[]E2, E2 ~byte, E1 ~byte](S1, S2) {} +func f12[S1 ~[]E1, E1 ~byte, E2 ~byte, S2 ~[]E2](S1, S2) {} +func f13[E1 ~byte, S1 ~[]E1, E2 ~byte, S2 ~[]E2](S1, S2) {} +func f14[E2 ~byte, S1 ~[]E1, E1 ~byte, S2 ~[]E2](S1, S2) {} +func f15[S1 ~[]E1, E2 ~byte, E1 ~byte, S2 ~[]E2](S1, S2) {} +func f16[E1 ~byte, E2 ~byte, S1 ~[]E1, S2 ~[]E2](S1, S2) {} +func f17[E2 ~byte, E1 ~byte, S1 ~[]E1, S2 ~[]E2](S1, S2) {} +func f18[E2 ~byte, E1 ~byte, S2 ~[]E2, S1 ~[]E1](S1, S2) {} +func f19[E1 ~byte, E2 ~byte, S2 ~[]E2, S1 ~[]E1](S1, S2) {} +func f20[S2 ~[]E2, E2 ~byte, E1 ~byte, S1 ~[]E1](S1, S2) {} +func f21[E2 ~byte, S2 ~[]E2, E1 ~byte, S1 ~[]E1](S1, S2) {} +func f22[E1 ~byte, S2 ~[]E2, E2 ~byte, S1 ~[]E1](S1, S2) {} +func f23[S2 ~[]E2, E1 ~byte, E2 ~byte, S1 ~[]E1](S1, S2) {} + +type myByte byte + +func _(a []byte, b []myByte) { + f00(a, b) + f01(a, b) + f02(a, b) + f03(a, b) + f04(a, b) + f05(a, b) + f06(a, b) + f07(a, b) + f08(a, b) + f09(a, b) + f10(a, b) + f11(a, b) + f12(a, b) + f13(a, b) + f14(a, b) + f15(a, b) + f16(a, b) + f17(a, b) + f18(a, b) + f19(a, b) + f20(a, b) + f21(a, b) + f22(a, b) + f23(a, b) +} + +// Constraint type inference may have to iterate. +// Again, the order of the type parameters shouldn't matter. + +func g0[S ~[]E, M ~map[string]S, E any](m M) {} +func g1[M ~map[string]S, S ~[]E, E any](m M) {} +func g2[E any, S ~[]E, M ~map[string]S](m M) {} +func g3[S ~[]E, E any, M ~map[string]S](m M) {} +func g4[M ~map[string]S, E any, S ~[]E](m M) {} +func g5[E any, M ~map[string]S, S ~[]E](m M) {} + +func _(m map[string][]byte) { + g0(m) + g1(m) + g2(m) + g3(m) + g4(m) + g5(m) +} + +// Worst-case scenario. +// There are 10 unknown type parameters. In each iteration of +// constraint type inference we infer one more, from right to left. +// Each iteration looks repeatedly at all 11 type parameters, +// requiring a total of 10*11 = 110 iterations with the current +// implementation. Pathological case. + +func h[K any, J ~*K, I ~*J, H ~*I, G ~*H, F ~*G, E ~*F, D ~*E, C ~*D, B ~*C, A ~*B](x A) {} + +func _(x **********int) { + h(x) +} + +// Examples with channel constraints and tilde. + +func ch1[P chan<- int]() (_ P) { return } // core(P) == chan<- int (single type, no tilde) +func ch2[P ~chan int]() { return } // core(P) == ~chan<- int (tilde) +func ch3[P chan E, E any](E) { return } // core(P) == chan<- E (single type, no tilde) +func ch4[P chan E | ~chan<- E, E any](E) { return } // core(P) == ~chan<- E (tilde) +func ch5[P chan int | chan<- int]() { return } // core(P) == chan<- int (not a single type) + +func _() { + // P can be inferred as there's a single specific type and no tilde. + var _ chan int = ch1 /* ERROR cannot use ch1.*value of type chan<- int */ () + var _ chan<- int = ch1() + + // P cannot be inferred as there's a tilde. + ch2( /* ERROR cannot infer P */ ) + type myChan chan int + ch2[myChan]() + + // P can be inferred as there's a single specific type and no tilde. + var e int + ch3(e) + + // P cannot be inferred as there's more than one specific type and a tilde. + ch4( /* ERROR cannot infer P */ e) + _ = ch4[chan int] + + // P cannot be inferred as there's more than one specific type. + ch5( /* ERROR cannot infer P */ ) + ch5[chan<- int]() +} + +// test case from issue + +func equal[M1 ~map[K1]V1, M2 ~map[K2]V2, K1, K2 ~uint32, V1, V2 ~string](m1 M1, m2 M2) bool { + if len(m1) != len(m2) { + return false + } + for k, v1 := range m1 { + if v2, ok := m2[K2(k)]; !ok || V2(v1) != v2 { + return false + } + } + return true +} + +func equalFixed[K1, K2 ~uint32, V1, V2 ~string](m1 map[K1]V1, m2 map[K2]V2) bool { + if len(m1) != len(m2) { + return false + } + for k, v1 := range m1 { + if v2, ok := m2[K2(k)]; !ok || v1 != V1(v2) { + return false + } + } + return true +} + +type ( + someNumericID uint32 + someStringID string +) + +func _() { + foo := map[uint32]string{10: "bar"} + bar := map[someNumericID]someStringID{10: "bar"} + equal(foo, bar) +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51232.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51232.go2 new file mode 100644 index 0000000000..3fa6a05732 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51232.go2 @@ -0,0 +1,30 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type RC[RG any] interface { + ~[]RG +} + +type Fn[RCT RC[RG], RG any] func(RCT) + +type F[RCT RC[RG], RG any] interface { + Fn() Fn /* ERROR got 1 arguments */ [RCT] +} + +type concreteF[RCT RC[RG], RG any] struct { + makeFn func() Fn /* ERROR got 1 arguments */ [RCT] +} + +func (c *concreteF[RCT, RG]) Fn() Fn /* ERROR got 1 arguments */ [RCT] { + return c.makeFn() +} + +func NewConcrete[RCT RC[RG], RG any](Rc RCT) F /* ERROR got 1 arguments */ [RCT] { + // TODO(rfindley): eliminate the duplicate error below. + return & /* ERROR cannot use .* as F\[RCT\] */ concreteF /* ERROR got 1 arguments */ [RCT]{ + makeFn: nil, + } +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51233.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51233.go2 new file mode 100644 index 0000000000..9c15028c91 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51233.go2 @@ -0,0 +1,27 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +// As of issue #51527, type-type inference has been disabled. + +type RC[RG any] interface { + ~[]RG +} + +type Fn[RCT RC[RG], RG any] func(RCT) + +type FFn[RCT RC[RG], RG any] func() Fn /* ERROR got 1 arguments */ [RCT] + +type F[RCT RC[RG], RG any] interface { + Fn() Fn /* ERROR got 1 arguments */ [RCT] +} + +type concreteF[RCT RC[RG], RG any] struct { + makeFn FFn /* ERROR got 1 arguments */ [RCT] +} + +func (c *concreteF[RCT, RG]) Fn() Fn /* ERROR got 1 arguments */ [RCT] { + return c.makeFn() +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51257.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51257.go2 new file mode 100644 index 0000000000..bc4208e6ee --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51257.go2 @@ -0,0 +1,46 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +func f[_ comparable]() {} + +type S1 struct{ x int } +type S2 struct{ x any } +type S3 struct{ x [10]interface{ m() } } + +func _[P1 comparable, P2 S2]() { + _ = f[S1] + _ = f[S2 /* ERROR S2 does not implement comparable */ ] + _ = f[S3 /* ERROR S3 does not implement comparable */ ] + + type L1 struct { x P1 } + type L2 struct { x P2 } + _ = f[L1] + _ = f[L2 /* ERROR L2 does not implement comparable */ ] +} + + +// example from issue + +type Set[T comparable] map[T]struct{} + +func NewSetFromSlice[T comparable](items []T) *Set[T] { + s := Set[T]{} + + for _, item := range items { + s[item] = struct{}{} + } + + return &s +} + +type T struct{ x any } + +func main() { + NewSetFromSlice( /* ERROR T does not implement comparable */ []T{ + {"foo"}, + {5}, + }) +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51335.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51335.go2 new file mode 100644 index 0000000000..0b5a1af082 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51335.go2 @@ -0,0 +1,16 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type S1 struct{} +type S2 struct{} + +func _[P *S1|*S2]() { + _= []P{{ /* ERROR invalid composite literal element type P: no core type */ }} +} + +func _[P *S1|S1]() { + _= []P{{ /* ERROR invalid composite literal element type P: no core type */ }} +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51339.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51339.go2 new file mode 100644 index 0000000000..84e551d9ad --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51339.go2 @@ -0,0 +1,18 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file is tested when running "go test -run Manual" +// without source arguments. Use for one-off debugging. + +package p + +type T[P any, B *P] struct{} + +func (T /* ERROR cannot use generic type */ ) m0() {} + +// TODO(rfindley): eliminate the duplicate errors here. +func (T /* ERROR got 1 type parameter, but receiver base type declares 2 */ /* ERROR got 1 arguments but 2 type parameters */ [_]) m1() {} +func (T[_, _]) m2() {} +// TODO(gri) this error is unfortunate (issue #51343) +func (T /* ERROR got 3 arguments but 2 type parameters */ [_, _, _]) m3() {} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51360.go b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51360.go new file mode 100644 index 0000000000..447ce036ae --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51360.go @@ -0,0 +1,13 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +func _() { + len. /* ERROR cannot select on len */ Println + len. /* ERROR cannot select on len */ Println() + _ = len. /* ERROR cannot select on len */ Println + _ = len[ /* ERROR cannot index len */ 0] + _ = *len /* ERROR cannot indirect len */ +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51376.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51376.go2 new file mode 100644 index 0000000000..4eba071801 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51376.go2 @@ -0,0 +1,24 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type Map map[string]int + +func f[M ~map[K]V, K comparable, V any](M) {} +func g[M map[K]V, K comparable, V any](M) {} + +func _[M1 ~map[K]V, M2 map[K]V, K comparable, V any]() { + var m1 M1 + f(m1) + g( /* ERROR M1 does not implement map\[K\]V */ m1) // M1 has tilde + + var m2 M2 + f(m2) + g(m2) // M1 does not have tilde + + var m3 Map + f(m3) + g( /* ERROR Map does not implement map\[string\]int */ m3) // M in g does not have tilde +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51386.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51386.go2 new file mode 100644 index 0000000000..ef6223927a --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51386.go2 @@ -0,0 +1,17 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type myString string + +func _[P ~string | ~[]byte | ~[]rune]() { + _ = P("") + const s myString = "" + _ = P(s) +} + +func _[P myString]() { + _ = P("") +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51437.go b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51437.go new file mode 100644 index 0000000000..376261516e --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51437.go @@ -0,0 +1,17 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type T struct{} + +func (T) m() []int { return nil } + +func f(x T) { + for _, x := range func() []int { + return x.m() // x declared in parameter list of f + }() { + _ = x // x declared by range clause + } +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51472.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51472.go2 new file mode 100644 index 0000000000..f19d906d97 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51472.go2 @@ -0,0 +1,54 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +func _[T comparable](x T) { + _ = x == x +} + +func _[T interface{interface{comparable}}](x T) { + _ = x == x +} + +func _[T interface{comparable; interface{comparable}}](x T) { + _ = x == x +} + +func _[T interface{comparable; ~int}](x T) { + _ = x == x +} + +func _[T interface{comparable; ~[]byte}](x T) { + _ = x /* ERROR cannot compare */ == x +} + +// TODO(gri) The error message here should be better. See issue #51525. +func _[T interface{comparable; ~int; ~string}](x T) { + _ = x /* ERROR cannot compare */ == x +} + +// TODO(gri) The error message here should be better. See issue #51525. +func _[T interface{~int; ~string}](x T) { + _ = x /* ERROR cannot compare */ == x +} + +func _[T interface{comparable; interface{~int}; interface{int|float64}}](x T) { + _ = x == x +} + +func _[T interface{interface{comparable; ~int}; interface{~float64; comparable; m()}}](x T) { + _ = x /* ERROR cannot compare */ == x +} + +// test case from issue + +func f[T interface{comparable; []byte|string}](x T) { + _ = x == x +} + +func _(s []byte) { + f( /* ERROR \[\]byte does not implement interface{comparable; \[\]byte\|string} */ s) + _ = f[[ /* ERROR does not implement */ ]byte] +} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51509.go b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51509.go new file mode 100644 index 0000000000..5ae47176d0 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51509.go @@ -0,0 +1,7 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type T /* ERROR illegal cycle */ T.x diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51578.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51578.go2 new file mode 100644 index 0000000000..5c204bae20 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51578.go2 @@ -0,0 +1,17 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +var _ = (*interface /* ERROR interface contains type constraints */ {int})(nil) + +// abbreviated test case from issue + +type TypeSet interface{ int | string } + +func _() { + f((*TypeSet /* ERROR interface contains type constraints */)(nil)) +} + +func f(any) {} \ No newline at end of file diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51593.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51593.go2 new file mode 100644 index 0000000000..d323618ee8 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51593.go2 @@ -0,0 +1,13 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +func f[P interface{ m(R) }, R any]() {} + +type T = interface { m(int) } + +func _() { + _ = f[ /* ERROR cannot infer R */ T] // don't crash in type inference +} diff --git a/src/cmd/compile/internal/types2/type.go b/src/cmd/compile/internal/types2/type.go index 9487ac5a84..0fe39dbca4 100644 --- a/src/cmd/compile/internal/types2/type.go +++ b/src/cmd/compile/internal/types2/type.go @@ -7,9 +7,7 @@ package types2 // A Type represents a type of Go. // All types implement the Type interface. type Type interface { - // Underlying returns the underlying type of a type - // w/o following forwarding chains. Only used by - // client packages. + // Underlying returns the underlying type of a type. Underlying() Type // String returns a string representation of a type. @@ -27,13 +25,13 @@ func under(t Type) Type { return t.Underlying() } -// If t is not a type parameter, structuralType returns the underlying type. -// If t is a type parameter, structuralType returns the single underlying +// If t is not a type parameter, coreType returns the underlying type. +// If t is a type parameter, coreType returns the single underlying // type of all types in its type set if it exists, or nil otherwise. If the // type set contains only unrestricted and restricted channel types (with // identical element types), the single underlying type is the restricted // channel type if the restrictions are always the same, or nil otherwise. -func structuralType(t Type) Type { +func coreType(t Type) Type { tpar, _ := t.(*TypeParam) if tpar == nil { return under(t) @@ -59,10 +57,10 @@ func structuralType(t Type) Type { return nil } -// structuralString is like structuralType but also considers []byte +// coreString is like coreType but also considers []byte // and strings as identical. In this case, if successful and we saw // a string, the result is of type (possibly untyped) string. -func structuralString(t Type) Type { +func coreString(t Type) Type { tpar, _ := t.(*TypeParam) if tpar == nil { return under(t) // string or untyped string diff --git a/src/cmd/compile/internal/types2/typeparam.go b/src/cmd/compile/internal/types2/typeparam.go index 971fdaec73..2e9a2adae6 100644 --- a/src/cmd/compile/internal/types2/typeparam.go +++ b/src/cmd/compile/internal/types2/typeparam.go @@ -31,11 +31,13 @@ func (t *TypeParam) Obj() *TypeName { return t.obj } // or Signature type by calling SetTypeParams. Setting a type parameter on more // than one type will result in a panic. // -// The constraint argument can be nil, and set later via SetConstraint. +// The constraint argument can be nil, and set later via SetConstraint. If the +// constraint is non-nil, it must be fully defined. func NewTypeParam(obj *TypeName, constraint Type) *TypeParam { return (*Checker)(nil).newTypeParam(obj, constraint) } +// check may be nil func (check *Checker) newTypeParam(obj *TypeName, constraint Type) *TypeParam { // Always increment lastID, even if it is not used. id := nextID() @@ -50,9 +52,7 @@ func (check *Checker) newTypeParam(obj *TypeName, constraint Type) *TypeParam { // iface may mutate typ.bound, so we must ensure that iface() is called // at least once before the resulting TypeParam escapes. if check != nil { - check.later(func() { - typ.iface() - }) + check.needsCleanup(typ) } else if constraint != nil { typ.iface() } @@ -72,8 +72,10 @@ func (t *TypeParam) Constraint() Type { // SetConstraint sets the type constraint for t. // -// SetConstraint should not be called concurrently, but once SetConstraint -// returns the receiver t is safe for concurrent use. +// It must be called by users of NewTypeParam after the bound's underlying is +// fully defined, and before using the type parameter in any way other than to +// form other types. Once SetConstraint returns the receiver, t is safe for +// concurrent use. func (t *TypeParam) SetConstraint(bound Type) { if bound == nil { panic("nil constraint") @@ -93,9 +95,12 @@ func (t *TypeParam) String() string { return TypeString(t, nil) } // ---------------------------------------------------------------------------- // Implementation +func (t *TypeParam) cleanup() { + t.iface() + t.check = nil +} + // iface returns the constraint interface of t. -// TODO(gri) If we make tparamIsIface the default, this should be renamed to under -// (similar to Named.under). func (t *TypeParam) iface() *Interface { bound := t.bound @@ -136,16 +141,6 @@ func (t *TypeParam) iface() *Interface { return ityp } -// singleType returns the single type of the type parameter constraint; or nil. -func (t *TypeParam) singleType() Type { - return t.iface().typeSet().singleType() -} - -// hasTerms reports whether the type parameter constraint has specific type terms. -func (t *TypeParam) hasTerms() bool { - return t.iface().typeSet().hasTerms() -} - // is calls f with the specific type terms of t's constraint and reports whether // all calls to f returned true. If there are no specific terms, is // returns the result of f(nil). diff --git a/src/cmd/compile/internal/types2/typeset.go b/src/cmd/compile/internal/types2/typeset.go index fff348bcf4..8df8949435 100644 --- a/src/cmd/compile/internal/types2/typeset.go +++ b/src/cmd/compile/internal/types2/typeset.go @@ -15,20 +15,25 @@ import ( // API // A _TypeSet represents the type set of an interface. +// Because of existing language restrictions, methods can be "factored out" +// from the terms. The actual type set is the intersection of the type set +// implied by the methods and the type set described by the terms and the +// comparable bit. To test whether a type is included in a type set +// ("implements" relation), the type must implement all methods _and_ be +// an element of the type set described by the terms and the comparable bit. +// If the term list describes the set of all types and comparable is true, +// only comparable types are meant; in all other cases comparable is false. type _TypeSet struct { - comparable bool // if set, the interface is or embeds comparable - // TODO(gri) consider using a set for the methods for faster lookup - methods []*Func // all methods of the interface; sorted by unique ID - terms termlist // type terms of the type set + methods []*Func // all methods of the interface; sorted by unique ID + terms termlist // type terms of the type set + comparable bool // invariant: !comparable || terms.isAll() } // IsEmpty reports whether type set s is the empty set. func (s *_TypeSet) IsEmpty() bool { return s.terms.isEmpty() } // IsAll reports whether type set s is the set of all types (corresponding to the empty interface). -func (s *_TypeSet) IsAll() bool { - return !s.comparable && len(s.methods) == 0 && s.terms.isAll() -} +func (s *_TypeSet) IsAll() bool { return s.IsMethodSet() && len(s.methods) == 0 } // IsMethodSet reports whether the interface t is fully described by its method set. func (s *_TypeSet) IsMethodSet() bool { return !s.comparable && s.terms.isAll() } @@ -39,17 +44,10 @@ func (s *_TypeSet) IsComparable(seen map[Type]bool) bool { return s.comparable } return s.is(func(t *term) bool { - return t != nil && comparable(t.typ, seen, nil) + return t != nil && comparable(t.typ, false, seen, nil) }) } -// TODO(gri) IsTypeSet is not a great name for this predicate. Find a better one. - -// IsTypeSet reports whether the type set s is represented by a finite set of underlying types. -func (s *_TypeSet) IsTypeSet() bool { - return !s.comparable && len(s.methods) == 0 -} - // NumMethods returns the number of methods available. func (s *_TypeSet) NumMethods() int { return len(s.methods) } @@ -103,9 +101,6 @@ func (s *_TypeSet) String() string { // hasTerms reports whether the type set has specific type terms. func (s *_TypeSet) hasTerms() bool { return !s.terms.isEmpty() && !s.terms.isAll() } -// singleType returns the single type in s if there is exactly one; otherwise the result is nil. -func (s *_TypeSet) singleType() Type { return s.terms.singleType() } - // subsetOf reports whether s1 ⊆ s2. func (s1 *_TypeSet) subsetOf(s2 *_TypeSet) bool { return s1.terms.subsetOf(s2.terms) } @@ -218,12 +213,12 @@ func computeInterfaceTypeSet(check *Checker, pos syntax.Pos, ityp *Interface) *_ var todo []*Func var seen objset - var methods []*Func + var allMethods []*Func mpos := make(map[*Func]syntax.Pos) // method specification or method embedding position, for good error messages addMethod := func(pos syntax.Pos, m *Func, explicit bool) { switch other := seen.insert(m); { case other == nil: - methods = append(methods, m) + allMethods = append(allMethods, m) mpos[m] = pos case explicit: if check == nil { @@ -262,7 +257,8 @@ func computeInterfaceTypeSet(check *Checker, pos syntax.Pos, ityp *Interface) *_ } // collect embedded elements - var allTerms = allTermlist + allTerms := allTermlist + allComparable := false for i, typ := range ityp.embeddeds { // The embedding position is nil for imported interfaces // and also for interface copies after substitution (but @@ -271,6 +267,7 @@ func computeInterfaceTypeSet(check *Checker, pos syntax.Pos, ityp *Interface) *_ if ityp.embedPos != nil { pos = (*ityp.embedPos)[i] } + var comparable bool var terms termlist switch u := under(typ).(type) { case *Interface: @@ -282,9 +279,7 @@ func computeInterfaceTypeSet(check *Checker, pos syntax.Pos, ityp *Interface) *_ check.versionErrorf(pos, "go1.18", "embedding constraint interface %s", typ) continue } - if tset.comparable { - ityp.tset.comparable = true - } + comparable = tset.comparable for _, m := range tset.methods { addMethod(pos, m, false) // use embedding position pos rather than m.pos } @@ -298,6 +293,8 @@ func computeInterfaceTypeSet(check *Checker, pos syntax.Pos, ityp *Interface) *_ if tset == &invalidTypeSet { continue // ignore invalid unions } + assert(!tset.comparable) + assert(len(tset.methods) == 0) terms = tset.terms default: if u == Typ[Invalid] { @@ -309,11 +306,11 @@ func computeInterfaceTypeSet(check *Checker, pos syntax.Pos, ityp *Interface) *_ } terms = termlist{{false, typ}} } - // The type set of an interface is the intersection - // of the type sets of all its elements. - // Intersection cannot produce longer termlists and - // thus cannot overflow. - allTerms = allTerms.intersect(terms) + + // The type set of an interface is the intersection of the type sets of all its elements. + // Due to language restrictions, only embedded interfaces can add methods, they are handled + // separately. Here we only need to intersect the term lists and comparable bits. + allTerms, allComparable = intersectTermLists(allTerms, allComparable, terms, comparable) } ityp.embedPos = nil // not needed anymore (errors have been reported) @@ -326,15 +323,46 @@ func computeInterfaceTypeSet(check *Checker, pos syntax.Pos, ityp *Interface) *_ } } - if methods != nil { - sortMethods(methods) - ityp.tset.methods = methods + ityp.tset.comparable = allComparable + if len(allMethods) != 0 { + sortMethods(allMethods) + ityp.tset.methods = allMethods } ityp.tset.terms = allTerms return ityp.tset } +// TODO(gri) The intersectTermLists function belongs to the termlist implementation. +// The comparable type set may also be best represented as a term (using +// a special type). + +// intersectTermLists computes the intersection of two term lists and respective comparable bits. +// xcomp, ycomp are valid only if xterms.isAll() and yterms.isAll() respectively. +func intersectTermLists(xterms termlist, xcomp bool, yterms termlist, ycomp bool) (termlist, bool) { + terms := xterms.intersect(yterms) + // If one of xterms or yterms is marked as comparable, + // the result must only include comparable types. + comp := xcomp || ycomp + if comp && !terms.isAll() { + // only keep comparable terms + i := 0 + for _, t := range terms { + assert(t.typ != nil) + if Comparable(t.typ) { + terms[i] = t + i++ + } + } + terms = terms[:i] + if !terms.isAll() { + comp = false + } + } + assert(!comp || terms.isAll()) // comparable invariant + return terms, comp +} + func sortMethods(list []*Func) { sort.Sort(byUniqueMethodName(list)) } diff --git a/src/cmd/compile/internal/types2/typeset_test.go b/src/cmd/compile/internal/types2/typeset_test.go index 7f7cc06db9..68e5d8ad62 100644 --- a/src/cmd/compile/internal/types2/typeset_test.go +++ b/src/cmd/compile/internal/types2/typeset_test.go @@ -25,9 +25,9 @@ func TestTypeSetString(t *testing.T) { "{int; string}": "∅", "{comparable}": "{comparable}", - "{comparable; int}": "{comparable; int}", - "{~int; comparable}": "{comparable; ~int}", - "{int|string; comparable}": "{comparable; int ∪ string}", + "{comparable; int}": "{int}", + "{~int; comparable}": "{~int}", + "{int|string; comparable}": "{int ∪ string}", "{comparable; int; string}": "∅", "{m()}": "{func (p.T).m()}", @@ -37,8 +37,8 @@ func TestTypeSetString(t *testing.T) { "{m1(); comparable; m2() int }": "{comparable; func (p.T).m1(); func (p.T).m2() int}", "{comparable; error}": "{comparable; func (error).Error() string}", - "{m(); comparable; int|float32|string}": "{comparable; func (p.T).m(); int ∪ float32 ∪ string}", - "{m1(); int; m2(); comparable }": "{comparable; func (p.T).m1(); func (p.T).m2(); int}", + "{m(); comparable; int|float32|string}": "{func (p.T).m(); int ∪ float32 ∪ string}", + "{m1(); int; m2(); comparable }": "{func (p.T).m1(); func (p.T).m2(); int}", "{E}; type E interface{}": "𝓤", "{E}; type E interface{int;string}": "∅", diff --git a/src/cmd/compile/internal/types2/typexpr.go b/src/cmd/compile/internal/types2/typexpr.go index de778fb010..7e30562e97 100644 --- a/src/cmd/compile/internal/types2/typexpr.go +++ b/src/cmd/compile/internal/types2/typexpr.go @@ -147,10 +147,16 @@ func (check *Checker) typ(e syntax.Expr) Type { // constraint interface. func (check *Checker) varType(e syntax.Expr) Type { typ := check.definedType(e, nil) + check.validVarType(e, typ) + return typ +} +// validVarType reports an error if typ is a constraint interface. +// The expression e is used for error reporting, if any. +func (check *Checker) validVarType(e syntax.Expr, typ Type) { // If we have a type parameter there's nothing to do. if isTypeParam(typ) { - return typ + return } // We don't want to call under() or complete interfaces while we are in @@ -169,8 +175,6 @@ func (check *Checker) varType(e syntax.Expr) Type { } } }) - - return typ } // definedType is like typ but also accepts a type name def. @@ -256,7 +260,7 @@ func (check *Checker) typInternal(e0 syntax.Expr, def *Named) (T Type) { case *syntax.SelectorExpr: var x operand - check.selector(&x, e) + check.selector(&x, e, def) switch x.mode { case typexpr: @@ -342,7 +346,7 @@ func (check *Checker) typInternal(e0 syntax.Expr, def *Named) (T Type) { return typ case *syntax.InterfaceType: - typ := new(Interface) + typ := check.newInterface() def.setUnderlying(typ) if def != nil { typ.obj = def.obj @@ -430,10 +434,14 @@ func (check *Checker) instantiatedType(x syntax.Expr, xlist []syntax.Expr, def * // evaluate arguments targs := check.typeList(xlist) if targs == nil { - def.setUnderlying(Typ[Invalid]) // avoid later errors due to lazy instantiation + def.setUnderlying(Typ[Invalid]) // avoid errors later due to lazy instantiation return Typ[Invalid] } + // enableTypeTypeInference controls whether to infer missing type arguments + // using constraint type inference. See issue #51527. + const enableTypeTypeInference = false + // create the instance ctxt := check.bestContext(nil) h := ctxt.instanceHash(orig, targs) @@ -453,19 +461,18 @@ func (check *Checker) instantiatedType(x syntax.Expr, xlist []syntax.Expr, def * def.setUnderlying(inst) inst.resolver = func(ctxt *Context, n *Named) (*TypeParamList, Type, *methodList) { - tparams := orig.TypeParams().list() + tparams := n.orig.TypeParams().list() - inferred := targs - if len(targs) < len(tparams) { + targs := n.targs.list() + if enableTypeTypeInference && len(targs) < len(tparams) { // If inference fails, len(inferred) will be 0, and inst.underlying will // be set to Typ[Invalid] in expandNamed. - inferred = check.infer(x.Pos(), tparams, targs, nil, nil) + inferred := check.infer(x.Pos(), tparams, targs, nil, nil) if len(inferred) > len(targs) { - inst.targs = newTypeList(inferred) + n.targs = newTypeList(inferred) } } - check.recordInstance(x, inferred, inst) return expandNamed(ctxt, n, x.Pos()) } @@ -478,6 +485,7 @@ func (check *Checker) instantiatedType(x syntax.Expr, xlist []syntax.Expr, def * // Since check is non-nil, we can still mutate inst. Unpinning the resolver // frees some memory. inst.resolver = nil + check.recordInstance(x, inst.TypeArgs().list(), inst) if check.validateTArgLen(x.Pos(), inst.tparams.Len(), inst.targs.Len()) { if i, err := check.verify(x.Pos(), inst.tparams.list(), inst.targs.list()); err != nil { @@ -502,12 +510,20 @@ func (check *Checker) instantiatedType(x syntax.Expr, xlist []syntax.Expr, def * // and returns the constant length >= 0, or a value < 0 // to indicate an error (and thus an unknown length). func (check *Checker) arrayLength(e syntax.Expr) int64 { - // If e is an undeclared identifier, the array declaration might be an - // attempt at a parameterized type declaration with missing constraint. - // Provide a better error message than just "undeclared name: X". - if name, _ := e.(*syntax.Name); name != nil && check.lookup(name.Value) == nil { - check.errorf(name, "undeclared name %s for array length", name.Value) - return -1 + // If e is an identifier, the array declaration might be an + // attempt at a parameterized type declaration with missing + // constraint. Provide an error message that mentions array + // length. + if name, _ := e.(*syntax.Name); name != nil { + obj := check.lookup(name.Value) + if obj == nil { + check.errorf(name, "undeclared name %s for array length", name.Value) + return -1 + } + if _, ok := obj.(*Const); !ok { + check.errorf(name, "invalid array length %s", name.Value) + return -1 + } } var x operand diff --git a/src/cmd/compile/internal/types2/unify.go b/src/cmd/compile/internal/types2/unify.go index 079db3276c..97d327cf8b 100644 --- a/src/cmd/compile/internal/types2/unify.go +++ b/src/cmd/compile/internal/types2/unify.go @@ -9,6 +9,7 @@ package types2 import ( "bytes" "fmt" + "strings" ) // The unifier maintains two separate sets of type parameters x and y @@ -26,7 +27,7 @@ import ( // parameter P ("x" side), but the argument type P must be left alone so // that unification resolves the type parameter P to P. // -// For bidirection unification, both sets are provided. This enables +// For bidirectional unification, both sets are provided. This enables // unification to go from argument to parameter type and vice versa. // For constraint type inference, we use bidirectional unification // where both the x and y type parameters are identical. This is done @@ -41,6 +42,19 @@ const ( // Whether to panic when unificationDepthLimit is reached. Turn on when // investigating infinite recursion. panicAtUnificationDepthLimit = false + + // If enableCoreTypeUnification is set, unification will consider + // the core types, if any, of non-local (unbound) type parameters. + enableCoreTypeUnification = true + + // If traceInference is set, unification will print a trace of its operation. + // Interpretation of trace: + // x ≡ y attempt to unify types x and y + // p ➞ y type parameter p is set to type y (p is inferred to be y) + // p ⇄ q type parameters p and q match (p is inferred to be q and vice versa) + // x ≢ y types x and y cannot be unified + // [p, q, ...] ➞ [x, y, ...] mapping from type parameters to types + traceInference = false ) // A unifier maintains the current type parameters for x and y @@ -58,6 +72,7 @@ type unifier struct { // exactly. If exact is not set, a named type's underlying type // is considered if unification would fail otherwise, and the // direction of channels is ignored. +// TODO(gri) exact is not set anymore by a caller. Consider removing it. func newUnifier(exact bool) *unifier { u := &unifier{exact: exact} u.x.unifier = u @@ -70,6 +85,10 @@ func (u *unifier) unify(x, y Type) bool { return u.nify(x, y, nil) } +func (u *unifier) tracef(format string, args ...interface{}) { + fmt.Println(strings.Repeat(". ", u.depth) + sprintf(nil, true, format, args...)) +} + // A tparamsList describes a list of type parameters and the types inferred for them. type tparamsList struct { unifier *unifier @@ -121,6 +140,9 @@ func (d *tparamsList) init(tparams []*TypeParam) { // If both type parameters already have a type associated with them and they are // not joined, join fails and returns false. func (u *unifier) join(i, j int) bool { + if traceInference { + u.tracef("%s ⇄ %s", u.x.tparams[i], u.y.tparams[j]) + } ti := u.x.indices[i] tj := u.y.indices[j] switch { @@ -210,6 +232,9 @@ func (d *tparamsList) at(i int) Type { func (d *tparamsList) set(i int, typ Type) { assert(typ != nil) u := d.unifier + if traceInference { + u.tracef("%s ➞ %s", d.tparams[i], typ) + } switch ti := d.indices[i]; { case ti < 0: u.types[-ti-1] = typ @@ -222,6 +247,17 @@ func (d *tparamsList) set(i int, typ Type) { } } +// unknowns returns the number of type parameters for which no type has been set yet. +func (d *tparamsList) unknowns() int { + n := 0 + for _, ti := range d.indices { + if ti <= 0 { + n++ + } + } + return n +} + // types returns the list of inferred types (via unification) for the type parameters // described by d, and an index. If all types were inferred, the returned index is < 0. // Otherwise, it is the index of the first type parameter which couldn't be inferred; @@ -247,9 +283,16 @@ func (u *unifier) nifyEq(x, y Type, p *ifacePair) bool { // adapted version of Checker.identical. For changes to that // code the corresponding changes should be made here. // Must not be called directly from outside the unifier. -func (u *unifier) nify(x, y Type, p *ifacePair) bool { +func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) { + if traceInference { + u.tracef("%s ≡ %s", x, y) + } + // Stop gap for cases where unification fails. if u.depth >= unificationDepthLimit { + if traceInference { + u.tracef("depth %d >= %d", u.depth, unificationDepthLimit) + } if panicAtUnificationDepthLimit { panic("unification reached recursion depth limit") } @@ -258,6 +301,9 @@ func (u *unifier) nify(x, y Type, p *ifacePair) bool { u.depth++ defer func() { u.depth-- + if traceInference && !result { + u.tracef("%s ≢ %s", x, y) + } }() if !u.exact { @@ -267,8 +313,14 @@ func (u *unifier) nify(x, y Type, p *ifacePair) bool { // (We use !hasName to exclude any type with a name, including // basic types and type parameters; the rest are unamed types.) if nx, _ := x.(*Named); nx != nil && !hasName(y) { + if traceInference { + u.tracef("under %s ≡ %s", nx, y) + } return u.nify(nx.under(), y, p) } else if ny, _ := y.(*Named); ny != nil && !hasName(x) { + if traceInference { + u.tracef("%s ≡ under %s", x, ny) + } return u.nify(x, ny.under(), p) } } @@ -302,6 +354,39 @@ func (u *unifier) nify(x, y Type, p *ifacePair) bool { return true } + // If we get here and x or y is a type parameter, they are type parameters + // from outside our declaration list. Try to unify their core types, if any + // (see issue #50755 for a test case). + if enableCoreTypeUnification && !u.exact { + if isTypeParam(x) && !hasName(y) { + // When considering the type parameter for unification + // we look at the adjusted core term (adjusted core type + // with tilde information). + // If the adjusted core type is a named type N; the + // corresponding core type is under(N). Since !u.exact + // and y doesn't have a name, unification will end up + // comparing under(N) to y, so we can just use the core + // type instead. And we can ignore the tilde because we + // already look at the underlying types on both sides + // and we have known types on both sides. + // Optimization. + if cx := coreType(x); cx != nil { + if traceInference { + u.tracef("core %s ≡ %s", x, y) + } + return u.nify(cx, y, p) + } + } else if isTypeParam(y) && !hasName(x) { + // see comment above + if cy := coreType(y); cy != nil { + if traceInference { + u.tracef("%s ≡ core %s", x, y) + } + return u.nify(x, cy, p) + } + } + } + // For type unification, do not shortcut (x == y) for identical // types. Instead keep comparing them element-wise to unify the // matching (and equal type parameter types). A simple test case @@ -490,7 +575,7 @@ func (u *unifier) nify(x, y Type, p *ifacePair) bool { // avoid a crash in case of nil type default: - panic(fmt.Sprintf("### u.nify(%s, %s), u.x.tparams = %s", x, y, u.x.tparams)) + panic(sprintf(nil, true, "u.nify(%s, %s), u.x.tparams = %s", x, y, u.x.tparams)) } return false diff --git a/src/cmd/compile/internal/types2/union.go b/src/cmd/compile/internal/types2/union.go index 3c0df04ccd..e317b9cced 100644 --- a/src/cmd/compile/internal/types2/union.go +++ b/src/cmd/compile/internal/types2/union.go @@ -100,25 +100,27 @@ func parseUnion(check *Checker, uexpr syntax.Expr) Type { if !Identical(u, t.typ) { check.errorf(tlist[i], "invalid use of ~ (underlying type of %s is %s)", t.typ, u) - continue // don't report another error for t + continue } } // Stand-alone embedded interfaces are ok and are handled by the single-type case // in the beginning. Embedded interfaces with tilde are excluded above. If we reach - // here, we must have at least two terms in the union. - if f != nil && !f.typeSet().IsTypeSet() { + // here, we must have at least two terms in the syntactic term list (but not necessarily + // in the term list of the union's type set). + if f != nil { + tset := f.typeSet() switch { - case f.typeSet().NumMethods() != 0: + case tset.NumMethods() != 0: check.errorf(tlist[i], "cannot use %s in union (%s contains methods)", t, t) + continue case t.typ == universeComparable.Type(): check.error(tlist[i], "cannot use comparable in union") - case f.typeSet().comparable: + continue + case tset.comparable: check.errorf(tlist[i], "cannot use %s in union (%s embeds comparable)", t, t) - default: - panic("not a type set but no methods and not comparable") + continue } - continue // don't report another error for t } // Report overlapping (non-disjoint) terms such as diff --git a/src/cmd/compile/internal/types2/universe.go b/src/cmd/compile/internal/types2/universe.go index 6ee5dbdca3..11c81863a9 100644 --- a/src/cmd/compile/internal/types2/universe.go +++ b/src/cmd/compile/internal/types2/universe.go @@ -111,7 +111,7 @@ func defPredeclaredTypes() { typ := NewNamed(obj, nil, nil) // interface{} // marked as comparable - ityp := &Interface{obj: obj, complete: true, tset: &_TypeSet{true, nil, allTermlist}} + ityp := &Interface{obj: obj, complete: true, tset: &_TypeSet{nil, allTermlist, true}} typ.SetUnderlying(ityp) def(obj) diff --git a/src/cmd/compile/internal/types2/validtype.go b/src/cmd/compile/internal/types2/validtype.go index c508eadc7c..f365ad1e27 100644 --- a/src/cmd/compile/internal/types2/validtype.go +++ b/src/cmd/compile/internal/types2/validtype.go @@ -79,7 +79,7 @@ func (check *Checker) validType0(typ Type, env *tparamEnv, path []Object) typeIn // would have reported a type cycle and couldn't have been // imported in the first place. assert(t.obj.pkg == check.pkg) - t.underlying = Typ[Invalid] // t is in the current package (no race possibilty) + t.underlying = Typ[Invalid] // t is in the current package (no race possibility) // Find the starting point of the cycle and report it. for i, tn := range path { if tn == t.obj { diff --git a/src/cmd/compile/internal/walk/closure.go b/src/cmd/compile/internal/walk/closure.go index 4d1c5621fe..cd922c98c4 100644 --- a/src/cmd/compile/internal/walk/closure.go +++ b/src/cmd/compile/internal/walk/closure.go @@ -235,15 +235,7 @@ func methodValueWrapper(dot *ir.SelectorExpr) *ir.Name { saveLineNo := base.Pos ir.CurFunc = nil - // Set line number equal to the line number where the method is declared. - if pos := dot.Selection.Pos; pos.IsKnown() { - base.Pos = pos - } - // Note: !dot.Selection.Pos.IsKnown() happens for method expressions where - // the method is implicitly declared. The Error method of the - // built-in error type is one such method. We leave the line - // number at the use of the method expression in this - // case. See issue 29389. + base.Pos = base.AutogeneratedPos tfn := ir.NewFuncType(base.Pos, nil, typecheck.NewFuncParams(t0.Params(), true), diff --git a/src/cmd/dist/test.go b/src/cmd/dist/test.go index 4b67565430..d9eb9c3862 100644 --- a/src/cmd/dist/test.go +++ b/src/cmd/dist/test.go @@ -1119,7 +1119,7 @@ func (t *tester) cgoTest(dt *distTest) error { // Skip internal linking cases on arm64 to support GCC-9.4 and above. // See issue #39466. - skipInternalLink := goarch == "arm64" && goos != "windows" + skipInternalLink := goarch == "arm64" && goos != "darwin" if t.internalLink() && !skipInternalLink { cmd := t.addCmd(dt, "misc/cgo/test", t.goTest(), "-tags=internal") diff --git a/src/cmd/go/alldocs.go b/src/cmd/go/alldocs.go index 13a3f00d6f..420529b1a9 100644 --- a/src/cmd/go/alldocs.go +++ b/src/cmd/go/alldocs.go @@ -177,14 +177,6 @@ // directory, but it is not accessed. When -modfile is specified, an // alternate go.sum file is also used: its path is derived from the // -modfile flag by trimming the ".mod" extension and appending ".sum". -// -workfile file -// in module aware mode, use the given go.work file as a workspace file. -// By default or when -workfile is "auto", the go command searches for a -// file named go.work in the current directory and then containing directories -// until one is found. If a valid go.work file is found, the modules -// specified will collectively be used as the main modules. If -workfile -// is "off", or a go.work file is not found in "auto" mode, workspace -// mode is disabled. // -overlay file // read a JSON config file that provides an overlay for build operations. // The file is a JSON struct with a single field, named 'Replace', that @@ -1364,7 +1356,7 @@ // // Workspace maintenance // -// Go workspace provides access to operations on workspaces. +// Work provides access to operations on workspaces. // // Note that support for workspaces is built into many other commands, not // just 'go work'. @@ -1372,6 +1364,12 @@ // See 'go help modules' for information about Go's module system of which // workspaces are a part. // +// See https://go.dev/ref/mod#workspaces for an in-depth reference on +// workspaces. +// +// See https://go.dev/doc/tutorial/workspaces for an introductory +// tutorial on workspaces. +// // A workspace is specified by a go.work file that specifies a set of // module directories with the "use" directive. These modules are used as // root modules by the go command for builds and related operations. A @@ -1493,9 +1491,8 @@ // Version string // } // -// See the workspaces design proposal at -// https://go.googlesource.com/proposal/+/master/design/45713-workspace.md for -// more information. +// See the workspaces reference at https://go.dev/ref/mod#workspaces +// for more information. // // // Initialize workspace file @@ -1515,6 +1512,9 @@ // Each argument path is added to a use directive in the go.work file. The // current go version will also be listed in the go.work file. // +// See the workspaces reference at https://go.dev/ref/mod#workspaces +// for more information. +// // // Sync workspace build list to modules // @@ -1538,12 +1538,15 @@ // build list's version of each module is always the same or higher than // that in each workspace module. // +// See the workspaces reference at https://go.dev/ref/mod#workspaces +// for more information. +// // // Add modules to workspace file // // Usage: // -// go work use [-r] [moddirs] +// go work use [-r] moddirs // // Use provides a command-line interface for adding // directories, optionally recursively, to a go.work file. @@ -1557,6 +1560,9 @@ // were specified as arguments: namely, use directives will be added for // directories that exist, and removed for directories that do not exist. // +// See the workspaces reference at https://go.dev/ref/mod#workspaces +// for more information. +// // // Compile and run Go program // @@ -2075,6 +2081,14 @@ // GOVCS // Lists version control commands that may be used with matching servers. // See 'go help vcs'. +// GOWORK +// In module aware mode, use the given go.work file as a workspace file. +// By default or when GOWORK is "auto", the go command searches for a +// file named go.work in the current directory and then containing directories +// until one is found. If a valid go.work file is found, the modules +// specified will collectively be used as the main modules. If GOWORK +// is "off", or a go.work file is not found in "auto" mode, workspace +// mode is disabled. // // Environment variables for use with cgo: // diff --git a/src/cmd/go/internal/base/flag.go b/src/cmd/go/internal/base/flag.go index 2c72c7e562..120420a126 100644 --- a/src/cmd/go/internal/base/flag.go +++ b/src/cmd/go/internal/base/flag.go @@ -62,13 +62,6 @@ func AddModFlag(flags *flag.FlagSet) { flags.Var(explicitStringFlag{value: &cfg.BuildMod, explicit: &cfg.BuildModExplicit}, "mod", "") } -// AddWorkfileFlag adds the workfile flag to the flag set. It enables workspace -// mode for commands that support it by resetting the cfg.WorkFile variable -// to "" (equivalent to auto) rather than off. -func AddWorkfileFlag(flags *flag.FlagSet) { - flags.Var(explicitStringFlag{value: &cfg.WorkFile, explicit: &cfg.WorkFileExplicit}, "workfile", "") -} - // AddModCommonFlags adds the module-related flags common to build commands // and 'go mod' subcommands. func AddModCommonFlags(flags *flag.FlagSet) { diff --git a/src/cmd/go/internal/cfg/cfg.go b/src/cmd/go/internal/cfg/cfg.go index 7f68d7bb62..deab3dddd0 100644 --- a/src/cmd/go/internal/cfg/cfg.go +++ b/src/cmd/go/internal/cfg/cfg.go @@ -49,10 +49,8 @@ var ( BuildWork bool // -work flag BuildX bool // -x flag - ModCacheRW bool // -modcacherw flag - ModFile string // -modfile flag - WorkFile string // -workfile flag - WorkFileExplicit bool // whether -workfile was set explicitly + ModCacheRW bool // -modcacherw flag + ModFile string // -modfile flag CmdName string // "build", "install", "list", "mod tidy", etc. diff --git a/src/cmd/go/internal/envcmd/env.go b/src/cmd/go/internal/envcmd/env.go index e56dd8223f..c1adf8cef4 100644 --- a/src/cmd/go/internal/envcmd/env.go +++ b/src/cmd/go/internal/envcmd/env.go @@ -154,6 +154,10 @@ func ExtraEnvVars() []cfg.EnvVar { } modload.InitWorkfile() gowork := modload.WorkFilePath() + // As a special case, if a user set off explicitly, report that in GOWORK. + if cfg.Getenv("GOWORK") == "off" { + gowork = "off" + } return []cfg.EnvVar{ {Name: "GOMOD", Value: gomod}, {Name: "GOWORK", Value: gowork}, diff --git a/src/cmd/go/internal/help/helpdoc.go b/src/cmd/go/internal/help/helpdoc.go index d1eaad1c12..28ddaac8f1 100644 --- a/src/cmd/go/internal/help/helpdoc.go +++ b/src/cmd/go/internal/help/helpdoc.go @@ -545,6 +545,14 @@ General-purpose environment variables: GOVCS Lists version control commands that may be used with matching servers. See 'go help vcs'. + GOWORK + In module aware mode, use the given go.work file as a workspace file. + By default or when GOWORK is "auto", the go command searches for a + file named go.work in the current directory and then containing directories + until one is found. If a valid go.work file is found, the modules + specified will collectively be used as the main modules. If GOWORK + is "off", or a go.work file is not found in "auto" mode, workspace + mode is disabled. Environment variables for use with cgo: diff --git a/src/cmd/go/internal/list/list.go b/src/cmd/go/internal/list/list.go index d9a7078ccf..8be9211935 100644 --- a/src/cmd/go/internal/list/list.go +++ b/src/cmd/go/internal/list/list.go @@ -316,7 +316,6 @@ For more about modules, see https://golang.org/ref/mod. func init() { CmdList.Run = runList // break init cycle work.AddBuildFlags(CmdList, work.DefaultBuildFlags) - base.AddWorkfileFlag(&CmdList.Flag) } var ( diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go index aba1dfd1c1..1a510b86c7 100644 --- a/src/cmd/go/internal/load/pkg.go +++ b/src/cmd/go/internal/load/pkg.go @@ -825,11 +825,11 @@ func loadPackageData(ctx context.Context, path, parentPath, parentDir, parentRoo } r := resolvedImportCache.Do(importKey, func() any { var r resolvedImport - if build.IsLocalImport(path) { + if cfg.ModulesEnabled { + r.dir, r.path, r.err = modload.Lookup(parentPath, parentIsStd, path) + } else if build.IsLocalImport(path) { r.dir = filepath.Join(parentDir, path) r.path = dirToImportPath(r.dir) - } else if cfg.ModulesEnabled { - r.dir, r.path, r.err = modload.Lookup(parentPath, parentIsStd, path) } else if mode&ResolveImport != 0 { // We do our own path resolution, because we want to // find out the key to use in packageCache without the @@ -1119,6 +1119,7 @@ func dirAndRoot(path string, dir, root string) (string, string) { } if !str.HasFilePathPrefix(dir, root) || len(dir) <= len(root) || dir[len(root)] != filepath.Separator || path != "command-line-arguments" && !build.IsLocalImport(path) && filepath.Join(root, path) != dir { + debug.PrintStack() base.Fatalf("unexpected directory layout:\n"+ " import path: %s\n"+ " root: %s\n"+ @@ -2235,13 +2236,17 @@ func (p *Package) setBuildInfo() { var debugModFromModinfo func(*modinfo.ModulePublic) *debug.Module debugModFromModinfo = func(mi *modinfo.ModulePublic) *debug.Module { + version := mi.Version + if version == "" { + version = "(devel)" + } dm := &debug.Module{ Path: mi.Path, - Version: mi.Version, + Version: version, } if mi.Replace != nil { dm.Replace = debugModFromModinfo(mi.Replace) - } else { + } else if mi.Version != "" { dm.Sum = modfetch.Sum(module.Version{Path: mi.Path, Version: mi.Version}) } return dm @@ -2424,12 +2429,7 @@ func (p *Package) setBuildInfo() { appendSetting("vcs.modified", strconv.FormatBool(st.Uncommitted)) } - text, err := info.MarshalText() - if err != nil { - setPkgErrorf("error formatting build info: %v", err) - return - } - p.Internal.BuildInfo = string(text) + p.Internal.BuildInfo = info.String() } // SafeArg reports whether arg is a "safe" command-line argument, diff --git a/src/cmd/go/internal/modcmd/download.go b/src/cmd/go/internal/modcmd/download.go index 6b8a010fd9..5bc6cbc4bb 100644 --- a/src/cmd/go/internal/modcmd/download.go +++ b/src/cmd/go/internal/modcmd/download.go @@ -70,7 +70,6 @@ func init() { // TODO(jayconrod): https://golang.org/issue/35849 Apply -x to other 'go mod' commands. cmdDownload.Flag.BoolVar(&cfg.BuildX, "x", false, "") base.AddModCommonFlags(&cmdDownload.Flag) - base.AddWorkfileFlag(&cmdDownload.Flag) } type moduleJSON struct { diff --git a/src/cmd/go/internal/modcmd/graph.go b/src/cmd/go/internal/modcmd/graph.go index 9b6aa1fb14..9568c65740 100644 --- a/src/cmd/go/internal/modcmd/graph.go +++ b/src/cmd/go/internal/modcmd/graph.go @@ -42,7 +42,6 @@ var ( func init() { cmdGraph.Flag.Var(&graphGo, "go", "") base.AddModCommonFlags(&cmdGraph.Flag) - base.AddWorkfileFlag(&cmdGraph.Flag) } func runGraph(ctx context.Context, cmd *base.Command, args []string) { diff --git a/src/cmd/go/internal/modcmd/verify.go b/src/cmd/go/internal/modcmd/verify.go index 3f0c005d5d..459bf5d070 100644 --- a/src/cmd/go/internal/modcmd/verify.go +++ b/src/cmd/go/internal/modcmd/verify.go @@ -39,7 +39,6 @@ See https://golang.org/ref/mod#go-mod-verify for more about 'go mod verify'. func init() { base.AddModCommonFlags(&cmdVerify.Flag) - base.AddWorkfileFlag(&cmdVerify.Flag) } func runVerify(ctx context.Context, cmd *base.Command, args []string) { diff --git a/src/cmd/go/internal/modcmd/why.go b/src/cmd/go/internal/modcmd/why.go index d8355cca95..2d3f1eb05b 100644 --- a/src/cmd/go/internal/modcmd/why.go +++ b/src/cmd/go/internal/modcmd/why.go @@ -59,7 +59,6 @@ var ( func init() { cmdWhy.Run = runWhy // break init cycle base.AddModCommonFlags(&cmdWhy.Flag) - base.AddWorkfileFlag(&cmdWhy.Flag) } func runWhy(ctx context.Context, cmd *base.Command, args []string) { diff --git a/src/cmd/go/internal/modfetch/coderepo.go b/src/cmd/go/internal/modfetch/coderepo.go index 2206c7c840..dfaf16def6 100644 --- a/src/cmd/go/internal/modfetch/coderepo.go +++ b/src/cmd/go/internal/modfetch/coderepo.go @@ -305,17 +305,46 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e // // (If the version is +incompatible, then the go.mod file must not exist: // +incompatible is not an ongoing opt-out from semantic import versioning.) - var canUseIncompatible func() bool - canUseIncompatible = func() bool { - var ok bool - if r.codeDir == "" && r.pathMajor == "" { + incompatibleOk := map[string]bool{} + canUseIncompatible := func(v string) bool { + if r.codeDir != "" || r.pathMajor != "" { + // A non-empty codeDir indicates a module within a subdirectory, + // which necessarily has a go.mod file indicating the module boundary. + // A non-empty pathMajor indicates a module path with a major-version + // suffix, which must match. + return false + } + + ok, seen := incompatibleOk[""] + if !seen { _, errGoMod := r.code.ReadFile(info.Name, "go.mod", codehost.MaxGoMod) - if errGoMod != nil { - ok = true + ok = (errGoMod != nil) + incompatibleOk[""] = ok + } + if !ok { + // A go.mod file exists at the repo root. + return false + } + + // Per https://go.dev/issue/51324, previous versions of the 'go' command + // didn't always check for go.mod files in subdirectories, so if the user + // requests a +incompatible version explicitly, we should continue to allow + // it. Otherwise, if vN/go.mod exists, expect that release tags for that + // major version are intended for the vN module. + if v != "" && !strings.HasSuffix(statVers, "+incompatible") { + major := semver.Major(v) + ok, seen = incompatibleOk[major] + if !seen { + _, errGoModSub := r.code.ReadFile(info.Name, path.Join(major, "go.mod"), codehost.MaxGoMod) + ok = (errGoModSub != nil) + incompatibleOk[major] = ok + } + if !ok { + return false } } - canUseIncompatible = func() bool { return ok } - return ok + + return true } // checkCanonical verifies that the canonical version v is compatible with the @@ -367,7 +396,7 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e base := strings.TrimSuffix(v, "+incompatible") var errIncompatible error if !module.MatchPathMajor(base, r.pathMajor) { - if canUseIncompatible() { + if canUseIncompatible(base) { v = base + "+incompatible" } else { if r.pathMajor != "" { @@ -495,7 +524,7 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e // Save the highest non-retracted canonical tag for the revision. // If we don't find a better match, we'll use it as the canonical version. if tagIsCanonical && semver.Compare(highestCanonical, v) < 0 && !isRetracted(v) { - if module.MatchPathMajor(v, r.pathMajor) || canUseIncompatible() { + if module.MatchPathMajor(v, r.pathMajor) || canUseIncompatible(v) { highestCanonical = v } } @@ -513,12 +542,12 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e // retracted versions. allowedMajor := func(major string) func(v string) bool { return func(v string) bool { - return (major == "" || semver.Major(v) == major) && !isRetracted(v) + return ((major == "" && canUseIncompatible(v)) || semver.Major(v) == major) && !isRetracted(v) } } if pseudoBase == "" { var tag string - if r.pseudoMajor != "" || canUseIncompatible() { + if r.pseudoMajor != "" || canUseIncompatible("") { tag, _ = r.code.RecentTag(info.Name, tagPrefix, allowedMajor(r.pseudoMajor)) } else { // Allow either v1 or v0, but not incompatible higher versions. diff --git a/src/cmd/go/internal/modfetch/coderepo_test.go b/src/cmd/go/internal/modfetch/coderepo_test.go index d98ea87da2..bb9268adb8 100644 --- a/src/cmd/go/internal/modfetch/coderepo_test.go +++ b/src/cmd/go/internal/modfetch/coderepo_test.go @@ -458,6 +458,54 @@ var codeRepoTests = []codeRepoTest{ rev: "v3.0.0-devel", err: `resolves to version v0.1.1-0.20220203155313-d59622f6e4d7 (v3.0.0-devel is not a tag)`, }, + + // If v2/go.mod exists, then we should prefer to match the "v2" + // pseudo-versions to the nested module, and resolve the module in the parent + // directory to only compatible versions. + // + // However (https://go.dev/issue/51324), previous versions of the 'go' command + // didn't always do so, so if the user explicitly requests a +incompatible + // version (as would be present in an existing go.mod file), we should + // continue to allow it. + { + vcs: "git", + path: "vcs-test.golang.org/git/v2sub.git", + rev: "80beb17a1603", + version: "v0.0.0-20220222205507-80beb17a1603", + name: "80beb17a16036f17a5aedd1bb5bd6d407b3c6dc5", + short: "80beb17a1603", + time: time.Date(2022, 2, 22, 20, 55, 7, 0, time.UTC), + }, + { + vcs: "git", + path: "vcs-test.golang.org/git/v2sub.git", + rev: "v2.0.0", + err: `module contains a go.mod file, so module path must match major version ("vcs-test.golang.org/git/v2sub.git/v2")`, + }, + { + vcs: "git", + path: "vcs-test.golang.org/git/v2sub.git", + rev: "v2.0.1-0.20220222205507-80beb17a1603", + err: `module contains a go.mod file, so module path must match major version ("vcs-test.golang.org/git/v2sub.git/v2")`, + }, + { + vcs: "git", + path: "vcs-test.golang.org/git/v2sub.git", + rev: "v2.0.0+incompatible", + version: "v2.0.0+incompatible", + name: "5fcd3eaeeb391d399f562fd45a50dac9fc34ae8b", + short: "5fcd3eaeeb39", + time: time.Date(2022, 2, 22, 20, 53, 33, 0, time.UTC), + }, + { + vcs: "git", + path: "vcs-test.golang.org/git/v2sub.git", + rev: "v2.0.1-0.20220222205507-80beb17a1603+incompatible", + version: "v2.0.1-0.20220222205507-80beb17a1603+incompatible", + name: "80beb17a16036f17a5aedd1bb5bd6d407b3c6dc5", + short: "80beb17a1603", + time: time.Date(2022, 2, 22, 20, 55, 7, 0, time.UTC), + }, } func TestCodeRepo(t *testing.T) { diff --git a/src/cmd/go/internal/modfetch/fetch.go b/src/cmd/go/internal/modfetch/fetch.go index f5423b48ad..21d5f54688 100644 --- a/src/cmd/go/internal/modfetch/fetch.go +++ b/src/cmd/go/internal/modfetch/fetch.go @@ -319,7 +319,7 @@ func downloadZip(ctx context.Context, mod module.Version, zipfile string) (err e // // If the hash does not match go.sum (or the sumdb if enabled), hashZip returns // an error and does not write ziphashfile. -func hashZip(mod module.Version, zipfile, ziphashfile string) error { +func hashZip(mod module.Version, zipfile, ziphashfile string) (err error) { hash, err := dirhash.HashZip(zipfile, dirhash.DefaultHash) if err != nil { return err @@ -331,16 +331,17 @@ func hashZip(mod module.Version, zipfile, ziphashfile string) error { if err != nil { return err } + defer func() { + if closeErr := hf.Close(); err == nil && closeErr != nil { + err = closeErr + } + }() if err := hf.Truncate(int64(len(hash))); err != nil { return err } if _, err := hf.WriteAt([]byte(hash), 0); err != nil { return err } - if err := hf.Close(); err != nil { - return err - } - return nil } diff --git a/src/cmd/go/internal/modload/import.go b/src/cmd/go/internal/modload/import.go index 812e48a156..4862f625b4 100644 --- a/src/cmd/go/internal/modload/import.go +++ b/src/cmd/go/internal/modload/import.go @@ -248,12 +248,26 @@ func (e *invalidImportError) Unwrap() error { // return the module, its root directory, and a list of other modules that // lexically could have provided the package but did not. func importFromModules(ctx context.Context, path string, rs *Requirements, mg *ModuleGraph) (m module.Version, dir string, altMods []module.Version, err error) { + invalidf := func(format string, args ...interface{}) (module.Version, string, []module.Version, error) { + return module.Version{}, "", nil, &invalidImportError{ + importPath: path, + err: fmt.Errorf(format, args...), + } + } + if strings.Contains(path, "@") { - return module.Version{}, "", nil, fmt.Errorf("import path should not have @version") + return invalidf("import path %q should not have @version", path) } if build.IsLocalImport(path) { - return module.Version{}, "", nil, fmt.Errorf("relative import not supported") + return invalidf("%q is relative, but relative import paths are not supported in module mode", path) } + if filepath.IsAbs(path) { + return invalidf("%q is not a package path; see 'go help packages'", path) + } + if search.IsMetaPackage(path) { + return invalidf("%q is not an importable package; see 'go help packages'", path) + } + if path == "C" { // There's no directory for import "C". return module.Version{}, "", nil, nil diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go index 23f4efd02a..f960edd251 100644 --- a/src/cmd/go/internal/modload/init.go +++ b/src/cmd/go/internal/modload/init.go @@ -288,20 +288,25 @@ func BinDir() string { // operate in workspace mode. It should not be called by other commands, // for example 'go mod tidy', that don't operate in workspace mode. func InitWorkfile() { - switch cfg.WorkFile { + if RootMode == NoRoot { + workFilePath = "" + return + } + + switch gowork := cfg.Getenv("GOWORK"); gowork { case "off": workFilePath = "" case "", "auto": workFilePath = findWorkspaceFile(base.Cwd()) default: - if !filepath.IsAbs(cfg.WorkFile) { - base.Fatalf("the path provided to -workfile must be an absolute path") + if !filepath.IsAbs(gowork) { + base.Fatalf("the path provided to GOWORK must be an absolute path") } - workFilePath = cfg.WorkFile + workFilePath = gowork } } -// WorkFilePath returns the path of the go.work file, or "" if not in +// WorkFilePath returns the absolute path of the go.work file, or "" if not in // workspace mode. WorkFilePath must be called after InitWorkfile. func WorkFilePath() string { return workFilePath @@ -1033,11 +1038,25 @@ func makeMainModules(ms []module.Version, rootDirs []string, modFiles []*modfile for _, r := range modFiles[i].Replace { if replacedByWorkFile[r.Old.Path] { continue - } else if prev, ok := replacements[r.Old]; ok && !curModuleReplaces[r.Old] && prev != r.New { - base.Fatalf("go: conflicting replacements for %v:\n\t%v\n\t%v\nuse \"go work edit -replace %v=[override]\" to resolve", r.Old, prev, r.New, r.Old) + } + var newV module.Version = r.New + if WorkFilePath() != "" && newV.Version == "" && !filepath.IsAbs(newV.Path) { + // Since we are in a workspace, we may be loading replacements from + // multiple go.mod files. Relative paths in those replacement are + // relative to the go.mod file, not the workspace, so the same string + // may refer to two different paths and different strings may refer to + // the same path. Convert them all to be absolute instead. + // + // (We could do this outside of a workspace too, but it would mean that + // replacement paths in error strings needlessly differ from what's in + // the go.mod file.) + newV.Path = filepath.Join(rootDirs[i], newV.Path) + } + if prev, ok := replacements[r.Old]; ok && !curModuleReplaces[r.Old] && prev != newV { + base.Fatalf("go: conflicting replacements for %v:\n\t%v\n\t%v\nuse \"go work edit -replace %v=[override]\" to resolve", r.Old, prev, newV, r.Old) } curModuleReplaces[r.Old] = true - replacements[r.Old] = r.New + replacements[r.Old] = newV v, ok := mainModules.highestReplaced[r.Old.Path] if !ok || semver.Compare(r.Old.Version, v) > 0 { @@ -1095,7 +1114,7 @@ func setDefaultBuildMod() { if inWorkspaceMode() && cfg.BuildMod != "readonly" { base.Fatalf("go: -mod may only be set to readonly when in workspace mode, but it is set to %q"+ "\n\tRemove the -mod flag to use the default readonly value,"+ - "\n\tor set -workfile=off to disable workspace mode.", cfg.BuildMod) + "\n\tor set GOWORK=off to disable workspace mode.", cfg.BuildMod) } // Don't override an explicit '-mod=' argument. return diff --git a/src/cmd/go/internal/modload/load.go b/src/cmd/go/internal/modload/load.go index 617b634d26..d4847efb98 100644 --- a/src/cmd/go/internal/modload/load.go +++ b/src/cmd/go/internal/modload/load.go @@ -479,7 +479,11 @@ func matchLocalDirs(ctx context.Context, modRoots []string, m *search.Match, rs } if !found && search.InDir(absDir, cfg.GOROOTsrc) == "" && pathInModuleCache(ctx, absDir, rs) == "" { m.Dirs = []string{} - m.AddError(fmt.Errorf("directory prefix %s outside available modules", base.ShortPath(absDir))) + scope := "main module or its selected dependencies" + if inWorkspaceMode() { + scope = "modules listed in go.work or their selected dependencies" + } + m.AddError(fmt.Errorf("directory prefix %s does not contain %s", base.ShortPath(absDir), scope)) return } } @@ -601,7 +605,11 @@ func resolveLocalPackage(ctx context.Context, dir string, rs *Requirements) (str pkg := pathInModuleCache(ctx, absDir, rs) if pkg == "" { - return "", fmt.Errorf("directory %s outside available modules", base.ShortPath(absDir)) + scope := "main module or its selected dependencies" + if inWorkspaceMode() { + scope = "modules listed in go.work or their selected dependencies" + } + return "", fmt.Errorf("directory %s outside %s", base.ShortPath(absDir), scope) } return pkg, nil } @@ -1667,24 +1675,6 @@ func (ld *loader) preloadRootModules(ctx context.Context, rootPkgs []string) (ch // load loads an individual package. func (ld *loader) load(ctx context.Context, pkg *loadPkg) { - if strings.Contains(pkg.path, "@") { - // Leave for error during load. - return - } - if build.IsLocalImport(pkg.path) || filepath.IsAbs(pkg.path) { - // Leave for error during load. - // (Module mode does not allow local imports.) - return - } - - if search.IsMetaPackage(pkg.path) { - pkg.err = &invalidImportError{ - importPath: pkg.path, - err: fmt.Errorf("%q is not an importable package; see 'go help packages'", pkg.path), - } - return - } - var mg *ModuleGraph if ld.requirements.pruning == unpruned { var err error diff --git a/src/cmd/go/internal/modload/modfile.go b/src/cmd/go/internal/modload/modfile.go index 627cf1dbc0..75c278a7df 100644 --- a/src/cmd/go/internal/modload/modfile.go +++ b/src/cmd/go/internal/modload/modfile.go @@ -802,7 +802,7 @@ var latestVersionIgnoringRetractionsCache par.Cache // path → queryLatestVersi // an absolute path or a relative path starting with a '.' or '..' // path component. func ToDirectoryPath(path string) string { - if modfile.IsDirectoryPath(path) { + if path == "." || modfile.IsDirectoryPath(path) { return path } // The path is not a relative path or an absolute path, so make it relative diff --git a/src/cmd/go/internal/run/run.go b/src/cmd/go/internal/run/run.go index c4b70b64fe..312b49ef5d 100644 --- a/src/cmd/go/internal/run/run.go +++ b/src/cmd/go/internal/run/run.go @@ -65,7 +65,6 @@ func init() { CmdRun.Run = runRun // break init loop work.AddBuildFlags(CmdRun, work.DefaultBuildFlags) - base.AddWorkfileFlag(&CmdRun.Flag) CmdRun.Flag.Var((*base.StringsFlag)(&work.ExecCmd), "exec", "") } @@ -74,8 +73,6 @@ func printStderr(args ...any) (int, error) { } func runRun(ctx context.Context, cmd *base.Command, args []string) { - modload.InitWorkfile() - if shouldUseOutsideModuleMode(args) { // Set global module flags for 'go run cmd@version'. // This must be done before modload.Init, but we need to call work.BuildInit @@ -85,7 +82,10 @@ func runRun(ctx context.Context, cmd *base.Command, args []string) { modload.RootMode = modload.NoRoot modload.AllowMissingModuleImports() modload.Init() + } else { + modload.InitWorkfile() } + work.BuildInit() var b work.Builder b.Init() diff --git a/src/cmd/go/internal/test/testflag.go b/src/cmd/go/internal/test/testflag.go index b9d1ec91ff..c046caca25 100644 --- a/src/cmd/go/internal/test/testflag.go +++ b/src/cmd/go/internal/test/testflag.go @@ -28,7 +28,6 @@ import ( func init() { work.AddBuildFlags(CmdTest, work.OmitVFlag) - base.AddWorkfileFlag(&CmdTest.Flag) cf := CmdTest.Flag cf.BoolVar(&testC, "c", false, "") diff --git a/src/cmd/go/internal/vcs/vcs.go b/src/cmd/go/internal/vcs/vcs.go index fd521b2eb1..2acabf7aaf 100644 --- a/src/cmd/go/internal/vcs/vcs.go +++ b/src/cmd/go/internal/vcs/vcs.go @@ -312,7 +312,7 @@ func gitStatus(vcsGit *Cmd, rootDir string) (Status, error) { // uncommitted files and skip tagging revision / committime. var rev string var commitTime time.Time - out, err = vcsGit.runOutputVerboseOnly(rootDir, "show -s --no-show-signature --format=%H:%ct") + out, err = vcsGit.runOutputVerboseOnly(rootDir, "-c log.showsignature=false show -s --format=%H:%ct") if err != nil && !uncommitted { return Status{}, err } else if err == nil { diff --git a/src/cmd/go/internal/version/version.go b/src/cmd/go/internal/version/version.go index 52502e95c6..1c0eb5407d 100644 --- a/src/cmd/go/internal/version/version.go +++ b/src/cmd/go/internal/version/version.go @@ -6,7 +6,6 @@ package version import ( - "bytes" "context" "debug/buildinfo" "errors" @@ -156,12 +155,8 @@ func scanFile(file string, info fs.FileInfo, mustPrint bool) { fmt.Printf("%s: %s\n", file, bi.GoVersion) bi.GoVersion = "" // suppress printing go version again - mod, err := bi.MarshalText() - if err != nil { - fmt.Fprintf(os.Stderr, "%s: formatting build info: %v\n", file, err) - return - } + mod := bi.String() if *versionM && len(mod) > 0 { - fmt.Printf("\t%s\n", bytes.ReplaceAll(mod[:len(mod)-1], []byte("\n"), []byte("\n\t"))) + fmt.Printf("\t%s\n", strings.ReplaceAll(mod[:len(mod)-1], "\n", "\n\t")) } } diff --git a/src/cmd/go/internal/vet/vet.go b/src/cmd/go/internal/vet/vet.go index 88b3c570a0..d3e0dd8116 100644 --- a/src/cmd/go/internal/vet/vet.go +++ b/src/cmd/go/internal/vet/vet.go @@ -13,6 +13,7 @@ import ( "cmd/go/internal/base" "cmd/go/internal/cfg" "cmd/go/internal/load" + "cmd/go/internal/modload" "cmd/go/internal/trace" "cmd/go/internal/work" ) @@ -54,6 +55,7 @@ See also: go fmt, go fix. func runVet(ctx context.Context, cmd *base.Command, args []string) { vetFlags, pkgArgs := vetFlags(args) + modload.InitWorkfile() // The vet command does custom flag processing; initialize workspaces after that. if cfg.DebugTrace != "" { var close func() error diff --git a/src/cmd/go/internal/work/build.go b/src/cmd/go/internal/work/build.go index 1c278d3d99..0b5848a77d 100644 --- a/src/cmd/go/internal/work/build.go +++ b/src/cmd/go/internal/work/build.go @@ -130,14 +130,6 @@ and test commands: directory, but it is not accessed. When -modfile is specified, an alternate go.sum file is also used: its path is derived from the -modfile flag by trimming the ".mod" extension and appending ".sum". - -workfile file - in module aware mode, use the given go.work file as a workspace file. - By default or when -workfile is "auto", the go command searches for a - file named go.work in the current directory and then containing directories - until one is found. If a valid go.work file is found, the modules - specified will collectively be used as the main modules. If -workfile - is "off", or a go.work file is not found in "auto" mode, workspace - mode is disabled. -overlay file read a JSON config file that provides an overlay for build operations. The file is a JSON struct with a single field, named 'Replace', that @@ -217,7 +209,6 @@ func init() { AddBuildFlags(CmdBuild, DefaultBuildFlags) AddBuildFlags(CmdInstall, DefaultBuildFlags) - base.AddWorkfileFlag(&CmdBuild.Flag) } // Note that flags consulted by other parts of the code diff --git a/src/cmd/go/internal/work/exec.go b/src/cmd/go/internal/work/exec.go index 48a74458bd..ac80f503cd 100644 --- a/src/cmd/go/internal/work/exec.go +++ b/src/cmd/go/internal/work/exec.go @@ -2013,6 +2013,7 @@ func (b *Builder) showOutput(a *Action, dir, desc, out string) { if reldir := base.ShortPath(dir); reldir != dir { suffix = strings.ReplaceAll(suffix, " "+dir, " "+reldir) suffix = strings.ReplaceAll(suffix, "\n"+dir, "\n"+reldir) + suffix = strings.ReplaceAll(suffix, "\n\t"+dir, "\n\t"+reldir) } suffix = strings.ReplaceAll(suffix, " "+b.WorkDir, " $WORK") diff --git a/src/cmd/go/internal/workcmd/edit.go b/src/cmd/go/internal/workcmd/edit.go index e7b1b13271..1478c19389 100644 --- a/src/cmd/go/internal/workcmd/edit.go +++ b/src/cmd/go/internal/workcmd/edit.go @@ -84,9 +84,8 @@ writing it back to go.mod. The JSON output corresponds to these Go types: Version string } -See the workspaces design proposal at -https://go.googlesource.com/proposal/+/master/design/45713-workspace.md for -more information. +See the workspaces reference at https://go.dev/ref/mod#workspaces +for more information. `, } @@ -110,8 +109,6 @@ func init() { cmdEdit.Flag.Var(flagFunc(flagEditworkDropUse), "dropuse", "") cmdEdit.Flag.Var(flagFunc(flagEditworkReplace), "replace", "") cmdEdit.Flag.Var(flagFunc(flagEditworkDropReplace), "dropreplace", "") - - base.AddWorkfileFlag(&cmdEdit.Flag) } func runEditwork(ctx context.Context, cmd *base.Command, args []string) { @@ -137,7 +134,7 @@ func runEditwork(ctx context.Context, cmd *base.Command, args []string) { } if gowork == "" { - base.Fatalf("go: no go.work file found\n\t(run 'go work init' first or specify path using -workfile flag)") + base.Fatalf("go: no go.work file found\n\t(run 'go work init' first or specify path using GOWORK environment variable)") } anyFlags := diff --git a/src/cmd/go/internal/workcmd/init.go b/src/cmd/go/internal/workcmd/init.go index cefecee832..c2513bac35 100644 --- a/src/cmd/go/internal/workcmd/init.go +++ b/src/cmd/go/internal/workcmd/init.go @@ -27,13 +27,14 @@ modules will be created. Each argument path is added to a use directive in the go.work file. The current go version will also be listed in the go.work file. +See the workspaces reference at https://go.dev/ref/mod#workspaces +for more information. `, Run: runInit, } func init() { base.AddModCommonFlags(&cmdInit.Flag) - base.AddWorkfileFlag(&cmdInit.Flag) } func runInit(ctx context.Context, cmd *base.Command, args []string) { @@ -41,12 +42,10 @@ func runInit(ctx context.Context, cmd *base.Command, args []string) { modload.ForceUseModules = true - // TODO(matloob): support using the -workfile path - // To do that properly, we'll have to make the module directories - // make dirs relative to workFile path before adding the paths to - // the directory entries - - workFile := filepath.Join(base.Cwd(), "go.work") + workFile := modload.WorkFilePath() + if workFile == "" { + workFile = filepath.Join(base.Cwd(), "go.work") + } modload.CreateWorkFile(ctx, workFile, args) } diff --git a/src/cmd/go/internal/workcmd/sync.go b/src/cmd/go/internal/workcmd/sync.go index 948fc5d370..7712eb6b6b 100644 --- a/src/cmd/go/internal/workcmd/sync.go +++ b/src/cmd/go/internal/workcmd/sync.go @@ -33,20 +33,22 @@ if the dependency module's version is not already the same as the build list's version. Note that Minimal Version Selection guarantees that the build list's version of each module is always the same or higher than that in each workspace module. + +See the workspaces reference at https://go.dev/ref/mod#workspaces +for more information. `, Run: runSync, } func init() { base.AddModCommonFlags(&cmdSync.Flag) - base.AddWorkfileFlag(&cmdSync.Flag) } func runSync(ctx context.Context, cmd *base.Command, args []string) { modload.ForceUseModules = true modload.InitWorkfile() if modload.WorkFilePath() == "" { - base.Fatalf("go: no go.work file found\n\t(run 'go work init' first or specify path using -workfile flag)") + base.Fatalf("go: no go.work file found\n\t(run 'go work init' first or specify path using GOWORK environment variable)") } workGraph := modload.LoadModGraph(ctx, "") diff --git a/src/cmd/go/internal/workcmd/use.go b/src/cmd/go/internal/workcmd/use.go index d3bc1b7d55..e20041f79f 100644 --- a/src/cmd/go/internal/workcmd/use.go +++ b/src/cmd/go/internal/workcmd/use.go @@ -10,14 +10,17 @@ import ( "cmd/go/internal/base" "cmd/go/internal/fsys" "cmd/go/internal/modload" + "cmd/go/internal/str" "context" + "errors" + "fmt" "io/fs" "os" "path/filepath" ) var cmdUse = &base.Command{ - UsageLine: "go work use [-r] [moddirs]", + UsageLine: "go work use [-r] moddirs", Short: "add modules to workspace file", Long: `Use provides a command-line interface for adding directories, optionally recursively, to a go.work file. @@ -30,6 +33,9 @@ The -r flag searches recursively for modules in the argument directories, and the use command operates as if each of the directories were specified as arguments: namely, use directives will be added for directories that exist, and removed for directories that do not exist. + +See the workspaces reference at https://go.dev/ref/mod#workspaces +for more information. `, } @@ -39,7 +45,6 @@ func init() { cmdUse.Run = runUse // break init cycle base.AddModCommonFlags(&cmdUse.Flag) - base.AddWorkfileFlag(&cmdUse.Flag) } func runUse(ctx context.Context, cmd *base.Command, args []string) { @@ -50,50 +55,40 @@ func runUse(ctx context.Context, cmd *base.Command, args []string) { gowork = modload.WorkFilePath() if gowork == "" { - base.Fatalf("go: no go.work file found\n\t(run 'go work init' first or specify path using -workfile flag)") + base.Fatalf("go: no go.work file found\n\t(run 'go work init' first or specify path using GOWORK environment variable)") } workFile, err := modload.ReadWorkFile(gowork) if err != nil { base.Fatalf("go: %v", err) } + workDir := filepath.Dir(gowork) // Absolute, since gowork itself is absolute. haveDirs := make(map[string][]string) // absolute → original(s) for _, use := range workFile.Use { - var absDir string + var abs string if filepath.IsAbs(use.Path) { - absDir = filepath.Clean(use.Path) + abs = filepath.Clean(use.Path) } else { - absDir = filepath.Join(filepath.Dir(gowork), use.Path) + abs = filepath.Join(workDir, use.Path) } - haveDirs[absDir] = append(haveDirs[absDir], use.Path) + haveDirs[abs] = append(haveDirs[abs], use.Path) } - addDirs := make(map[string]bool) - removeDirs := make(map[string]bool) + // keepDirs maps each absolute path to keep to the literal string to use for + // that path (either an absolute or a relative path), or the empty string if + // all entries for the absolute path should be removed. + keepDirs := make(map[string]string) + + // lookDir updates the entry in keepDirs for the directory dir, + // which is either absolute or relative to the current working directory + // (not necessarily the directory containing the workfile). lookDir := func(dir string) { - // If the path is absolute, try to keep it absolute. If it's relative, - // make it relative to the go.work file rather than the working directory. - absDir := dir - if !filepath.IsAbs(dir) { - absDir = filepath.Join(base.Cwd(), dir) - rel, err := filepath.Rel(filepath.Dir(gowork), absDir) - if err == nil { - // Normalize relative paths to use slashes, so that checked-in go.work - // files with relative paths within the repo are platform-independent. - dir = filepath.ToSlash(rel) - } else { - // The path can't be made relative to the go.work file, - // so it must be kept absolute instead. - dir = absDir - } - } + absDir, dir := pathRel(workDir, dir) fi, err := os.Stat(filepath.Join(absDir, "go.mod")) if err != nil { if os.IsNotExist(err) { - for _, origDir := range haveDirs[absDir] { - removeDirs[origDir] = true - } + keepDirs[absDir] = "" return } base.Errorf("go: %v", err) @@ -103,31 +98,99 @@ func runUse(ctx context.Context, cmd *base.Command, args []string) { base.Errorf("go: %v is not regular", filepath.Join(dir, "go.mod")) } - if len(haveDirs[absDir]) == 0 { - addDirs[dir] = true + if dup := keepDirs[absDir]; dup != "" && dup != dir { + base.Errorf(`go: already added "%s" as "%s"`, dir, dup) } + keepDirs[absDir] = dir } + if len(args) == 0 { + base.Fatalf("go: 'go work use' requires one or more directory arguments") + } for _, useDir := range args { - if *useR { - fsys.Walk(useDir, func(path string, info fs.FileInfo, err error) error { - if !info.IsDir() { - return nil - } - lookDir(path) - return nil - }) + if !*useR { + lookDir(useDir) continue } - lookDir(useDir) + + // Add or remove entries for any subdirectories that still exist. + err := fsys.Walk(useDir, func(path string, info fs.FileInfo, err error) error { + if !info.IsDir() { + if info.Mode()&fs.ModeSymlink != 0 { + if target, err := fsys.Stat(path); err == nil && target.IsDir() { + fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path) + } + } + return nil + } + lookDir(path) + return nil + }) + if err != nil && !errors.Is(err, os.ErrNotExist) { + base.Errorf("go: %v", err) + } + + // Remove entries for subdirectories that no longer exist. + // Because they don't exist, they will be skipped by Walk. + absArg, _ := pathRel(workDir, useDir) + for absDir, _ := range haveDirs { + if str.HasFilePathPrefix(absDir, absArg) { + if _, ok := keepDirs[absDir]; !ok { + keepDirs[absDir] = "" // Mark for deletion. + } + } + } } - for dir := range removeDirs { - workFile.DropUse(dir) - } - for dir := range addDirs { - workFile.AddUse(dir, "") + base.ExitIfErrors() + + for absDir, keepDir := range keepDirs { + nKept := 0 + for _, dir := range haveDirs[absDir] { + if dir == keepDir { // (note that dir is always non-empty) + nKept++ + } else { + workFile.DropUse(dir) + } + } + if keepDir != "" && nKept != 1 { + // If we kept more than one copy, delete them all. + // We'll recreate a unique copy with AddUse. + if nKept > 1 { + workFile.DropUse(keepDir) + } + workFile.AddUse(keepDir, "") + } } modload.UpdateWorkFile(workFile) modload.WriteWorkFile(gowork, workFile) } + +// pathRel returns the absolute and canonical forms of dir for use in a +// go.work file located in directory workDir. +// +// If dir is relative, it is intepreted relative to base.Cwd() +// and its canonical form is relative to workDir if possible. +// If dir is absolute or cannot be made relative to workDir, +// its canonical form is absolute. +// +// Canonical absolute paths are clean. +// Canonical relative paths are clean and slash-separated. +func pathRel(workDir, dir string) (abs, canonical string) { + if filepath.IsAbs(dir) { + abs = filepath.Clean(dir) + return abs, abs + } + + abs = filepath.Join(base.Cwd(), dir) + rel, err := filepath.Rel(workDir, abs) + if err != nil { + // The path can't be made relative to the go.work file, + // so it must be kept absolute instead. + return abs, abs + } + + // Normalize relative paths to use slashes, so that checked-in go.work + // files with relative paths within the repo are platform-independent. + return abs, modload.ToDirectoryPath(rel) +} diff --git a/src/cmd/go/internal/workcmd/work.go b/src/cmd/go/internal/workcmd/work.go index d3cc250231..39c81e8f5d 100644 --- a/src/cmd/go/internal/workcmd/work.go +++ b/src/cmd/go/internal/workcmd/work.go @@ -12,7 +12,7 @@ import ( var CmdWork = &base.Command{ UsageLine: "go work", Short: "workspace maintenance", - Long: `Go workspace provides access to operations on workspaces. + Long: `Work provides access to operations on workspaces. Note that support for workspaces is built into many other commands, not just 'go work'. @@ -20,6 +20,12 @@ just 'go work'. See 'go help modules' for information about Go's module system of which workspaces are a part. +See https://go.dev/ref/mod#workspaces for an in-depth reference on +workspaces. + +See https://go.dev/doc/tutorial/workspaces for an introductory +tutorial on workspaces. + A workspace is specified by a go.work file that specifies a set of module directories with the "use" directive. These modules are used as root modules by the go command for builds and related operations. A diff --git a/src/cmd/go/testdata/script/build_internal.txt b/src/cmd/go/testdata/script/build_internal.txt index 25aa18cfcb..5b786f2fbc 100644 --- a/src/cmd/go/testdata/script/build_internal.txt +++ b/src/cmd/go/testdata/script/build_internal.txt @@ -10,8 +10,10 @@ stderr 'internal' # Test internal packages outside GOROOT are respected cd ../testinternal2 +env GO111MODULE=off ! go build -v . stderr 'p\.go:3:8: use of internal package .*internal/w not allowed' +env GO111MODULE='' [gccgo] skip # gccgo does not have GOROOT cd ../testinternal diff --git a/src/cmd/go/testdata/script/mod_download_partial.txt b/src/cmd/go/testdata/script/mod_download_partial.txt index 3a02fcd747..617b1fd8e3 100644 --- a/src/cmd/go/testdata/script/mod_download_partial.txt +++ b/src/cmd/go/testdata/script/mod_download_partial.txt @@ -15,12 +15,13 @@ cp empty $GOPATH/pkg/mod/cache/download/rsc.io/quote/@v/v1.5.2.partial go mod verify # 'go list' should not load packages from the directory. -# NOTE: the message "directory $dir outside available modules" is reported -# for directories not in the main module, active modules in the module cache, -# or local replacements. In this case, the directory is in the right place, -# but it's incomplete, so 'go list' acts as if it's not an active module. +# NOTE: the message "directory $dir outside main module or its selected dependencies" +# is reported for directories not in the main module, active modules in the +# module cache, or local replacements. In this case, the directory is in the +# right place, but it's incomplete, so 'go list' acts as if it's not an +# active module. ! go list $GOPATH/pkg/mod/rsc.io/quote@v1.5.2 -stderr 'outside available modules' +stderr 'outside main module or its selected dependencies' # 'go list -m' should not print the directory. go list -m -f '{{.Dir}}' rsc.io/quote diff --git a/src/cmd/go/testdata/script/mod_fs_patterns.txt b/src/cmd/go/testdata/script/mod_fs_patterns.txt index a20fefd6d3..276d04e538 100644 --- a/src/cmd/go/testdata/script/mod_fs_patterns.txt +++ b/src/cmd/go/testdata/script/mod_fs_patterns.txt @@ -51,11 +51,11 @@ stdout '^at$' # a package path. cd ../badat/bad@ ! go list . -stderr 'directory . outside available modules' +stderr 'directory . outside main module or its selected dependencies' ! go list $PWD -stderr 'directory . outside available modules' +stderr 'directory . outside main module or its selected dependencies' ! go list $PWD/... -stderr 'directory . outside available modules' +stderr 'directory . outside main module or its selected dependencies' -- x/go.mod -- module m diff --git a/src/cmd/go/testdata/script/mod_list_dir.txt b/src/cmd/go/testdata/script/mod_list_dir.txt index 7ad65ffbc7..157d3b6a8a 100644 --- a/src/cmd/go/testdata/script/mod_list_dir.txt +++ b/src/cmd/go/testdata/script/mod_list_dir.txt @@ -24,7 +24,7 @@ go get rsc.io/sampler@v1.3.1 go list -f '{{.ImportPath}}' $GOPATH/pkg/mod/rsc.io/sampler@v1.3.1 stdout '^rsc.io/sampler$' ! go list -f '{{.ImportPath}}' $GOPATH/pkg/mod/rsc.io/sampler@v1.3.0 -stderr 'outside available modules' +stderr 'outside main module or its selected dependencies' -- go.mod -- module x diff --git a/src/cmd/go/testdata/script/mod_list_replace_dir.txt b/src/cmd/go/testdata/script/mod_list_replace_dir.txt index eac5ca7dd3..b446543916 100644 --- a/src/cmd/go/testdata/script/mod_list_replace_dir.txt +++ b/src/cmd/go/testdata/script/mod_list_replace_dir.txt @@ -9,7 +9,7 @@ go get go mod download rsc.io/quote@v1.5.2 ! go list $GOPATH/pkg/mod/rsc.io/quote@v1.5.2 -stderr '^directory ..[/\\]pkg[/\\]mod[/\\]rsc.io[/\\]quote@v1.5.2 outside available modules$' +stderr '^directory ..[/\\]pkg[/\\]mod[/\\]rsc.io[/\\]quote@v1.5.2 outside main module or its selected dependencies$' go list $GOPATH/pkg/mod/rsc.io/quote@v1.5.1 stdout 'rsc.io/quote' diff --git a/src/cmd/go/testdata/script/run_issue51125.txt b/src/cmd/go/testdata/script/run_issue51125.txt new file mode 100644 index 0000000000..8fa4486ca4 --- /dev/null +++ b/src/cmd/go/testdata/script/run_issue51125.txt @@ -0,0 +1,54 @@ +# Regression test for https://go.dev/issue/51125: +# Relative import paths (a holdover from GOPATH) were accidentally allowed in module mode. + +cd $WORK + +# Relative imports should not be allowed with a go.mod file. + +! go run driver.go +stderr '^driver.go:3:8: "./mypkg" is relative, but relative import paths are not supported in module mode$' + +go list -e -f '{{with .Error}}{{.}}{{end}}' -deps driver.go +stdout '^driver.go:3:8: "./mypkg" is relative, but relative import paths are not supported in module mode$' +! stderr . + + +# Relative imports should not be allowed in module mode even without a go.mod file. +rm go.mod + +! go run driver.go +stderr '^driver.go:3:8: "./mypkg" is relative, but relative import paths are not supported in module mode$' + +go list -e -f '{{with .Error}}{{.}}{{end}}' -deps driver.go +stdout '^driver.go:3:8: "./mypkg" is relative, but relative import paths are not supported in module mode$' +! stderr . + + +# In GOPATH mode, they're still allowed (but only outside of GOPATH/src). +env GO111MODULE=off + +[!short] go run driver.go + +go list -deps driver.go + + +-- $WORK/go.mod -- +module example + +go 1.17 +-- $WORK/driver.go -- +package main + +import "./mypkg" + +func main() { + mypkg.MyFunc() +} +-- $WORK/mypkg/code.go -- +package mypkg + +import "fmt" + +func MyFunc() { + fmt.Println("Hello, world!") +} diff --git a/src/cmd/go/testdata/script/run_work_versioned.txt b/src/cmd/go/testdata/script/run_work_versioned.txt new file mode 100644 index 0000000000..eb0f22d1c0 --- /dev/null +++ b/src/cmd/go/testdata/script/run_work_versioned.txt @@ -0,0 +1,16 @@ +[short] skip +go run example.com/printversion@v0.1.0 +stdout '^main is example.com/printversion v0.1.0$' + +-- go.work -- +go 1.18 + +use ( + . +) +-- go.mod -- +module example + +go 1.18 + +require example.com/printversion v1.0.0 diff --git a/src/cmd/go/testdata/script/test_fuzz_minimize_dirty_cov.txt b/src/cmd/go/testdata/script/test_fuzz_minimize_dirty_cov.txt new file mode 100644 index 0000000000..571bf752d0 --- /dev/null +++ b/src/cmd/go/testdata/script/test_fuzz_minimize_dirty_cov.txt @@ -0,0 +1,84 @@ +# Test that minimization doesn't use dirty coverage snapshots when it +# is unable to actually minimize the input. We do this by checking that +# a expected value appears in the cache. If a dirty coverage map is used +# (i.e. the coverage map generated during the last minimization step, +# rather than the map provided with the initial input) then this value +# is unlikely to appear in the cache, since the map generated during +# the last minimization step should not increase the coverage. + +[short] skip +[!fuzz-instrumented] skip + +env GOCACHE=$WORK/gocache +go test -fuzz=FuzzCovMin -fuzztime=25s -test.fuzzcachedir=$GOCACHE/fuzz +go run check_file/main.go $GOCACHE/fuzz/FuzzCovMin abcd + +-- go.mod -- +module test + +-- covmin_test.go -- +package covmin + +import "testing" + +func FuzzCovMin(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + if len(data) >= 4 && data[0] == 'a' && data[1] == 'b' && data[2] == 'c' && data[3] == 'd' { + return + } + }) +} + +-- check_file/main.go -- +package main + +import ( + "bytes" + "fmt" + "os" + "path/filepath" + "regexp" + "strconv" +) + +func checkFile(name, expected string) (bool, error) { + data, err := os.ReadFile(name) + if err != nil { + return false, err + } + for _, line := range bytes.Split(data, []byte("\n")) { + m := valRe.FindSubmatch(line) + if m == nil { + continue + } + fmt.Println(strconv.Unquote(string(m[1]))) + if s, err := strconv.Unquote(string(m[1])); err != nil { + return false, err + } else if s == expected { + return true, nil + } + } + return false, nil +} + +var valRe = regexp.MustCompile(`^\[\]byte\(([^)]+)\)$`) + +func main() { + dir, expected := os.Args[1], os.Args[2] + ents, err := os.ReadDir(dir) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + for _, ent := range ents { + name := filepath.Join(dir, ent.Name()) + if good, err := checkFile(name, expected); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } else if good { + os.Exit(0) + } + } + fmt.Fprintln(os.Stderr, "input over minimized") + os.Exit(1) +} diff --git a/src/cmd/go/testdata/script/test_fuzz_minimize_interesting.txt b/src/cmd/go/testdata/script/test_fuzz_minimize_interesting.txt index 5d0de17f6b..a09e85b972 100644 --- a/src/cmd/go/testdata/script/test_fuzz_minimize_interesting.txt +++ b/src/cmd/go/testdata/script/test_fuzz_minimize_interesting.txt @@ -127,19 +127,8 @@ func FuzzMinCache(f *testing.F) { if bytes.Equal(buf, seed) { return } - if n := sum(buf); n < 0 { - t.Error("sum cannot be negative") - } }) } - -func sum(buf []byte) int { - n := 0 - for _, b := range buf { - n += int(b) - } - return n -} -- check_testdata/check_testdata.go -- //go:build ignore // +build ignore diff --git a/src/cmd/go/testdata/script/test_fuzz_return.txt b/src/cmd/go/testdata/script/test_fuzz_return.txt new file mode 100644 index 0000000000..63275aad01 --- /dev/null +++ b/src/cmd/go/testdata/script/test_fuzz_return.txt @@ -0,0 +1,19 @@ +[short] skip + +! go test . +stdout '^panic: testing: fuzz target must not return a value \[recovered\]$' + +-- go.mod -- +module test +go 1.18 +-- x_test.go -- +package test + +import "testing" + +func FuzzReturnErr(f *testing.F) { + f.Add("hello, validation!") + f.Fuzz(func(t *testing.T, in string) string { + return in + }) +} diff --git a/src/cmd/go/testdata/script/test_relative_cmdline.txt b/src/cmd/go/testdata/script/test_relative_cmdline.txt index 2f9c80fe4d..96f7b87265 100644 --- a/src/cmd/go/testdata/script/test_relative_cmdline.txt +++ b/src/cmd/go/testdata/script/test_relative_cmdline.txt @@ -1,5 +1,7 @@ # Relative imports in command line package +env GO111MODULE=off + # Run tests outside GOPATH. env GOPATH=$WORK/tmp @@ -47,4 +49,4 @@ func TestF1(t *testing.T) { if F() != p2.F() { t.Fatal(F()) } -} \ No newline at end of file +} diff --git a/src/cmd/go/testdata/script/version_buildvcs_git.txt b/src/cmd/go/testdata/script/version_buildvcs_git.txt index 86d1de06df..44706870e2 100644 --- a/src/cmd/go/testdata/script/version_buildvcs_git.txt +++ b/src/cmd/go/testdata/script/version_buildvcs_git.txt @@ -111,7 +111,7 @@ rm $GOBIN/d$GOEXE go list -x ./... stdout -count=3 '^example.com' stderr -count=1 '^git status' -stderr -count=1 '^git show' +stderr -count=1 '^git -c log.showsignature=false show' -- $WORK/fakebin/git -- #!/bin/sh diff --git a/src/cmd/go/testdata/script/work.txt b/src/cmd/go/testdata/script/work.txt index cbb3746a69..fa1558f9e6 100644 --- a/src/cmd/go/testdata/script/work.txt +++ b/src/cmd/go/testdata/script/work.txt @@ -4,7 +4,7 @@ go env GOWORK ! stdout . go work init ./a ./b -cmp go.work go.work.want +cmpenv go.work go.work.want go env GOWORK stdout '^'$WORK'(\\|/)gopath(\\|/)src(\\|/)go.work$' @@ -32,7 +32,9 @@ stdout 'example.com/b' go list -mod=readonly all ! go list -mod=mod all stderr '^go: -mod may only be set to readonly when in workspace mode' -go list -mod=mod -workfile=off all +env GOWORK=off +go list -mod=mod all +env GOWORK= # Test that duplicates in the use list return an error cp go.work go.work.backup @@ -53,7 +55,9 @@ go run example.com/d # This exercises the code that determines which module command-line-arguments # belongs to. go list ./b/main.go -go build -n -workfile=off -o foo foo.go +env GOWORK=off +go build -n -o foo foo.go +env GOWORK= go build -n -o foo foo.go -- go.work.dup -- @@ -65,7 +69,7 @@ use ( ../src/a ) -- go.work.want -- -go 1.18 +go $goversion use ( ./a diff --git a/src/cmd/go/testdata/script/work_edit.txt b/src/cmd/go/testdata/script/work_edit.txt index fd04bbda6e..278afb7f61 100644 --- a/src/cmd/go/testdata/script/work_edit.txt +++ b/src/cmd/go/testdata/script/work_edit.txt @@ -1,10 +1,10 @@ # Test editing go.work files. go work init m -cmp go.work go.work.want_initial +cmpenv go.work go.work.want_initial go work edit -use n -cmp go.work go.work.want_use_n +cmpenv go.work go.work.want_use_n go work edit -go 1.18 cmp go.work go.work.want_go_118 @@ -30,7 +30,8 @@ cmp stdout go.work.want_print go work edit -json -go 1.19 -use b -dropuse c -replace 'x.1@v1.4.0 = ../z' -dropreplace x.1 -dropreplace x.1@v1.3.0 cmp stdout go.work.want_json -go work edit -print -fmt -workfile $GOPATH/src/unformatted +env GOWORK=$GOPATH/src/unformatted +go work edit -print -fmt cmp stdout formatted -- m/go.mod -- @@ -38,11 +39,11 @@ module m go 1.18 -- go.work.want_initial -- -go 1.18 +go $goversion use ./m -- go.work.want_use_n -- -go 1.18 +go $goversion use ( ./m diff --git a/src/cmd/go/testdata/script/work_env.txt b/src/cmd/go/testdata/script/work_env.txt index ec3d3be3ed..511bb4e2cb 100644 --- a/src/cmd/go/testdata/script/work_env.txt +++ b/src/cmd/go/testdata/script/work_env.txt @@ -13,6 +13,10 @@ cd src go env GOWORK stdout 'go.work' +env GOWORK='off' +go env GOWORK +stdout 'off' + ! go env -w GOWORK=off stderr '^go: GOWORK cannot be modified$' diff --git a/src/cmd/go/testdata/script/work_gowork.txt b/src/cmd/go/testdata/script/work_gowork.txt new file mode 100644 index 0000000000..1cfbf0ca18 --- /dev/null +++ b/src/cmd/go/testdata/script/work_gowork.txt @@ -0,0 +1,24 @@ +env GOWORK=stop.work +! go list a # require absolute path +! stderr panic +env GOWORK=doesnotexist +! go list a +! stderr panic + +env GOWORK=$GOPATH/src/stop.work +go list -n a +go build -n a +go test -n a + +-- stop.work -- +go 1.18 + +use ./a +-- a/a.go -- +package a +-- a/a_test.go -- +package a +-- a/go.mod -- +module a + +go 1.18 \ No newline at end of file diff --git a/src/cmd/go/testdata/script/work_init_gowork.txt b/src/cmd/go/testdata/script/work_init_gowork.txt new file mode 100644 index 0000000000..55ac99b8c0 --- /dev/null +++ b/src/cmd/go/testdata/script/work_init_gowork.txt @@ -0,0 +1,19 @@ +# Test that the GOWORK environment variable flag is used by go work init. + +! exists go.work +go work init +exists go.work + +env GOWORK=$GOPATH/src/foo/foo.work +! exists foo/foo.work +go work init +exists foo/foo.work + +env GOWORK= +cd foo/bar +! go work init +stderr 'already exists' + +# Create directories to make go.work files in. +-- foo/dummy.txt -- +-- foo/bar/dummy.txt -- diff --git a/src/cmd/go/testdata/script/work_init_path.txt b/src/cmd/go/testdata/script/work_init_path.txt new file mode 100644 index 0000000000..e3977882a0 --- /dev/null +++ b/src/cmd/go/testdata/script/work_init_path.txt @@ -0,0 +1,17 @@ +# Regression test for https://go.dev/issue/51448. +# 'go work init . foo/bar' should produce a go.work file +# with the same paths as 'go work init; go work use -r .'. + +go work init . foo/bar +mv go.work go.work.init + +go work init +go work use -r . +cmp go.work go.work.init + +-- go.mod -- +module example +go 1.18 +-- foo/bar/go.mod -- +module example +go 1.18 diff --git a/src/cmd/go/testdata/script/work_issue51204.txt b/src/cmd/go/testdata/script/work_issue51204.txt new file mode 100644 index 0000000000..d483002060 --- /dev/null +++ b/src/cmd/go/testdata/script/work_issue51204.txt @@ -0,0 +1,57 @@ +go work sync + +go list -f '{{.Dir}}' example.com/test +stdout '^'$PWD${/}test'$' + +-- go.work -- +go 1.18 + +use ( + ./test2 + ./test2/sub +) +-- test/go.mod -- +module example.com/test + +go 1.18 +-- test/file.go -- +package test + +func DoSomething() { +} +-- test2/go.mod -- +module example.com/test2 + +go 1.18 + +replace example.com/test => ../test + +require example.com/test v0.0.0-00010101000000-000000000000 +-- test2/file.go -- +package test2 + +import ( + "example.com/test" +) + +func DoSomething() { + test.DoSomething() +} +-- test2/sub/go.mod -- +module example.com/test2/sub + +go 1.18 + +replace example.com/test => ../../test + +require example.com/test v0.0.0 +-- test2/sub/file.go -- +package test2 + +import ( + "example.com/test" +) + +func DoSomething() { + test.DoSomething() +} diff --git a/src/cmd/go/testdata/script/work_module_not_in_go_work.txt b/src/cmd/go/testdata/script/work_module_not_in_go_work.txt new file mode 100644 index 0000000000..23d908c302 --- /dev/null +++ b/src/cmd/go/testdata/script/work_module_not_in_go_work.txt @@ -0,0 +1,25 @@ +# This is a regression test for issue #49632. +# The Go command should mention go.work if the user +# tries to load a local package that's in a module +# that's not in go.work and can't be resolved. + +! go list ./... +stderr 'pattern ./...: directory prefix . does not contain modules listed in go.work or their selected dependencies' + +! go list ./a +stderr 'directory a outside modules listed in go.work' + +-- go.work -- +go 1.18 + +use ./b +-- a/go.mod -- +module example.com/a + +go 1.18 +-- a/a.go -- +package a +-- b/go.mod -- +module example.com/b + +go 1.18 diff --git a/src/cmd/go/testdata/script/work_nowork.txt b/src/cmd/go/testdata/script/work_nowork.txt index b0320cbccb..b4c9b1d9cf 100644 --- a/src/cmd/go/testdata/script/work_nowork.txt +++ b/src/cmd/go/testdata/script/work_nowork.txt @@ -1,17 +1,17 @@ ! go work use -stderr '^go: no go\.work file found\n\t\(run ''go work init'' first or specify path using -workfile flag\)$' +stderr '^go: no go\.work file found\n\t\(run ''go work init'' first or specify path using GOWORK environment variable\)$' ! go work use . -stderr '^go: no go\.work file found\n\t\(run ''go work init'' first or specify path using -workfile flag\)$' +stderr '^go: no go\.work file found\n\t\(run ''go work init'' first or specify path using GOWORK environment variable\)$' ! go work edit -stderr '^go: no go\.work file found\n\t\(run ''go work init'' first or specify path using -workfile flag\)$' +stderr '^go: no go\.work file found\n\t\(run ''go work init'' first or specify path using GOWORK environment variable\)$' ! go work edit -go=1.18 -stderr '^go: no go\.work file found\n\t\(run ''go work init'' first or specify path using -workfile flag\)$' +stderr '^go: no go\.work file found\n\t\(run ''go work init'' first or specify path using GOWORK environment variable\)$' ! go work sync -stderr '^go: no go\.work file found\n\t\(run ''go work init'' first or specify path using -workfile flag\)$' +stderr '^go: no go\.work file found\n\t\(run ''go work init'' first or specify path using GOWORK environment variable\)$' -- go.mod -- module example diff --git a/src/cmd/go/testdata/script/work_replace_conflict.txt b/src/cmd/go/testdata/script/work_replace_conflict.txt index 81d1fcb043..7b71b0fbd7 100644 --- a/src/cmd/go/testdata/script/work_replace_conflict.txt +++ b/src/cmd/go/testdata/script/work_replace_conflict.txt @@ -2,7 +2,7 @@ # overriding it in the go.work file. ! go list -m example.com/dep -stderr 'go: conflicting replacements for example.com/dep@v1.0.0:\n\t./dep1\n\t./dep2\nuse "go work edit -replace example.com/dep@v1.0.0=\[override\]" to resolve' +stderr 'go: conflicting replacements for example.com/dep@v1.0.0:\n\t'$PWD${/}'dep1\n\t'$PWD${/}'dep2\nuse "go work edit -replace example.com/dep@v1.0.0=\[override\]" to resolve' go work edit -replace example.com/dep@v1.0.0=./dep1 go list -m example.com/dep stdout 'example.com/dep v1.0.0 => ./dep1' @@ -15,7 +15,7 @@ use n module example.com/m require example.com/dep v1.0.0 -replace example.com/dep v1.0.0 => ./dep1 +replace example.com/dep v1.0.0 => ../dep1 -- m/m.go -- package m @@ -28,7 +28,7 @@ func F() { module example.com/n require example.com/dep v1.0.0 -replace example.com/dep v1.0.0 => ./dep2 +replace example.com/dep v1.0.0 => ../dep2 -- n/n.go -- package n diff --git a/src/cmd/go/testdata/script/work_use.txt b/src/cmd/go/testdata/script/work_use.txt index f5ea89c900..12c8cecab7 100644 --- a/src/cmd/go/testdata/script/work_use.txt +++ b/src/cmd/go/testdata/script/work_use.txt @@ -14,16 +14,16 @@ use ( go 1.18 use ( - foo - foo/bar/baz + ./foo + ./foo/bar/baz ) -- go.want_work_other -- go 1.18 use ( - foo - foo/bar/baz - other + ./foo + ./foo/bar/baz + ./other ) -- foo/go.mod -- module foo diff --git a/src/cmd/go/testdata/script/work_use_deleted.txt b/src/cmd/go/testdata/script/work_use_deleted.txt new file mode 100644 index 0000000000..b379cbc09d --- /dev/null +++ b/src/cmd/go/testdata/script/work_use_deleted.txt @@ -0,0 +1,22 @@ +go work use -r . +cmp go.work go.work.want + +-- go.work -- +go 1.18 + +use ( + . + ./sub + ./sub/dir/deleted +) +-- go.work.want -- +go 1.18 + +use ./sub/dir +-- sub/README.txt -- +A go.mod file has been deleted from this directory. +In addition, the entire subdirectory sub/dir/deleted +has been deleted, along with sub/dir/deleted/go.mod. +-- sub/dir/go.mod -- +module example/sub/dir +go 1.18 diff --git a/src/cmd/go/testdata/script/work_use_dot.txt b/src/cmd/go/testdata/script/work_use_dot.txt index c24aae33e8..8f210423ec 100644 --- a/src/cmd/go/testdata/script/work_use_dot.txt +++ b/src/cmd/go/testdata/script/work_use_dot.txt @@ -1,6 +1,7 @@ cp go.work go.work.orig -# 'go work use .' should add an entry for the current directory. +# If the current directory contains a go.mod file, +# 'go work use .' should add an entry for it. cd bar/baz go work use . cmp ../../go.work ../../go.work.rel @@ -11,9 +12,28 @@ mv go.mod go.mod.bak go work use . cmp ../../go.work ../../go.work.orig +# If the path is absolute, it should remain absolute. mv go.mod.bak go.mod go work use $PWD -cmpenv ../../go.work ../../go.work.abs +grep -count=1 '^use ' ../../go.work +grep '^use ["]?'$PWD'["]?$' ../../go.work + +# An absolute path should replace an entry for the corresponding relative path +# and vice-versa. +go work use . +cmp ../../go.work ../../go.work.rel +go work use $PWD +grep -count=1 '^use ' ../../go.work +grep '^use ["]?'$PWD'["]?$' ../../go.work + +# If both the absolute and relative paths are named, 'go work use' should error +# out: we don't know which one to use, and shouldn't add both because the +# resulting workspace would contain a duplicate module. +cp ../../go.work.orig ../../go.work +! go work use $PWD . +stderr '^go: already added "\./bar/baz" as "'$PWD'"$' +cmp ../../go.work ../../go.work.orig + -- go.mod -- module example @@ -23,11 +43,7 @@ go 1.18 -- go.work.rel -- go 1.18 -use bar/baz --- go.work.abs -- -go 1.18 - -use $PWD +use ./bar/baz -- bar/baz/go.mod -- module example/bar/baz go 1.18 diff --git a/src/cmd/go/testdata/script/work_use_noargs.txt b/src/cmd/go/testdata/script/work_use_noargs.txt new file mode 100644 index 0000000000..ca054344c6 --- /dev/null +++ b/src/cmd/go/testdata/script/work_use_noargs.txt @@ -0,0 +1,11 @@ +# For now, 'go work use' requires arguments. +# (Eventually, we may may it implicitly behave like 'go work use .'. + +! go work use +stderr '^go: ''go work use'' requires one or more directory arguments' + +! go work use -r +stderr '^go: ''go work use'' requires one or more directory arguments' + +-- go.work -- +go 1.18 diff --git a/src/cmd/go/testdata/script/work_vet.txt b/src/cmd/go/testdata/script/work_vet.txt new file mode 100644 index 0000000000..e258fc0394 --- /dev/null +++ b/src/cmd/go/testdata/script/work_vet.txt @@ -0,0 +1,19 @@ +! go vet ./a +stderr 'fmt.Println call has possible formatting directive' + +-- go.work -- +go 1.18 + +use ./a +-- a/go.mod -- +module example.com/a + +go 1.18 +-- a/a.go -- +package a + +import "fmt" + +func A() { + fmt.Println("%s") +} \ No newline at end of file diff --git a/src/cmd/go/testdata/script/work_workfile.txt b/src/cmd/go/testdata/script/work_workfile.txt deleted file mode 100644 index b62918147e..0000000000 --- a/src/cmd/go/testdata/script/work_workfile.txt +++ /dev/null @@ -1,21 +0,0 @@ -! go list -workfile=stop.work a # require absolute path -! stderr panic -! go list -workfile=doesnotexist a -! stderr panic - -go list -n -workfile=$GOPATH/src/stop.work a -go build -n -workfile=$GOPATH/src/stop.work a -go test -n -workfile=$GOPATH/src/stop.work a - --- stop.work -- -go 1.18 - -use ./a --- a/a.go -- -package a --- a/a_test.go -- -package a --- a/go.mod -- -module a - -go 1.18 \ No newline at end of file diff --git a/src/cmd/gofmt/gofmt.go b/src/cmd/gofmt/gofmt.go index 51f6e652d9..4280ed4459 100644 --- a/src/cmd/gofmt/gofmt.go +++ b/src/cmd/gofmt/gofmt.go @@ -52,6 +52,16 @@ const ( printerNormalizeNumbers = 1 << 30 ) +// fdSem guards the number of concurrently-open file descriptors. +// +// For now, this is arbitrarily set to 200, based on the observation that many +// platforms default to a kernel limit of 256. Ideally, perhaps we should derive +// it from rlimit on platforms that support that system call. +// +// File descriptors opened from outside of this package are not tracked, +// so this limit may be approximate. +var fdSem = make(chan bool, 200) + var ( rewrite func(*token.FileSet, *ast.File) *ast.File parserMode parser.Mode @@ -213,51 +223,9 @@ func (r *reporter) ExitCode() int { // If info == nil, we are formatting stdin instead of a file. // If in == nil, the source is the contents of the file with the given filename. func processFile(filename string, info fs.FileInfo, in io.Reader, r *reporter) error { - if in == nil { - var err error - in, err = os.Open(filename) - if err != nil { - return err - } - } - - // Compute the file's size and read its contents with minimal allocations. - // - // If the size is unknown (or bogus, or overflows an int), fall back to - // a size-independent ReadAll. - var src []byte - size := -1 - if info != nil && info.Mode().IsRegular() && int64(int(info.Size())) == info.Size() { - size = int(info.Size()) - } - if size+1 > 0 { - // If we have the FileInfo from filepath.WalkDir, use it to make - // a buffer of the right size and avoid ReadAll's reallocations. - // - // We try to read size+1 bytes so that we can detect modifications: if we - // read more than size bytes, then the file was modified concurrently. - // (If that happens, we could, say, append to src to finish the read, or - // proceed with a truncated buffer — but the fact that it changed at all - // indicates a possible race with someone editing the file, so we prefer to - // stop to avoid corrupting it.) - src = make([]byte, size+1) - n, err := io.ReadFull(in, src) - if err != nil && err != io.ErrUnexpectedEOF { - return err - } - if n < size { - return fmt.Errorf("error: size of %s changed during reading (from %d to %d bytes)", filename, size, n) - } else if n > size { - return fmt.Errorf("error: size of %s changed during reading (from %d to >=%d bytes)", filename, size, len(src)) - } - src = src[:n] - } else { - // The file is not known to be regular, so we don't have a reliable size for it. - var err error - src, err = io.ReadAll(in) - if err != nil { - return err - } + src, err := readFile(filename, info, in) + if err != nil { + return err } fileSet := token.NewFileSet() @@ -306,7 +274,9 @@ func processFile(filename string, info fs.FileInfo, in io.Reader, r *reporter) e if err != nil { return err } + fdSem <- true err = os.WriteFile(filename, res, perm) + <-fdSem if err != nil { os.Rename(bakname, filename) return err @@ -333,6 +303,65 @@ func processFile(filename string, info fs.FileInfo, in io.Reader, r *reporter) e return err } +// readFile reads the contents of filename, described by info. +// If in is non-nil, readFile reads directly from it. +// Otherwise, readFile opens and reads the file itself, +// with the number of concurrently-open files limited by fdSem. +func readFile(filename string, info fs.FileInfo, in io.Reader) ([]byte, error) { + if in == nil { + fdSem <- true + var err error + f, err := os.Open(filename) + if err != nil { + return nil, err + } + in = f + defer func() { + f.Close() + <-fdSem + }() + } + + // Compute the file's size and read its contents with minimal allocations. + // + // If we have the FileInfo from filepath.WalkDir, use it to make + // a buffer of the right size and avoid ReadAll's reallocations. + // + // If the size is unknown (or bogus, or overflows an int), fall back to + // a size-independent ReadAll. + size := -1 + if info != nil && info.Mode().IsRegular() && int64(int(info.Size())) == info.Size() { + size = int(info.Size()) + } + if size+1 <= 0 { + // The file is not known to be regular, so we don't have a reliable size for it. + var err error + src, err := io.ReadAll(in) + if err != nil { + return nil, err + } + return src, nil + } + + // We try to read size+1 bytes so that we can detect modifications: if we + // read more than size bytes, then the file was modified concurrently. + // (If that happens, we could, say, append to src to finish the read, or + // proceed with a truncated buffer — but the fact that it changed at all + // indicates a possible race with someone editing the file, so we prefer to + // stop to avoid corrupting it.) + src := make([]byte, size+1) + n, err := io.ReadFull(in, src) + if err != nil && err != io.ErrUnexpectedEOF { + return nil, err + } + if n < size { + return nil, fmt.Errorf("error: size of %s changed during reading (from %d to %d bytes)", filename, size, n) + } else if n > size { + return nil, fmt.Errorf("error: size of %s changed during reading (from %d to >=%d bytes)", filename, size, len(src)) + } + return src[:n], nil +} + func main() { // Arbitrarily limit in-flight work to 2MiB times the number of threads. // @@ -354,12 +383,16 @@ func gofmtMain(s *sequencer) { flag.Parse() if *cpuprofile != "" { + fdSem <- true f, err := os.Create(*cpuprofile) if err != nil { s.AddReport(fmt.Errorf("creating cpu profile: %s", err)) return } - defer f.Close() + defer func() { + f.Close() + <-fdSem + }() pprof.StartCPUProfile(f) defer pprof.StopCPUProfile() } @@ -474,6 +507,9 @@ const chmodSupported = runtime.GOOS != "windows" // with lineMax { + if line >= lineMax { // cannot represent line, use max. line so we have some information line = lineMax + // Drop column information if line number saturates. + // Ensures line+col is monotonic. See issue 51193. + col = 0 } if col > colMax { // cannot represent column, use max. column so we have some information diff --git a/src/cmd/internal/src/pos_test.go b/src/cmd/internal/src/pos_test.go index d4cd0e7ff1..cdf4ab4081 100644 --- a/src/cmd/internal/src/pos_test.go +++ b/src/cmd/internal/src/pos_test.go @@ -140,8 +140,8 @@ func TestLico(t *testing.T) { {makeLico(1, 0), ":1", 1, 0}, {makeLico(1, 1), ":1:1", 1, 1}, {makeLico(2, 3), ":2:3", 2, 3}, - {makeLico(lineMax, 1), fmt.Sprintf(":%d:1", lineMax), lineMax, 1}, - {makeLico(lineMax+1, 1), fmt.Sprintf(":%d:1", lineMax), lineMax, 1}, // line too large, stick with max. line + {makeLico(lineMax, 1), fmt.Sprintf(":%d", lineMax), lineMax, 1}, + {makeLico(lineMax+1, 1), fmt.Sprintf(":%d", lineMax), lineMax, 1}, // line too large, stick with max. line {makeLico(1, colMax), ":1", 1, colMax}, {makeLico(1, colMax+1), ":1", 1, 0}, // column too large {makeLico(lineMax+1, colMax+1), fmt.Sprintf(":%d", lineMax), lineMax, 0}, @@ -170,8 +170,8 @@ func TestIsStmt(t *testing.T) { {makeLico(1, 1), ":1:1" + def, 1, 1}, {makeLico(1, 1).withIsStmt(), ":1:1" + is, 1, 1}, {makeLico(1, 1).withNotStmt(), ":1:1" + not, 1, 1}, - {makeLico(lineMax, 1), fmt.Sprintf(":%d:1", lineMax) + def, lineMax, 1}, - {makeLico(lineMax+1, 1), fmt.Sprintf(":%d:1", lineMax) + def, lineMax, 1}, // line too large, stick with max. line + {makeLico(lineMax, 1), fmt.Sprintf(":%d", lineMax) + def, lineMax, 1}, + {makeLico(lineMax+1, 1), fmt.Sprintf(":%d", lineMax) + def, lineMax, 1}, // line too large, stick with max. line {makeLico(1, colMax), ":1" + def, 1, colMax}, {makeLico(1, colMax+1), ":1" + def, 1, 0}, // column too large {makeLico(lineMax+1, colMax+1), fmt.Sprintf(":%d", lineMax) + def, lineMax, 0}, @@ -214,9 +214,9 @@ func TestLogue(t *testing.T) { {makeLico(1, 1).withXlogue(PosPrologueEnd), ":1:1" + defs + pro, 1, 1}, {makeLico(1, 1).withXlogue(PosEpilogueBegin), ":1:1" + defs + epi, 1, 1}, - {makeLico(lineMax, 1).withXlogue(PosDefaultLogue), fmt.Sprintf(":%d:1", lineMax) + defs + defp, lineMax, 1}, - {makeLico(lineMax, 1).withXlogue(PosPrologueEnd), fmt.Sprintf(":%d:1", lineMax) + defs + pro, lineMax, 1}, - {makeLico(lineMax, 1).withXlogue(PosEpilogueBegin), fmt.Sprintf(":%d:1", lineMax) + defs + epi, lineMax, 1}, + {makeLico(lineMax, 1).withXlogue(PosDefaultLogue), fmt.Sprintf(":%d", lineMax) + defs + defp, lineMax, 1}, + {makeLico(lineMax, 1).withXlogue(PosPrologueEnd), fmt.Sprintf(":%d", lineMax) + defs + pro, lineMax, 1}, + {makeLico(lineMax, 1).withXlogue(PosEpilogueBegin), fmt.Sprintf(":%d", lineMax) + defs + epi, lineMax, 1}, } { x := test.x if got := formatstr("", x.Line(), x.Col(), true) + fmt.Sprintf(":%d:%d", x.IsStmt(), x.Xlogue()); got != test.string { diff --git a/src/cmd/link/internal/ld/symtab.go b/src/cmd/link/internal/ld/symtab.go index 720c03afd2..39066da286 100644 --- a/src/cmd/link/internal/ld/symtab.go +++ b/src/cmd/link/internal/ld/symtab.go @@ -567,6 +567,7 @@ func (ctxt *Link) symtab(pcln *pclntab) []sym.SymKind { strings.HasSuffix(name, ".arginfo0"), strings.HasSuffix(name, ".arginfo1"), strings.HasSuffix(name, ".argliveinfo"), + strings.HasSuffix(name, ".wrapinfo"), strings.HasSuffix(name, ".args_stackmap"), strings.HasSuffix(name, ".stkobj"): ldr.SetAttrNotInSymbolTable(s, true) diff --git a/src/crypto/aes/asm_amd64.s b/src/crypto/aes/asm_amd64.s index ad871ec5de..ed831bf47f 100644 --- a/src/crypto/aes/asm_amd64.s +++ b/src/crypto/aes/asm_amd64.s @@ -15,7 +15,7 @@ TEXT ·encryptBlockAsm(SB),NOSPLIT,$0 ADDQ $16, AX PXOR X1, X0 SUBQ $12, CX - JE Lenc196 + JE Lenc192 JB Lenc128 Lenc256: MOVUPS 0(AX), X1 @@ -23,7 +23,7 @@ Lenc256: MOVUPS 16(AX), X1 AESENC X1, X0 ADDQ $32, AX -Lenc196: +Lenc192: MOVUPS 0(AX), X1 AESENC X1, X0 MOVUPS 16(AX), X1 @@ -64,7 +64,7 @@ TEXT ·decryptBlockAsm(SB),NOSPLIT,$0 ADDQ $16, AX PXOR X1, X0 SUBQ $12, CX - JE Ldec196 + JE Ldec192 JB Ldec128 Ldec256: MOVUPS 0(AX), X1 @@ -72,7 +72,7 @@ Ldec256: MOVUPS 16(AX), X1 AESDEC X1, X0 ADDQ $32, AX -Ldec196: +Ldec192: MOVUPS 0(AX), X1 AESDEC X1, X0 MOVUPS 16(AX), X1 @@ -115,7 +115,7 @@ TEXT ·expandKeyAsm(SB),NOSPLIT,$0 ADDQ $16, BX PXOR X4, X4 // _expand_key_* expect X4 to be zero CMPL CX, $12 - JE Lexp_enc196 + JE Lexp_enc192 JB Lexp_enc128 Lexp_enc256: MOVUPS 16(AX), X2 @@ -148,7 +148,7 @@ Lexp_enc256: AESKEYGENASSIST $0x40, X2, X1 CALL _expand_key_256a<>(SB) JMP Lexp_dec -Lexp_enc196: +Lexp_enc192: MOVQ 16(AX), X2 AESKEYGENASSIST $0x01, X2, X1 CALL _expand_key_192a<>(SB) diff --git a/src/crypto/x509/internal/macos/corefoundation.go b/src/crypto/x509/internal/macos/corefoundation.go index cda1d95d81..75c212910b 100644 --- a/src/crypto/x509/internal/macos/corefoundation.go +++ b/src/crypto/x509/internal/macos/corefoundation.go @@ -48,7 +48,7 @@ func CFStringToString(ref CFRef) string { // TimeToCFDateRef converts a time.Time into an apple CFDateRef func TimeToCFDateRef(t time.Time) CFRef { secs := t.Sub(time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)).Seconds() - ref := CFDateCreate(int(secs)) + ref := CFDateCreate(secs) return ref } @@ -170,8 +170,8 @@ func x509_CFArrayAppendValue_trampoline() //go:cgo_import_dynamic x509_CFDateCreate CFDateCreate "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation" -func CFDateCreate(seconds int) CFRef { - ret := syscall(abi.FuncPCABI0(x509_CFDateCreate_trampoline), kCFAllocatorDefault, uintptr(seconds), 0, 0, 0, 0) +func CFDateCreate(seconds float64) CFRef { + ret := syscall(abi.FuncPCABI0(x509_CFDateCreate_trampoline), kCFAllocatorDefault, 0, 0, 0, 0, seconds) return CFRef(ret) } func x509_CFDateCreate_trampoline() @@ -193,7 +193,7 @@ func CFStringCreateExternalRepresentation(strRef CFRef) CFRef { func x509_CFStringCreateExternalRepresentation_trampoline() // syscall is implemented in the runtime package (runtime/sys_darwin.go) -func syscall(fn, a1, a2, a3, a4, a5, a6 uintptr) uintptr +func syscall(fn, a1, a2, a3, a4, a5 uintptr, f1 float64) uintptr // ReleaseCFArray iterates through an array, releasing its contents, and then // releases the array itself. This is necessary because we cannot, easily, set the diff --git a/src/database/sql/fakedb_test.go b/src/database/sql/fakedb_test.go index 8f953f6cb6..d1edcb8c48 100644 --- a/src/database/sql/fakedb_test.go +++ b/src/database/sql/fakedb_test.go @@ -676,6 +676,9 @@ func (c *fakeConn) PrepareContext(ctx context.Context, query string) (driver.Stm if c.waiter != nil { c.waiter(ctx) + if err := ctx.Err(); err != nil { + return nil, err + } } if stmt.wait > 0 { diff --git a/src/database/sql/sql_test.go b/src/database/sql/sql_test.go index 08ca1f5b9a..a921dd5a84 100644 --- a/src/database/sql/sql_test.go +++ b/src/database/sql/sql_test.go @@ -418,26 +418,31 @@ func TestQueryContextWait(t *testing.T) { defer closeDB(t, db) prepares0 := numPrepares(t, db) - // TODO(kardianos): convert this from using a timeout to using an explicit - // cancel when the query signals that it is "executing" the query. - ctx, cancel := context.WithTimeout(context.Background(), 300*time.Millisecond) + ctx, cancel := context.WithCancel(context.Background()) defer cancel() // This will trigger the *fakeConn.Prepare method which will take time // performing the query. The ctxDriverPrepare func will check the context // after this and close the rows and return an error. - _, err := db.QueryContext(ctx, "WAIT|1s|SELECT|people|age,name|") - if err != context.DeadlineExceeded { + c, err := db.Conn(ctx) + if err != nil { + t.Fatal(err) + } + + c.dc.ci.(*fakeConn).waiter = func(c context.Context) { + cancel() + <-ctx.Done() + } + _, err = c.QueryContext(ctx, "SELECT|people|age,name|") + c.Close() + if err != context.Canceled { t.Fatalf("expected QueryContext to error with context deadline exceeded but returned %v", err) } // Verify closed rows connection after error condition. waitForFree(t, db, 1) if prepares := numPrepares(t, db) - prepares0; prepares != 1 { - // TODO(kardianos): if the context timeouts before the db.QueryContext - // executes this check may fail. After adjusting how the context - // is canceled above revert this back to a Fatal error. - t.Logf("executed %d Prepare statements; want 1", prepares) + t.Fatalf("executed %d Prepare statements; want 1", prepares) } } @@ -455,14 +460,14 @@ func TestTxContextWait(t *testing.T) { } tx.keepConnOnRollback = false - go func() { - time.Sleep(15 * time.Millisecond) + tx.dc.ci.(*fakeConn).waiter = func(c context.Context) { cancel() - }() + <-ctx.Done() + } // This will trigger the *fakeConn.Prepare method which will take time // performing the query. The ctxDriverPrepare func will check the context // after this and close the rows and return an error. - _, err = tx.QueryContext(ctx, "WAIT|1s|SELECT|people|age,name|") + _, err = tx.QueryContext(ctx, "SELECT|people|age,name|") if err != context.Canceled { t.Fatalf("expected QueryContext to error with context canceled but returned %v", err) } diff --git a/src/debug/buildinfo/buildinfo.go b/src/debug/buildinfo/buildinfo.go index 2c0200e8dc..8de03ff106 100644 --- a/src/debug/buildinfo/buildinfo.go +++ b/src/debug/buildinfo/buildinfo.go @@ -75,8 +75,8 @@ func Read(r io.ReaderAt) (*BuildInfo, error) { if err != nil { return nil, err } - bi := &BuildInfo{} - if err := bi.UnmarshalText([]byte(mod)); err != nil { + bi, err := debug.ParseBuildInfo(mod) + if err != nil { return nil, err } bi.GoVersion = vers diff --git a/src/debug/buildinfo/buildinfo_test.go b/src/debug/buildinfo/buildinfo_test.go index 8346be0109..ac71626fda 100644 --- a/src/debug/buildinfo/buildinfo_test.go +++ b/src/debug/buildinfo/buildinfo_test.go @@ -212,12 +212,10 @@ func TestReadFile(t *testing.T) { } else { if tc.wantErr != "" { t.Fatalf("unexpected success; want error containing %q", tc.wantErr) - } else if got, err := info.MarshalText(); err != nil { - t.Fatalf("unexpected error marshaling BuildInfo: %v", err) - } else if got := cleanOutputForComparison(string(got)); got != tc.want { - if got != tc.want { - t.Fatalf("got:\n%s\nwant:\n%s", got, tc.want) - } + } + got := info.String() + if clean := cleanOutputForComparison(string(got)); got != tc.want && clean != tc.want { + t.Fatalf("got:\n%s\nwant:\n%s", got, tc.want) } } }) diff --git a/src/encoding/xml/marshal.go b/src/encoding/xml/marshal.go index 6859be04a2..7792ac77f8 100644 --- a/src/encoding/xml/marshal.go +++ b/src/encoding/xml/marshal.go @@ -512,7 +512,7 @@ func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo, startTemplat } fv := finfo.value(val, dontInitNilPointers) - if finfo.flags&fOmitEmpty != 0 && isEmptyValue(fv) { + if finfo.flags&fOmitEmpty != 0 && (!fv.IsValid() || isEmptyValue(fv)) { continue } diff --git a/src/encoding/xml/marshal_test.go b/src/encoding/xml/marshal_test.go index 5fdbae7ef0..3fe7e2dc00 100644 --- a/src/encoding/xml/marshal_test.go +++ b/src/encoding/xml/marshal_test.go @@ -2495,3 +2495,39 @@ func TestInvalidXMLName(t *testing.T) { t.Errorf("error %q does not contain %q", err, want) } } + +// Issue 50164. Crash on zero value XML attribute. +type LayerOne struct { + XMLName Name `xml:"l1"` + + Value *float64 `xml:"value,omitempty"` + *LayerTwo `xml:",omitempty"` +} + +type LayerTwo struct { + ValueTwo *int `xml:"value_two,attr,omitempty"` +} + +func TestMarshalZeroValue(t *testing.T) { + proofXml := `1.2345` + var l1 LayerOne + err := Unmarshal([]byte(proofXml), &l1) + if err != nil { + t.Fatalf("unmarshal XML error: %v", err) + } + want := float64(1.2345) + got := *l1.Value + if got != want { + t.Fatalf("unexpected unmarshal result, want %f but got %f", want, got) + } + + // Marshal again (or Encode again) + // In issue 50164, here `Marshal(l1)` will panic because of the zero value of xml attribute ValueTwo `value_two`. + anotherXML, err := Marshal(l1) + if err != nil { + t.Fatalf("marshal XML error: %v", err) + } + if string(anotherXML) != proofXml { + t.Fatalf("unexpected unmarshal result, want %q but got %q", proofXml, anotherXML) + } +} diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index 4a54b689e5..ed8ddcb307 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -88,6 +88,7 @@ var depsRules = ` < internal/itoa < internal/unsafeheader < runtime/internal/sys + < runtime/internal/syscall < runtime/internal/atomic < runtime/internal/math < runtime diff --git a/src/go/doc/reader.go b/src/go/doc/reader.go index de1d422106..d9e721d01b 100644 --- a/src/go/doc/reader.go +++ b/src/go/doc/reader.go @@ -927,6 +927,7 @@ var predeclaredTypes = map[string]bool{ "any": true, "bool": true, "byte": true, + "comparable": true, "complex64": true, "complex128": true, "error": true, diff --git a/src/go/doc/testdata/b.0.golden b/src/go/doc/testdata/b.0.golden index 9d93392eaa..c06246a7b1 100644 --- a/src/go/doc/testdata/b.0.golden +++ b/src/go/doc/testdata/b.0.golden @@ -46,6 +46,9 @@ VARIABLES FUNCTIONS + // Associated with comparable type if AllDecls is set. + func ComparableFactory() comparable + // func F(x int) int diff --git a/src/go/doc/testdata/b.1.golden b/src/go/doc/testdata/b.1.golden index 66c47b5c2a..2b62c3400c 100644 --- a/src/go/doc/testdata/b.1.golden +++ b/src/go/doc/testdata/b.1.golden @@ -38,6 +38,12 @@ TYPES // func (x *T) M() + // Should only appear if AllDecls is set. + type comparable struct{} // overrides a predeclared type comparable + + // Associated with comparable type if AllDecls is set. + func ComparableFactory() comparable + // type notExported int diff --git a/src/go/doc/testdata/b.2.golden b/src/go/doc/testdata/b.2.golden index 9d93392eaa..c06246a7b1 100644 --- a/src/go/doc/testdata/b.2.golden +++ b/src/go/doc/testdata/b.2.golden @@ -46,6 +46,9 @@ VARIABLES FUNCTIONS + // Associated with comparable type if AllDecls is set. + func ComparableFactory() comparable + // func F(x int) int diff --git a/src/go/doc/testdata/b.go b/src/go/doc/testdata/b.go index e50663b3df..61b512bc8a 100644 --- a/src/go/doc/testdata/b.go +++ b/src/go/doc/testdata/b.go @@ -27,9 +27,15 @@ func UintFactory() uint {} // Associated with uint type if AllDecls is set. func uintFactory() uint {} +// Associated with comparable type if AllDecls is set. +func ComparableFactory() comparable {} + // Should only appear if AllDecls is set. type uint struct{} // overrides a predeclared type uint +// Should only appear if AllDecls is set. +type comparable struct{} // overrides a predeclared type comparable + // ---------------------------------------------------------------------------- // Exported declarations associated with non-exported types must always be shown. diff --git a/src/go/internal/gcimporter/iimport.go b/src/go/internal/gcimporter/iimport.go index 8ec4c5413b..bff1c09cc9 100644 --- a/src/go/internal/gcimporter/iimport.go +++ b/src/go/internal/gcimporter/iimport.go @@ -181,6 +181,15 @@ func iImportData(fset *token.FileSet, imports map[string]*types.Package, dataRea p.doDecl(localpkg, name) } + // SetConstraint can't be called if the constraint type is not yet complete. + // When type params are created in the 'P' case of (*importReader).obj(), + // the associated constraint type may not be complete due to recursion. + // Therefore, we defer calling SetConstraint there, and call it here instead + // after all types are complete. + for _, d := range p.later { + d.t.SetConstraint(d.constraint) + } + for _, typ := range p.interfaceList { typ.Complete() } @@ -195,6 +204,11 @@ func iImportData(fset *token.FileSet, imports map[string]*types.Package, dataRea return localpkg, nil } +type setConstraintArgs struct { + t *types.TypeParam + constraint types.Type +} + type iimporter struct { exportVersion int64 ipath string @@ -211,6 +225,9 @@ type iimporter struct { fake fakeFileSet interfaceList []*types.Interface + + // Arguments for calls to SetConstraint that are deferred due to recursive types + later []setConstraintArgs } func (p *iimporter) doDecl(pkg *types.Package, name string) { @@ -391,7 +408,11 @@ func (r *importReader) obj(name string) { } iface.MarkImplicit() } - t.SetConstraint(constraint) + // The constraint type may not be complete, if we + // are in the middle of a type recursion involving type + // constraints. So, we defer SetConstraint until we have + // completely set up all types in ImportData. + r.p.later = append(r.p.later, setConstraintArgs{t: t, constraint: constraint}) case 'V': typ := r.typ() diff --git a/src/go/parser/parser.go b/src/go/parser/parser.go index 4479adb732..51a3c3e67f 100644 --- a/src/go/parser/parser.go +++ b/src/go/parser/parser.go @@ -543,6 +543,13 @@ func (p *parser) parseArrayType(lbrack token.Pos, len ast.Expr) *ast.ArrayType { } p.exprLev-- } + if p.tok == token.COMMA { + // Trailing commas are accepted in type parameter + // lists but not in array type declarations. + // Accept for better error handling but complain. + p.error(p.pos, "unexpected comma; expecting ]") + p.next() + } p.expect(token.RBRACK) elt := p.parseType() return &ast.ArrayType{Lbrack: lbrack, Len: len, Elt: elt} @@ -797,7 +804,7 @@ func (p *parser) parseParamDecl(name *ast.Ident, typeSetsOK bool) (f field) { return } -func (p *parser) parseParameterList(name0 *ast.Ident, closing token.Token) (params []*ast.Field) { +func (p *parser) parseParameterList(name0 *ast.Ident, typ0 ast.Expr, closing token.Token) (params []*ast.Field) { if p.trace { defer un(trace(p, "ParameterList")) } @@ -816,8 +823,17 @@ func (p *parser) parseParameterList(name0 *ast.Ident, closing token.Token) (para var named int // number of parameters that have an explicit name and type for name0 != nil || p.tok != closing && p.tok != token.EOF { - par := p.parseParamDecl(name0, typeSetsOK) + var par field + if typ0 != nil { + if typeSetsOK { + typ0 = p.embeddedElem(typ0) + } + par = field{name0, typ0} + } else { + par = p.parseParamDecl(name0, typeSetsOK) + } name0 = nil // 1st name was consumed if present + typ0 = nil // 1st typ was consumed if present if par.name != nil || par.typ != nil { list = append(list, par) if par.name != nil && par.typ != nil { @@ -926,7 +942,7 @@ func (p *parser) parseParameters(acceptTParams bool) (tparams, params *ast.Field opening := p.pos p.next() // [T any](params) syntax - list := p.parseParameterList(nil, token.RBRACK) + list := p.parseParameterList(nil, nil, token.RBRACK) rbrack := p.expect(token.RBRACK) tparams = &ast.FieldList{Opening: opening, List: list, Closing: rbrack} // Type parameter lists must not be empty. @@ -940,7 +956,7 @@ func (p *parser) parseParameters(acceptTParams bool) (tparams, params *ast.Field var fields []*ast.Field if p.tok != token.RPAREN { - fields = p.parseParameterList(nil, token.RPAREN) + fields = p.parseParameterList(nil, nil, token.RPAREN) } rparen := p.expect(token.RPAREN) @@ -1007,7 +1023,7 @@ func (p *parser) parseMethodSpec() *ast.Field { // // Interface methods do not have type parameters. We parse them for a // better error message and improved error recovery. - _ = p.parseParameterList(name0, token.RBRACK) + _ = p.parseParameterList(name0, nil, token.RBRACK) _ = p.expect(token.RBRACK) p.error(lbrack, "interface method must have no type parameters") @@ -1784,7 +1800,12 @@ func (p *parser) tokPrec() (token.Token, int) { return tok, tok.Precedence() } -func (p *parser) parseBinaryExpr(x ast.Expr, prec1 int) ast.Expr { +// parseBinaryExpr parses a (possibly) binary expression. +// If x is non-nil, it is used as the left operand. +// If check is true, operands are checked to be valid expressions. +// +// TODO(rfindley): parseBinaryExpr has become overloaded. Consider refactoring. +func (p *parser) parseBinaryExpr(x ast.Expr, prec1 int, check bool) ast.Expr { if p.trace { defer un(trace(p, "BinaryExpr")) } @@ -1798,11 +1819,32 @@ func (p *parser) parseBinaryExpr(x ast.Expr, prec1 int) ast.Expr { return x } pos := p.expect(op) - y := p.parseBinaryExpr(nil, oprec+1) - x = &ast.BinaryExpr{X: p.checkExpr(x), OpPos: pos, Op: op, Y: p.checkExpr(y)} + y := p.parseBinaryExpr(nil, oprec+1, check) + if check { + x = p.checkExpr(x) + y = p.checkExpr(y) + } + x = &ast.BinaryExpr{X: x, OpPos: pos, Op: op, Y: y} } } +// checkBinaryExpr checks binary expressions that were not already checked by +// parseBinaryExpr, because the latter was called with check=false. +func (p *parser) checkBinaryExpr(x ast.Expr) { + bx, ok := x.(*ast.BinaryExpr) + if !ok { + return + } + + bx.X = p.checkExpr(bx.X) + bx.Y = p.checkExpr(bx.Y) + + // parseBinaryExpr checks x and y for each binary expr in a tree, so we + // traverse the tree of binary exprs starting from x. + p.checkBinaryExpr(bx.X) + p.checkBinaryExpr(bx.Y) +} + // The result may be a type or even a raw type ([...]int). Callers must // check the result (using checkExpr or checkExprOrType), depending on // context. @@ -1811,7 +1853,7 @@ func (p *parser) parseExpr() ast.Expr { defer un(trace(p, "Expression")) } - return p.parseBinaryExpr(nil, token.LowestPrec+1) + return p.parseBinaryExpr(nil, token.LowestPrec+1, true) } func (p *parser) parseRhs() ast.Expr { @@ -2534,12 +2576,12 @@ func (p *parser) parseValueSpec(doc *ast.CommentGroup, _ token.Pos, keyword toke return spec } -func (p *parser) parseGenericType(spec *ast.TypeSpec, openPos token.Pos, name0 *ast.Ident) { +func (p *parser) parseGenericType(spec *ast.TypeSpec, openPos token.Pos, name0 *ast.Ident, typ0 ast.Expr) { if p.trace { defer un(trace(p, "parseGenericType")) } - list := p.parseParameterList(name0, token.RBRACK) + list := p.parseParameterList(name0, typ0, token.RBRACK) closePos := p.expect(token.RBRACK) spec.TypeParams = &ast.FieldList{Opening: openPos, List: list, Closing: closePos} // Let the type checker decide whether to accept type parameters on aliases: @@ -2564,31 +2606,85 @@ func (p *parser) parseTypeSpec(doc *ast.CommentGroup, _ token.Pos, _ token.Token lbrack := p.pos p.next() if p.tok == token.IDENT { - // array type or generic type: [name0... - name0 := p.parseIdent() + // We may have an array type or a type parameter list. + // In either case we expect an expression x (which may + // just be a name, or a more complex expression) which + // we can analyze further. + // + // A type parameter list may have a type bound starting + // with a "[" as in: P []E. In that case, simply parsing + // an expression would lead to an error: P[] is invalid. + // But since index or slice expressions are never constant + // and thus invalid array length expressions, if we see a + // "[" following a name it must be the start of an array + // or slice constraint. Only if we don't see a "[" do we + // need to parse a full expression. // Index or slice expressions are never constant and thus invalid // array length expressions. Thus, if we see a "[" following name // we can safely assume that "[" name starts a type parameter list. - var x ast.Expr // x != nil means x is the array length expression + var x ast.Expr = p.parseIdent() if p.tok != token.LBRACK { - // We may still have either an array type or generic type -- check if - // name0 is the entire expr. + // To parse the expression starting with name, expand + // the call sequence we would get by passing in name + // to parser.expr, and pass in name to parsePrimaryExpr. p.exprLev++ - lhs := p.parsePrimaryExpr(name0) - x = p.parseBinaryExpr(lhs, token.LowestPrec+1) + lhs := p.parsePrimaryExpr(x) + x = p.parseBinaryExpr(lhs, token.LowestPrec+1, false) p.exprLev-- - if x == name0 && p.tok != token.RBRACK { - x = nil + } + + // analyze the cases + var pname *ast.Ident // pname != nil means pname is the type parameter name + var ptype ast.Expr // ptype != nil means ptype is the type parameter type; pname != nil in this case + + switch t := x.(type) { + case *ast.Ident: + // Unless we see a "]", we are at the start of a type parameter list. + if p.tok != token.RBRACK { + // d.Name "[" name ... + pname = t + // no ptype + } + case *ast.BinaryExpr: + // If we have an expression of the form name*T, and T is a (possibly + // parenthesized) type literal or the next token is a comma, we are + // at the start of a type parameter list. + if name, _ := t.X.(*ast.Ident); name != nil { + if t.Op == token.MUL && (isTypeLit(t.Y) || p.tok == token.COMMA) { + // d.Name "[" name "*" t.Y + // d.Name "[" name "*" t.Y "," + // convert t into unary *t.Y + pname = name + ptype = &ast.StarExpr{Star: t.OpPos, X: t.Y} + } + } + if pname == nil { + // A normal binary expression. Since we passed check=false, we must + // now check its operands. + p.checkBinaryExpr(t) + } + case *ast.CallExpr: + // If we have an expression of the form name(T), and T is a (possibly + // parenthesized) type literal or the next token is a comma, we are + // at the start of a type parameter list. + if name, _ := t.Fun.(*ast.Ident); name != nil { + if len(t.Args) == 1 && !t.Ellipsis.IsValid() && (isTypeLit(t.Args[0]) || p.tok == token.COMMA) { + // d.Name "[" name "(" t.ArgList[0] ")" + // d.Name "[" name "(" t.ArgList[0] ")" "," + pname = name + ptype = t.Args[0] + } } } - if x == nil { - // generic type [T any]; - p.parseGenericType(spec, lbrack, name0) + if pname != nil { + // d.Name "[" pname ... + // d.Name "[" pname ptype ... + // d.Name "[" pname ptype "," ... + p.parseGenericType(spec, lbrack, pname, ptype) } else { - // array type - // TODO(rfindley) should resolve all identifiers in x. + // d.Name "[" x ... spec.Type = p.parseArrayType(lbrack, x) } } else { @@ -2611,6 +2707,21 @@ func (p *parser) parseTypeSpec(doc *ast.CommentGroup, _ token.Pos, _ token.Token return spec } +// isTypeLit reports whether x is a (possibly parenthesized) type literal. +func isTypeLit(x ast.Expr) bool { + switch x := x.(type) { + case *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.ChanType: + return true + case *ast.StarExpr: + // *T may be a pointer dereferenciation. + // Only consider *T as type literal if T is a type literal. + return isTypeLit(x.X) + case *ast.ParenExpr: + return isTypeLit(x.X) + } + return false +} + func (p *parser) parseGenDecl(keyword token.Token, f parseSpecFunction) *ast.GenDecl { if p.trace { defer un(trace(p, "GenDecl("+keyword.String()+")")) diff --git a/src/go/parser/short_test.go b/src/go/parser/short_test.go index cf4fa0a902..d117f0d381 100644 --- a/src/go/parser/short_test.go +++ b/src/go/parser/short_test.go @@ -74,7 +74,7 @@ var validWithTParamsOnly = []string{ `package p; type T[P any /* ERROR "expected ']', found any" */ ] struct { P }`, `package p; type T[P comparable /* ERROR "expected ']', found comparable" */ ] struct { P }`, `package p; type T[P comparable /* ERROR "expected ']', found comparable" */ [P]] struct { P }`, - `package p; type T[P1, /* ERROR "expected ']', found ','" */ P2 any] struct { P1; f []P2 }`, + `package p; type T[P1, /* ERROR "unexpected comma" */ P2 any] struct { P1; f []P2 }`, `package p; func _[ /* ERROR "expected '\(', found '\['" */ T any]()()`, `package p; func _(T (P))`, `package p; func f[ /* ERROR "expected '\(', found '\['" */ A, B any](); func _() { _ = f[int, int] }`, @@ -83,8 +83,8 @@ var validWithTParamsOnly = []string{ `package p; func _(p.T[ /* ERROR "missing ',' in parameter list" */ Q])`, `package p; type _[A interface /* ERROR "expected ']', found 'interface'" */ {},] struct{}`, `package p; type _[A interface /* ERROR "expected ']', found 'interface'" */ {}] struct{}`, - `package p; type _[A, /* ERROR "expected ']', found ','" */ B any,] struct{}`, - `package p; type _[A, /* ERROR "expected ']', found ','" */ B any] struct{}`, + `package p; type _[A, /* ERROR "unexpected comma" */ B any,] struct{}`, + `package p; type _[A, /* ERROR "unexpected comma" */ B any] struct{}`, `package p; type _[A any /* ERROR "expected ']', found any" */,] struct{}`, `package p; type _[A any /* ERROR "expected ']', found any" */ ]struct{}`, `package p; type _[A any /* ERROR "expected ']', found any" */ ] struct{ A }`, @@ -95,8 +95,8 @@ var validWithTParamsOnly = []string{ `package p; func _[ /* ERROR "expected '\(', found '\['" */ A, B C](a A) B`, `package p; func _[ /* ERROR "expected '\(', found '\['" */ A, B C[A, B]](a A) B`, - `package p; type _[A, /* ERROR "expected ']', found ','" */ B any] interface { _(a A) B }`, - `package p; type _[A, /* ERROR "expected ']', found ','" */ B C[A, B]] interface { _(a A) B }`, + `package p; type _[A, /* ERROR "unexpected comma" */ B any] interface { _(a A) B }`, + `package p; type _[A, /* ERROR "unexpected comma" */ B C[A, B]] interface { _(a A) B }`, `package p; func _[ /* ERROR "expected '\(', found '\['" */ T1, T2 interface{}](x T1) T2`, `package p; func _[ /* ERROR "expected '\(', found '\['" */ T1 interface{ m() }, T2, T3 interface{}](x T1, y T3) T2`, `package p; var _ = [ /* ERROR "expected expression" */ ]T[int]{}`, @@ -193,7 +193,7 @@ var invalids = []string{ `package p; func f() { go func() { func() { f(x func /* ERROR "missing ','" */ (){}) } } }`, `package p; func _() (type /* ERROR "found 'type'" */ T)(T)`, `package p; func (type /* ERROR "found 'type'" */ T)(T) _()`, - `package p; type _[A+B, /* ERROR "expected ']'" */ ] int`, + `package p; type _[A+B, /* ERROR "unexpected comma" */ ] int`, // TODO(rfindley): this error should be positioned on the ':' `package p; var a = a[[]int:[ /* ERROR "expected expression" */ ]int];`, @@ -231,7 +231,7 @@ var invalidNoTParamErrs = []string{ `package p; type T[P any /* ERROR "expected ']', found any" */ ] = T0`, `package p; var _ func[ /* ERROR "expected '\(', found '\['" */ T any](T)`, `package p; func _[ /* ERROR "expected '\(', found '\['" */ ]()`, - `package p; type _[A, /* ERROR "expected ']', found ','" */] struct{ A }`, + `package p; type _[A, /* ERROR "unexpected comma" */] struct{ A }`, `package p; func _[ /* ERROR "expected '\(', found '\['" */ type P, *Q interface{}]()`, `package p; func (T) _[ /* ERROR "expected '\(', found '\['" */ A, B any](a A) B`, diff --git a/src/go/parser/testdata/issue49482.go2 b/src/go/parser/testdata/issue49482.go2 new file mode 100644 index 0000000000..50de65118e --- /dev/null +++ b/src/go/parser/testdata/issue49482.go2 @@ -0,0 +1,35 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type ( + // these need a comma to disambiguate + _[P *T,] struct{} + _[P *T, _ any] struct{} + _[P (*T),] struct{} + _[P (*T), _ any] struct{} + _[P (T),] struct{} + _[P (T), _ any] struct{} + + // these parse as name followed by type + _[P *struct{}] struct{} + _[P (*struct{})] struct{} + _[P ([]int)] struct{} + + // array declarations + _ [P(T)]struct{} + _ [P((T))]struct{} + _ [P * *T]struct{} + _ [P * T]struct{} + _ [P(*T)]struct{} + _ [P(**T)]struct{} + _ [P * T - T]struct{} + _ [P*T-T, /* ERROR "unexpected comma" */ ]struct{} + _ [10, /* ERROR "unexpected comma" */ ]struct{} + + // These should be parsed as generic type declarations. + _[P *struct /* ERROR "expected expression" */ {}|int] struct{} + _[P *struct /* ERROR "expected expression" */ {}|int|string] struct{} +) diff --git a/src/go/parser/testdata/typeparams.src b/src/go/parser/testdata/typeparams.src index 1fea23f51a..479cb96871 100644 --- a/src/go/parser/testdata/typeparams.src +++ b/src/go/parser/testdata/typeparams.src @@ -9,7 +9,7 @@ package p type List[E any /* ERROR "expected ']', found any" */ ] []E -type Pair[L, /* ERROR "expected ']', found ','" */ R any] struct { +type Pair[L, /* ERROR "unexpected comma" */ R any] struct { Left L Right R } diff --git a/src/go/printer/nodes.go b/src/go/printer/nodes.go index 19d4ab6663..9a09d58eb2 100644 --- a/src/go/printer/nodes.go +++ b/src/go/printer/nodes.go @@ -319,9 +319,17 @@ func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exp } } -func (p *printer) parameters(fields *ast.FieldList, isTypeParam bool) { +type paramMode int + +const ( + funcParam paramMode = iota + funcTParam + typeTParam +) + +func (p *printer) parameters(fields *ast.FieldList, mode paramMode) { openTok, closeTok := token.LPAREN, token.RPAREN - if isTypeParam { + if mode != funcParam { openTok, closeTok = token.LBRACK, token.RBRACK } p.print(fields.Opening, openTok) @@ -367,26 +375,54 @@ func (p *printer) parameters(fields *ast.FieldList, isTypeParam bool) { p.expr(stripParensAlways(par.Type)) prevLine = parLineEnd } + // if the closing ")" is on a separate line from the last parameter, // print an additional "," and line break if closing := p.lineFor(fields.Closing); 0 < prevLine && prevLine < closing { p.print(token.COMMA) p.linebreak(closing, 0, ignore, true) + } else if mode == typeTParam && fields.NumFields() == 1 { + // Otherwise, if we are in a type parameter list that could be confused + // with the constant array length expression [P*C], print a comma so that + // parsing is unambiguous. + // + // Note that while ParenExprs can also be ambiguous (issue #49482), the + // printed type is never parenthesized (stripParensAlways is used above). + if t, _ := fields.List[0].Type.(*ast.StarExpr); t != nil && !isTypeLit(t.X) { + p.print(token.COMMA) + } } + // unindent if we indented if ws == ignore { p.print(unindent) } } + p.print(fields.Closing, closeTok) } +// isTypeLit reports whether x is a (possibly parenthesized) type literal. +func isTypeLit(x ast.Expr) bool { + switch x := x.(type) { + case *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.ChanType: + return true + case *ast.StarExpr: + // *T may be a pointer dereferenciation. + // Only consider *T as type literal if T is a type literal. + return isTypeLit(x.X) + case *ast.ParenExpr: + return isTypeLit(x.X) + } + return false +} + func (p *printer) signature(sig *ast.FuncType) { if sig.TypeParams != nil { - p.parameters(sig.TypeParams, true) + p.parameters(sig.TypeParams, funcTParam) } if sig.Params != nil { - p.parameters(sig.Params, false) + p.parameters(sig.Params, funcParam) } else { p.print(token.LPAREN, token.RPAREN) } @@ -400,7 +436,7 @@ func (p *printer) signature(sig *ast.FuncType) { p.expr(stripParensAlways(res.List[0].Type)) return } - p.parameters(res, false) + p.parameters(res, funcParam) } } @@ -1611,7 +1647,7 @@ func (p *printer) spec(spec ast.Spec, n int, doIndent bool) { p.setComment(s.Doc) p.expr(s.Name) if s.TypeParams != nil { - p.parameters(s.TypeParams, true) + p.parameters(s.TypeParams, typeTParam) } if n == 1 { p.print(blank) @@ -1801,7 +1837,7 @@ func (p *printer) funcDecl(d *ast.FuncDecl) { // FUNC is emitted). startCol := p.out.Column - len("func ") if d.Recv != nil { - p.parameters(d.Recv, false) // method: print receiver + p.parameters(d.Recv, funcParam) // method: print receiver p.print(blank) } p.expr(d.Name) diff --git a/src/go/printer/testdata/generics.golden b/src/go/printer/testdata/generics.golden index 3d95eda5b2..c3a7df8372 100644 --- a/src/go/printer/testdata/generics.golden +++ b/src/go/printer/testdata/generics.golden @@ -38,3 +38,55 @@ func _() { // type constraint literals with elided interfaces func _[P ~int, Q int | string]() {} func _[P struct{ f int }, Q *P]() {} + +// various potentially ambiguous type parameter lists (issue #49482) +type _[P *T,] struct{} +type _[P *T, _ any] struct{} +type _[P *T,] struct{} +type _[P *T, _ any] struct{} +type _[P T] struct{} +type _[P T, _ any] struct{} + +type _[P *struct{}] struct{} +type _[P *struct{}] struct{} +type _[P []int] struct{} + +// array type declarations +type _ [P(T)]struct{} +type _ [P((T))]struct{} +type _ [P * *T]struct{} +type _ [P * T]struct{} +type _ [P(*T)]struct{} +type _ [P(**T)]struct{} +type _ [P * T]struct{} +type _ [P*T - T]struct{} + +type _[ + P *T, +] struct{} + +// equivalent test cases for potentially ambiguous type parameter lists, except +// for function declarations there is no ambiguity (issue #51548) +func _[P *T]() {} +func _[P *T, _ any]() {} +func _[P *T]() {} +func _[P *T, _ any]() {} +func _[P T]() {} +func _[P T, _ any]() {} + +func _[P *struct{}]() {} +func _[P *struct{}]() {} +func _[P []int]() {} + +func _[P T]() {} +func _[P T]() {} +func _[P **T]() {} +func _[P *T]() {} +func _[P *T]() {} +func _[P **T]() {} +func _[P *T]() {} + +func _[ + P *T, +]() { +} diff --git a/src/go/printer/testdata/generics.input b/src/go/printer/testdata/generics.input index 746dfdd235..66e1554f7f 100644 --- a/src/go/printer/testdata/generics.input +++ b/src/go/printer/testdata/generics.input @@ -35,3 +35,54 @@ func _() { // type constraint literals with elided interfaces func _[P ~int, Q int | string]() {} func _[P struct{f int}, Q *P]() {} + +// various potentially ambiguous type parameter lists (issue #49482) +type _[P *T,] struct{} +type _[P *T, _ any] struct{} +type _[P (*T),] struct{} +type _[P (*T), _ any] struct{} +type _[P (T),] struct{} +type _[P (T), _ any] struct{} + +type _[P *struct{}] struct{} +type _[P (*struct{})] struct{} +type _[P ([]int)] struct{} + +// array type declarations +type _ [P(T)]struct{} +type _ [P((T))]struct{} +type _ [P * *T]struct{} +type _ [P * T]struct{} +type _ [P(*T)]struct{} +type _ [P(**T)]struct{} +type _ [P * T]struct{} +type _ [P * T - T]struct{} + +type _[ + P *T, +] struct{} + +// equivalent test cases for potentially ambiguous type parameter lists, except +// for function declarations there is no ambiguity (issue #51548) +func _[P *T,]() {} +func _[P *T, _ any]() {} +func _[P (*T),]() {} +func _[P (*T), _ any]() {} +func _[P (T),]() {} +func _[P (T), _ any]() {} + +func _[P *struct{}] () {} +func _[P (*struct{})] () {} +func _[P ([]int)] () {} + +func _ [P(T)]() {} +func _ [P((T))]() {} +func _ [P * *T]() {} +func _ [P * T]() {} +func _ [P(*T)]() {} +func _ [P(**T)]() {} +func _ [P * T]() {} + +func _[ + P *T, +]() {} diff --git a/src/go/types/api.go b/src/go/types/api.go index 2776e05232..f2dcd104d8 100644 --- a/src/go/types/api.go +++ b/src/go/types/api.go @@ -199,12 +199,12 @@ type Info struct { // qualified identifiers are collected in the Uses map. Types map[ast.Expr]TypeAndValue - // Instances maps identifiers denoting parameterized types or functions to - // their type arguments and instantiated type. + // Instances maps identifiers denoting generic types or functions to their + // type arguments and instantiated type. // // For example, Instances will map the identifier for 'T' in the type // instantiation T[int, string] to the type arguments [int, string] and - // resulting instantiated *Named type. Given a parameterized function + // resulting instantiated *Named type. Given a generic function // func F[A any](A), Instances will map the identifier for 'F' in the call // expression F(int(1)) to the inferred type arguments [int], and resulting // instantiated *Signature. @@ -417,25 +417,45 @@ func (conf *Config) Check(path string, fset *token.FileSet, files []*ast.File, i } // AssertableTo reports whether a value of type V can be asserted to have type T. +// +// The behavior of AssertableTo is undefined in two cases: +// - if V is a generalized interface; i.e., an interface that may only be used +// as a type constraint in Go code +// - if T is an uninstantiated generic type func AssertableTo(V *Interface, T Type) bool { - m, _ := (*Checker)(nil).assertableTo(V, T) - return m == nil + // Checker.newAssertableTo suppresses errors for invalid types, so we need special + // handling here. + if T.Underlying() == Typ[Invalid] { + return false + } + return (*Checker)(nil).newAssertableTo(V, T) == nil } -// AssignableTo reports whether a value of type V is assignable to a variable of type T. +// AssignableTo reports whether a value of type V is assignable to a variable +// of type T. +// +// The behavior of AssignableTo is undefined if V or T is an uninstantiated +// generic type. func AssignableTo(V, T Type) bool { x := operand{mode: value, typ: V} ok, _ := x.assignableTo(nil, T, nil) // check not needed for non-constant x return ok } -// ConvertibleTo reports whether a value of type V is convertible to a value of type T. +// ConvertibleTo reports whether a value of type V is convertible to a value of +// type T. +// +// The behavior of ConvertibleTo is undefined if V or T is an uninstantiated +// generic type. func ConvertibleTo(V, T Type) bool { x := operand{mode: value, typ: V} return x.convertibleTo(nil, T, nil) // check not needed for non-constant x } // Implements reports whether type V implements interface T. +// +// The behavior of Implements is undefined if V is an uninstantiated generic +// type. func Implements(V Type, T *Interface) bool { if T.Empty() { // All types (even Typ[Invalid]) implement the empty interface. diff --git a/src/go/types/api_test.go b/src/go/types/api_test.go index a18ee16c7b..4014201769 100644 --- a/src/go/types/api_test.go +++ b/src/go/types/api_test.go @@ -16,6 +16,7 @@ import ( "internal/testenv" "reflect" "regexp" + "sort" "strings" "testing" @@ -434,129 +435,146 @@ func TestTypesInfo(t *testing.T) { } func TestInstanceInfo(t *testing.T) { - var tests = []struct { - src string - name string - targs []string - typ string - }{ - {`package p0; func f[T any](T) {}; func _() { f(42) }`, - `f`, - []string{`int`}, - `func(int)`, - }, - {`package p1; func f[T any](T) T { panic(0) }; func _() { f('@') }`, - `f`, - []string{`rune`}, - `func(rune) rune`, - }, - {`package p2; func f[T any](...T) T { panic(0) }; func _() { f(0i) }`, - `f`, - []string{`complex128`}, - `func(...complex128) complex128`, - }, - {`package p3; func f[A, B, C any](A, *B, []C) {}; func _() { f(1.2, new(string), []byte{}) }`, - `f`, - []string{`float64`, `string`, `byte`}, - `func(float64, *string, []byte)`, - }, - {`package p4; func f[A, B any](A, *B, ...[]B) {}; func _() { f(1.2, new(byte)) }`, - `f`, - []string{`float64`, `byte`}, - `func(float64, *byte, ...[]byte)`, - }, - - {`package s1; func f[T any, P interface{~*T}](x T) {}; func _(x string) { f(x) }`, - `f`, - []string{`string`, `*string`}, - `func(x string)`, - }, - {`package s2; func f[T any, P interface{~*T}](x []T) {}; func _(x []int) { f(x) }`, - `f`, - []string{`int`, `*int`}, - `func(x []int)`, - }, - {`package s3; type C[T any] interface{~chan<- T}; func f[T any, P C[T]](x []T) {}; func _(x []int) { f(x) }`, - `f`, - []string{`int`, `chan<- int`}, - `func(x []int)`, - }, - {`package s4; type C[T any] interface{~chan<- T}; func f[T any, P C[T], Q C[[]*P]](x []T) {}; func _(x []int) { f(x) }`, - `f`, - []string{`int`, `chan<- int`, `chan<- []*chan<- int`}, - `func(x []int)`, - }, - - {`package t1; func f[T any, P interface{~*T}]() T { panic(0) }; func _() { _ = f[string] }`, - `f`, - []string{`string`, `*string`}, - `func() string`, - }, - {`package t2; func f[T any, P interface{~*T}]() T { panic(0) }; func _() { _ = (f[string]) }`, - `f`, - []string{`string`, `*string`}, - `func() string`, - }, - {`package t3; type C[T any] interface{~chan<- T}; func f[T any, P C[T]]() []T { return nil }; func _() { _ = f[int] }`, - `f`, - []string{`int`, `chan<- int`}, - `func() []int`, - }, - {`package t4; type C[T any] interface{~chan<- T}; func f[T any, P C[T], Q C[[]*P]]() []T { return nil }; func _() { _ = f[int] }`, - `f`, - []string{`int`, `chan<- int`, `chan<- []*chan<- int`}, - `func() []int`, - }, - {`package i0; import "lib"; func _() { lib.F(42) }`, - `F`, - []string{`int`}, - `func(int)`, - }, - {`package type0; type T[P interface{~int}] struct{ x P }; var _ T[int]`, - `T`, - []string{`int`}, - `struct{x int}`, - }, - {`package type1; type T[P interface{~int}] struct{ x P }; var _ (T[int])`, - `T`, - []string{`int`}, - `struct{x int}`, - }, - {`package type2; type T[P interface{~int}] struct{ x P }; var _ T[(int)]`, - `T`, - []string{`int`}, - `struct{x int}`, - }, - {`package type3; type T[P1 interface{~[]P2}, P2 any] struct{ x P1; y P2 }; var _ T[[]int, int]`, - `T`, - []string{`[]int`, `int`}, - `struct{x []int; y int}`, - }, - {`package type4; import "lib"; var _ lib.T[int]`, - `T`, - []string{`int`}, - `[]int`, - }, - } - - for _, test := range tests { - const lib = `package lib + const lib = `package lib func F[P any](P) {} type T[P any] []P ` + type testInst struct { + name string + targs []string + typ string + } + + var tests = []struct { + src string + instances []testInst // recorded instances in source order + }{ + {`package p0; func f[T any](T) {}; func _() { f(42) }`, + []testInst{{`f`, []string{`int`}, `func(int)`}}, + }, + {`package p1; func f[T any](T) T { panic(0) }; func _() { f('@') }`, + []testInst{{`f`, []string{`rune`}, `func(rune) rune`}}, + }, + {`package p2; func f[T any](...T) T { panic(0) }; func _() { f(0i) }`, + []testInst{{`f`, []string{`complex128`}, `func(...complex128) complex128`}}, + }, + {`package p3; func f[A, B, C any](A, *B, []C) {}; func _() { f(1.2, new(string), []byte{}) }`, + []testInst{{`f`, []string{`float64`, `string`, `byte`}, `func(float64, *string, []byte)`}}, + }, + {`package p4; func f[A, B any](A, *B, ...[]B) {}; func _() { f(1.2, new(byte)) }`, + []testInst{{`f`, []string{`float64`, `byte`}, `func(float64, *byte, ...[]byte)`}}, + }, + + {`package s1; func f[T any, P interface{*T}](x T) {}; func _(x string) { f(x) }`, + []testInst{{`f`, []string{`string`, `*string`}, `func(x string)`}}, + }, + {`package s2; func f[T any, P interface{*T}](x []T) {}; func _(x []int) { f(x) }`, + []testInst{{`f`, []string{`int`, `*int`}, `func(x []int)`}}, + }, + {`package s3; type C[T any] interface{chan<- T}; func f[T any, P C[T]](x []T) {}; func _(x []int) { f(x) }`, + []testInst{ + {`C`, []string{`T`}, `interface{chan<- T}`}, + {`f`, []string{`int`, `chan<- int`}, `func(x []int)`}, + }, + }, + {`package s4; type C[T any] interface{chan<- T}; func f[T any, P C[T], Q C[[]*P]](x []T) {}; func _(x []int) { f(x) }`, + []testInst{ + {`C`, []string{`T`}, `interface{chan<- T}`}, + {`C`, []string{`[]*P`}, `interface{chan<- []*P}`}, + {`f`, []string{`int`, `chan<- int`, `chan<- []*chan<- int`}, `func(x []int)`}, + }, + }, + + {`package t1; func f[T any, P interface{*T}]() T { panic(0) }; func _() { _ = f[string] }`, + []testInst{{`f`, []string{`string`, `*string`}, `func() string`}}, + }, + {`package t2; func f[T any, P interface{*T}]() T { panic(0) }; func _() { _ = (f[string]) }`, + []testInst{{`f`, []string{`string`, `*string`}, `func() string`}}, + }, + {`package t3; type C[T any] interface{chan<- T}; func f[T any, P C[T], Q C[[]*P]]() []T { return nil }; func _() { _ = f[int] }`, + []testInst{ + {`C`, []string{`T`}, `interface{chan<- T}`}, + {`C`, []string{`[]*P`}, `interface{chan<- []*P}`}, + {`f`, []string{`int`, `chan<- int`, `chan<- []*chan<- int`}, `func() []int`}, + }, + }, + {`package t4; type C[T any] interface{chan<- T}; func f[T any, P C[T], Q C[[]*P]]() []T { return nil }; func _() { _ = (f[int]) }`, + []testInst{ + {`C`, []string{`T`}, `interface{chan<- T}`}, + {`C`, []string{`[]*P`}, `interface{chan<- []*P}`}, + {`f`, []string{`int`, `chan<- int`, `chan<- []*chan<- int`}, `func() []int`}, + }, + }, + {`package i0; import "lib"; func _() { lib.F(42) }`, + []testInst{{`F`, []string{`int`}, `func(int)`}}, + }, + + {`package duplfunc0; func f[T any](T) {}; func _() { f(42); f("foo"); f[int](3) }`, + []testInst{ + {`f`, []string{`int`}, `func(int)`}, + {`f`, []string{`string`}, `func(string)`}, + {`f`, []string{`int`}, `func(int)`}, + }, + }, + {`package duplfunc1; import "lib"; func _() { lib.F(42); lib.F("foo"); lib.F(3) }`, + []testInst{ + {`F`, []string{`int`}, `func(int)`}, + {`F`, []string{`string`}, `func(string)`}, + {`F`, []string{`int`}, `func(int)`}, + }, + }, + + {`package type0; type T[P interface{~int}] struct{ x P }; var _ T[int]`, + []testInst{{`T`, []string{`int`}, `struct{x int}`}}, + }, + {`package type1; type T[P interface{~int}] struct{ x P }; var _ (T[int])`, + []testInst{{`T`, []string{`int`}, `struct{x int}`}}, + }, + {`package type2; type T[P interface{~int}] struct{ x P }; var _ T[(int)]`, + []testInst{{`T`, []string{`int`}, `struct{x int}`}}, + }, + {`package type3; type T[P1 interface{~[]P2}, P2 any] struct{ x P1; y P2 }; var _ T[[]int, int]`, + []testInst{{`T`, []string{`[]int`, `int`}, `struct{x []int; y int}`}}, + }, + {`package type4; import "lib"; var _ lib.T[int]`, + []testInst{{`T`, []string{`int`}, `[]int`}}, + }, + + {`package dupltype0; type T[P interface{~int}] struct{ x P }; var x T[int]; var y T[int]`, + []testInst{ + {`T`, []string{`int`}, `struct{x int}`}, + {`T`, []string{`int`}, `struct{x int}`}, + }, + }, + {`package dupltype1; type T[P ~int] struct{ x P }; func (r *T[Q]) add(z T[Q]) { r.x += z.x }`, + []testInst{ + {`T`, []string{`Q`}, `struct{x Q}`}, + {`T`, []string{`Q`}, `struct{x Q}`}, + }, + }, + {`package dupltype1; import "lib"; var x lib.T[int]; var y lib.T[int]; var z lib.T[string]`, + []testInst{ + {`T`, []string{`int`}, `[]int`}, + {`T`, []string{`int`}, `[]int`}, + {`T`, []string{`string`}, `[]string`}, + }, + }, + } + + for _, test := range tests { imports := make(testImporter) conf := Config{Importer: imports} - instances := make(map[*ast.Ident]Instance) - uses := make(map[*ast.Ident]Object) + instMap := make(map[*ast.Ident]Instance) + useMap := make(map[*ast.Ident]Object) makePkg := func(src string) *Package { f, err := parser.ParseFile(fset, "p.go", src, 0) if err != nil { t.Fatal(err) } - pkg, err := conf.Check("", fset, []*ast.File{f}, &Info{Instances: instances, Uses: uses}) + pkg, err := conf.Check("", fset, []*ast.File{f}, &Info{Instances: instMap, Uses: useMap}) if err != nil { t.Fatal(err) } @@ -566,60 +584,71 @@ type T[P any] []P makePkg(lib) pkg := makePkg(test.src) - // look for instance information - var targs []Type - var typ Type - for ident, inst := range instances { - if ExprString(ident) == test.name { - for i := 0; i < inst.TypeArgs.Len(); i++ { - targs = append(targs, inst.TypeArgs.At(i)) - } - typ = inst.Type + t.Run(pkg.Name(), func(t *testing.T) { + // Sort instances in source order for stability. + instances := sortedInstances(instMap) + if got, want := len(instances), len(test.instances); got != want { + t.Fatalf("got %d instances, want %d", got, want) + } - // Check that we can find the corresponding parameterized type. - ptype := uses[ident].Type() + // Pairwise compare with the expected instances. + for ii, inst := range instances { + var targs []Type + for i := 0; i < inst.Inst.TypeArgs.Len(); i++ { + targs = append(targs, inst.Inst.TypeArgs.At(i)) + } + typ := inst.Inst.Type + + testInst := test.instances[ii] + if got := inst.Ident.Name; got != testInst.name { + t.Fatalf("got name %s, want %s", got, testInst.name) + } + if len(targs) != len(testInst.targs) { + t.Fatalf("got %d type arguments; want %d", len(targs), len(testInst.targs)) + } + for i, targ := range targs { + if got := targ.String(); got != testInst.targs[i] { + t.Errorf("type argument %d: got %s; want %s", i, got, testInst.targs[i]) + } + } + if got := typ.Underlying().String(); got != testInst.typ { + t.Errorf("package %s: got %s; want %s", pkg.Name(), got, testInst.typ) + } + + // Verify the invariant that re-instantiating the corresponding generic + // type with TypeArgs results in an identical instance. + ptype := useMap[inst.Ident].Type() lister, _ := ptype.(interface{ TypeParams() *TypeParamList }) if lister == nil || lister.TypeParams().Len() == 0 { - t.Errorf("package %s: info.Types[%v] = %v, want parameterized type", pkg.Name(), ident, ptype) - continue + t.Fatalf("info.Types[%v] = %v, want parameterized type", inst.Ident, ptype) } - - // Verify the invariant that re-instantiating the generic type with - // TypeArgs results in an equivalent type. inst2, err := Instantiate(nil, ptype, targs, true) if err != nil { t.Errorf("Instantiate(%v, %v) failed: %v", ptype, targs, err) } - if !Identical(inst.Type, inst2) { - t.Errorf("%v and %v are not identical", inst.Type, inst2) + if !Identical(inst.Inst.Type, inst2) { + t.Errorf("%v and %v are not identical", inst.Inst.Type, inst2) } - break } - } - if targs == nil { - t.Errorf("package %s: no instance information found for %s", pkg.Name(), test.name) - continue - } - - // check that type arguments are correct - if len(targs) != len(test.targs) { - t.Errorf("package %s: got %d type arguments; want %d", pkg.Name(), len(targs), len(test.targs)) - continue - } - for i, targ := range targs { - if got := targ.String(); got != test.targs[i] { - t.Errorf("package %s, %d. type argument: got %s; want %s", pkg.Name(), i, got, test.targs[i]) - continue - } - } - - // check that the types match - if got := typ.Underlying().String(); got != test.typ { - t.Errorf("package %s: got %s; want %s", pkg.Name(), got, test.typ) - } + }) } } +type recordedInstance struct { + Ident *ast.Ident + Inst Instance +} + +func sortedInstances(m map[*ast.Ident]Instance) (instances []recordedInstance) { + for id, inst := range m { + instances = append(instances, recordedInstance{id, inst}) + } + sort.Slice(instances, func(i, j int) bool { + return instances[i].Ident.Pos() < instances[j].Ident.Pos() + }) + return instances +} + func TestDefsInfo(t *testing.T) { var tests = []struct { src string @@ -1688,7 +1717,7 @@ func F(){ var F = /*F=func:12*/ F /*F=var:17*/ ; _ = F var a []int - for i, x := range /*i=undef*/ /*x=var:16*/ a /*i=var:20*/ /*x=var:20*/ { _ = i; _ = x } + for i, x := range a /*i=undef*/ /*x=var:16*/ { _ = i; _ = x } var i interface{} switch y := i.(type) { /*y=undef*/ @@ -2306,27 +2335,27 @@ type Bad Bad // invalid type conf := Config{Error: func(error) {}} pkg, _ := conf.Check(f.Name.Name, fset, []*ast.File{f}, nil) - scope := pkg.Scope() + lookup := func(tname string) Type { return pkg.Scope().Lookup(tname).Type() } var ( - EmptyIface = scope.Lookup("EmptyIface").Type().Underlying().(*Interface) - I = scope.Lookup("I").Type().(*Named) + EmptyIface = lookup("EmptyIface").Underlying().(*Interface) + I = lookup("I").(*Named) II = I.Underlying().(*Interface) - C = scope.Lookup("C").Type().(*Named) + C = lookup("C").(*Named) CI = C.Underlying().(*Interface) - Integer = scope.Lookup("Integer").Type().Underlying().(*Interface) - EmptyTypeSet = scope.Lookup("EmptyTypeSet").Type().Underlying().(*Interface) - N1 = scope.Lookup("N1").Type() + Integer = lookup("Integer").Underlying().(*Interface) + EmptyTypeSet = lookup("EmptyTypeSet").Underlying().(*Interface) + N1 = lookup("N1") N1p = NewPointer(N1) - N2 = scope.Lookup("N2").Type() + N2 = lookup("N2") N2p = NewPointer(N2) - N3 = scope.Lookup("N3").Type() - N4 = scope.Lookup("N4").Type() - Bad = scope.Lookup("Bad").Type() + N3 = lookup("N3") + N4 = lookup("N4") + Bad = lookup("Bad") ) tests := []struct { - t Type - i *Interface + V Type + T *Interface want bool }{ {I, II, true}, @@ -2357,8 +2386,20 @@ type Bad Bad // invalid type } for _, test := range tests { - if got := Implements(test.t, test.i); got != test.want { - t.Errorf("Implements(%s, %s) = %t, want %t", test.t, test.i, got, test.want) + if got := Implements(test.V, test.T); got != test.want { + t.Errorf("Implements(%s, %s) = %t, want %t", test.V, test.T, got, test.want) + } + + // The type assertion x.(T) is valid if T is an interface or if T implements the type of x. + // The assertion is never valid if T is a bad type. + V := test.T + T := test.V + want := false + if _, ok := T.Underlying().(*Interface); (ok || Implements(T, V)) && T != Bad { + want = true + } + if got := AssertableTo(V, T); got != want { + t.Errorf("AssertableTo(%s, %s) = %t, want %t", V, T, got, want) } } } diff --git a/src/go/types/assignments.go b/src/go/types/assignments.go index f75b8b6f6b..f5e22c2f67 100644 --- a/src/go/types/assignments.go +++ b/src/go/types/assignments.go @@ -290,15 +290,14 @@ func (check *Checker) typesSummary(list []Type, variadic bool) string { return "(" + strings.Join(res, ", ") + ")" } -func (check *Checker) assignError(rhs []ast.Expr, nvars, nvals int) { - measure := func(x int, unit string) string { - s := fmt.Sprintf("%d %s", x, unit) - if x != 1 { - s += "s" - } - return s +func measure(x int, unit string) string { + if x != 1 { + unit += "s" } + return fmt.Sprintf("%d %s", x, unit) +} +func (check *Checker) assignError(rhs []ast.Expr, nvars, nvals int) { vars := measure(nvars, "variable") vals := measure(nvals, "value") rhs0 := rhs[0] diff --git a/src/go/types/builtins.go b/src/go/types/builtins.go index 8fcfcb935f..c81e73c828 100644 --- a/src/go/types/builtins.go +++ b/src/go/types/builtins.go @@ -83,10 +83,24 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b // of S and the respective parameter passing rules apply." S := x.typ var T Type - if s, _ := structuralType(S).(*Slice); s != nil { + if s, _ := coreType(S).(*Slice); s != nil { T = s.elem } else { - check.invalidArg(x, _InvalidAppend, "%s is not a slice", x) + var cause string + switch { + case x.isNil(): + cause = "have untyped nil" + case isTypeParam(S): + if u := coreType(S); u != nil { + cause = check.sprintf("%s has core type %s", x, u) + } else { + cause = check.sprintf("%s has no core type", x) + } + default: + cause = check.sprintf("have %s", x) + } + // don't use Checker.invalidArg here as it would repeat "argument" in the error message + check.errorf(x, _InvalidAppend, "first argument to append must be a slice; %s", cause) return } @@ -102,7 +116,7 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b if x.mode == invalid { return } - if t := structuralString(x.typ); t != nil && isString(t) { + if t := coreString(x.typ); t != nil && isString(t) { if check.Types != nil { sig := makeSig(S, S, x.typ) sig.variadic = true @@ -336,14 +350,14 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b case _Copy: // copy(x, y []T) int - dst, _ := structuralType(x.typ).(*Slice) + dst, _ := coreType(x.typ).(*Slice) var y operand arg(&y, 1) if y.mode == invalid { return } - src0 := structuralString(y.typ) + src0 := coreString(y.typ) if src0 != nil && isString(src0) { src0 = NewSlice(universeByte) } @@ -481,13 +495,13 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b } var min int // minimum number of arguments - switch structuralType(T).(type) { + switch coreType(T).(type) { case *Slice: min = 2 case *Map, *Chan: min = 1 case nil: - check.errorf(arg0, _InvalidMake, "cannot make %s: no structural type", arg0) + check.errorf(arg0, _InvalidMake, "cannot make %s: no core type", arg0) return default: check.invalidArg(arg0, _InvalidMake, "cannot make %s; type must be slice, map, or channel", arg0) diff --git a/src/go/types/call.go b/src/go/types/call.go index aa87c48a65..5d1f60d432 100644 --- a/src/go/types/call.go +++ b/src/go/types/call.go @@ -171,7 +171,7 @@ func (check *Checker) callExpr(x *operand, call *ast.CallExpr) exprKind { cgocall := x.mode == cgofunc // a type parameter may be "called" if all types have the same signature - sig, _ := structuralType(x.typ).(*Signature) + sig, _ := coreType(x.typ).(*Signature) if sig == nil { check.invalidOp(x, _InvalidCall, "cannot call non-function %s", x) x.mode = invalid @@ -429,7 +429,7 @@ var cgoPrefixes = [...]string{ "_Cmacro_", // function to evaluate the expanded expression } -func (check *Checker) selector(x *operand, e *ast.SelectorExpr) { +func (check *Checker) selector(x *operand, e *ast.SelectorExpr, def *Named) { // these must be declared before the "goto Error" statements var ( obj Object @@ -527,7 +527,18 @@ func (check *Checker) selector(x *operand, e *ast.SelectorExpr) { } check.exprOrType(x, e.X, false) - if x.mode == invalid { + switch x.mode { + case typexpr: + // don't crash for "type T T.x" (was issue #51509) + if def != nil && x.typ == def { + check.cycleError([]Object{def.obj}) + goto Error + } + case builtin: + // types2 uses the position of '.' for the error + check.errorf(e.Sel, _UncalledBuiltin, "cannot select on %s", x) + goto Error + case invalid: goto Error } diff --git a/src/go/types/check.go b/src/go/types/check.go index a0c3700254..23136377c8 100644 --- a/src/go/types/check.go +++ b/src/go/types/check.go @@ -24,19 +24,6 @@ const ( compilerErrorMessages = false // match compiler error messages ) -// If forceStrict is set, the type-checker enforces additional -// rules not specified by the Go 1 spec, but which will -// catch guaranteed run-time errors if the respective -// code is executed. In other words, programs passing in -// strict mode are Go 1 compliant, but not all Go 1 programs -// will pass in strict mode. The additional rules are: -// -// - A type assertion x.(T) where T is an interface type -// is invalid if any (statically known) method that exists -// for both x and T have different signatures. -// -const forceStrict = false - // exprInfo stores information about an untyped expression. type exprInfo struct { isLhs bool // expression is lhs operand of a shift with delayed type-check @@ -146,7 +133,7 @@ type Checker struct { untyped map[ast.Expr]exprInfo // map of expressions without final type delayed []action // stack of delayed action segments; segments are processed in FIFO order objPath []Object // path of object dependencies during type inference (for cycle reporting) - defTypes []*Named // defined types created during type checking, for final validation. + cleaners []cleaner // list of types that may need a final cleanup at the end of type-checking // environment within which the current object is type-checked (valid only // for the duration of type-checking a specific object) @@ -225,6 +212,16 @@ func (check *Checker) pop() Object { return obj } +type cleaner interface { + cleanup() +} + +// needsCleanup records objects/types that implement the cleanup method +// which will be called at the end of type-checking. +func (check *Checker) needsCleanup(c cleaner) { + check.cleaners = append(check.cleaners, c) +} + // NewChecker returns a new Checker instance for a given package. // Package files may be added incrementally via checker.Files. func NewChecker(conf *Config, fset *token.FileSet, pkg *Package, info *Info) *Checker { @@ -268,6 +265,8 @@ func (check *Checker) initFiles(files []*ast.File) { check.methods = nil check.untyped = nil check.delayed = nil + check.objPath = nil + check.cleaners = nil // determine package name and collect valid files pkg := check.pkg @@ -317,22 +316,37 @@ func (check *Checker) checkFiles(files []*ast.File) (err error) { defer check.handleBailout(&err) + print := func(msg string) { + if trace { + fmt.Println() + fmt.Println(msg) + } + } + + print("== initFiles ==") check.initFiles(files) + print("== collectObjects ==") check.collectObjects() + print("== packageObjects ==") check.packageObjects() + print("== processDelayed ==") check.processDelayed(0) // incl. all functions - check.expandDefTypes() + print("== cleanup ==") + check.cleanup() + print("== initOrder ==") check.initOrder() if !check.conf.DisableUnusedImportCheck { + print("== unusedImports ==") check.unusedImports() } + print("== recordUntyped ==") check.recordUntyped() if check.firstErr == nil { @@ -350,7 +364,6 @@ func (check *Checker) checkFiles(files []*ast.File) (err error) { check.recvTParamMap = nil check.brokenAliases = nil check.unionTypeSets = nil - check.defTypes = nil check.ctxt = nil // TODO(rFindley) There's more memory we should release at this point. @@ -378,27 +391,13 @@ func (check *Checker) processDelayed(top int) { check.delayed = check.delayed[:top] } -func (check *Checker) expandDefTypes() { - // Ensure that every defined type created in the course of type-checking has - // either non-*Named underlying, or is unresolved. - // - // This guarantees that we don't leak any types whose underlying is *Named, - // because any unresolved instances will lazily compute their underlying by - // substituting in the underlying of their origin. The origin must have - // either been imported or type-checked and expanded here, and in either case - // its underlying will be fully expanded. - for i := 0; i < len(check.defTypes); i++ { - n := check.defTypes[i] - switch n.underlying.(type) { - case nil: - if n.resolver == nil { - panic("nil underlying") - } - case *Named: - n.under() // n.under may add entries to check.defTypes - } - n.check = nil +// cleanup runs cleanup for all collected cleaners. +func (check *Checker) cleanup() { + // Don't use a range clause since Named.cleanup may add more cleaners. + for i := 0; i < len(check.cleaners); i++ { + check.cleaners[i].cleanup() } + check.cleaners = nil } func (check *Checker) record(x *operand) { diff --git a/src/go/types/conversions.go b/src/go/types/conversions.go index 84741359c0..c5a69cddf4 100644 --- a/src/go/types/conversions.go +++ b/src/go/types/conversions.go @@ -48,11 +48,14 @@ func (check *Checker) conversion(x *operand, T Type) { // have specific types, constant x cannot be // converted. ok = T.(*TypeParam).underIs(func(u Type) bool { - // t is nil if there are no specific type terms + // u is nil if there are no specific type terms if u == nil { cause = check.sprintf("%s does not contain specific types", T) return false } + if isString(x.typ) && isBytesOrRunes(u) { + return true + } if !constConvertibleTo(u, nil) { cause = check.sprintf("cannot convert %s to %s (in %s)", x, u, T) return false diff --git a/src/go/types/decl.go b/src/go/types/decl.go index cd6f709a56..93a37d76ce 100644 --- a/src/go/types/decl.go +++ b/src/go/types/decl.go @@ -624,7 +624,6 @@ func (check *Checker) collectTypeParams(dst **TypeParamList, list *ast.FieldList }() index := 0 - var bounds []Type for _, f := range list.List { var bound Type // NOTE: we may be able to assert that f.Type != nil here, but this is not @@ -642,7 +641,6 @@ func (check *Checker) collectTypeParams(dst **TypeParamList, list *ast.FieldList } else { bound = Typ[Invalid] } - bounds = append(bounds, bound) for i := range f.Names { tparams[index+i].bound = bound } diff --git a/src/go/types/errorcodes.go b/src/go/types/errorcodes.go index 51f091a9cb..64cf24c96a 100644 --- a/src/go/types/errorcodes.go +++ b/src/go/types/errorcodes.go @@ -98,13 +98,10 @@ const ( // _InvalidDeclCycle occurs when a declaration cycle is not valid. // // Example: - // import "unsafe" - // - // type T struct { - // a [n]int + // type S struct { + // S // } // - // var n = unsafe.Sizeof(T{}) _InvalidDeclCycle // _InvalidTypeCycle occurs when a cycle in type definitions results in a @@ -1342,11 +1339,6 @@ const ( // func _() { // f() // } - // - // Example: - // type N[P, Q any] struct{} - // - // var _ N[int] _CannotInferTypeArgs // _InvalidTypeArg occurs when a type argument does not satisfy its diff --git a/src/go/types/errors.go b/src/go/types/errors.go index a1786ec0ff..fade8630e0 100644 --- a/src/go/types/errors.go +++ b/src/go/types/errors.go @@ -110,6 +110,17 @@ func sprintf(fset *token.FileSet, qf Qualifier, debug bool, format string, args } buf.WriteByte(']') arg = buf.String() + case []*TypeParam: + var buf bytes.Buffer + buf.WriteByte('[') + for i, x := range a { + if i > 0 { + buf.WriteString(", ") + } + buf.WriteString(typeString(x, qf, debug)) // use typeString so we get subscripts when debugging + } + buf.WriteByte(']') + arg = buf.String() } args[i] = arg } diff --git a/src/go/types/eval.go b/src/go/types/eval.go index c8bb005eb6..5700cbf79c 100644 --- a/src/go/types/eval.go +++ b/src/go/types/eval.go @@ -37,8 +37,8 @@ func Eval(fset *token.FileSet, pkg *Package, pos token.Pos, expr string) (_ Type // CheckExpr type checks the expression expr as if it had appeared at position // pos of package pkg. Type information about the expression is recorded in -// info. The expression may be an uninstantiated parameterized function or -// type. +// info. The expression may be an identifier denoting an uninstantiated generic +// function or type. // // If pkg == nil, the Universe scope is used and the provided // position pos is ignored. If pkg != nil, and pos is invalid, diff --git a/src/go/types/expr.go b/src/go/types/expr.go index 0d21a592f9..e24bd60dc3 100644 --- a/src/go/types/expr.go +++ b/src/go/types/expr.go @@ -173,9 +173,9 @@ func (check *Checker) unary(x *operand, e *ast.UnaryExpr) { return case token.ARROW: - u := structuralType(x.typ) + u := coreType(x.typ) if u == nil { - check.invalidOp(x, _InvalidReceive, "cannot receive from %s: no structural type", x) + check.invalidOp(x, _InvalidReceive, "cannot receive from %s: no core type", x) x.mode = invalid return } @@ -859,7 +859,7 @@ func (check *Checker) incomparableCause(typ Type) string { } // see if we can extract a more specific error var cause string - comparable(typ, nil, func(format string, args ...interface{}) { + comparable(typ, true, nil, func(format string, args ...interface{}) { cause = check.sprintf(format, args...) }) return cause @@ -1338,7 +1338,11 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind { case hint != nil: // no composite literal type present - use hint (element type of enclosing type) typ = hint - base, _ = deref(structuralType(typ)) // *T implies &T{} + base, _ = deref(coreType(typ)) // *T implies &T{} + if base == nil { + check.errorf(e, _InvalidLit, "invalid composite literal element type %s: no core type", typ) + goto Error + } default: // TODO(gri) provide better error messages depending on context @@ -1346,7 +1350,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind { goto Error } - switch utyp := structuralType(base).(type) { + switch utyp := coreType(base).(type) { case *Struct: // Prevent crash if the struct referred to is not yet set up. // See analogous comment for *Array. @@ -1529,7 +1533,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind { return kind case *ast.SelectorExpr: - check.selector(x, e) + check.selector(x, e, nil) case *ast.IndexExpr, *ast.IndexListExpr: ix := typeparams.UnpackIndexExpr(e) @@ -1584,6 +1588,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind { case invalid: goto Error case typexpr: + check.validVarType(e.X, x.typ) x.typ = &Pointer{base: x.typ} default: var base Type diff --git a/src/go/types/index.go b/src/go/types/index.go index db4732c8e0..33075edaf1 100644 --- a/src/go/types/index.go +++ b/src/go/types/index.go @@ -183,6 +183,7 @@ func (check *Checker) indexExpr(x *operand, e *typeparams.IndexExpr) (isFuncInst } if !valid { + // types2 uses the position of '[' for the error check.invalidOp(x, _NonIndexableOperand, "cannot index %s", x) x.mode = invalid return false @@ -214,9 +215,9 @@ func (check *Checker) sliceExpr(x *operand, e *ast.SliceExpr) { valid := false length := int64(-1) // valid if >= 0 - switch u := structuralString(x.typ).(type) { + switch u := coreString(x.typ).(type) { case nil: - check.invalidOp(x, _NonSliceableOperand, "cannot slice %s: %s has no structural type", x, x.typ) + check.invalidOp(x, _NonSliceableOperand, "cannot slice %s: %s has no core type", x, x.typ) x.mode = invalid return diff --git a/src/go/types/infer.go b/src/go/types/infer.go index 6a9a662565..6bed55c270 100644 --- a/src/go/types/infer.go +++ b/src/go/types/infer.go @@ -40,6 +40,13 @@ func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type, }() } + if traceInference { + check.dump("-- inferA %s%s ➞ %s", tparams, params, targs) + defer func() { + check.dump("=> inferA %s ➞ %s", tparams, result) + }() + } + // There must be at least one type parameter, and no more type arguments than type parameters. n := len(tparams) assert(n > 0 && len(targs) <= n) @@ -53,6 +60,64 @@ func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type, } // len(targs) < n + const enableTparamRenaming = true + if enableTparamRenaming { + // For the purpose of type inference we must differentiate type parameters + // occurring in explicit type or value function arguments from the type + // parameters we are solving for via unification, because they may be the + // same in self-recursive calls. For example: + // + // func f[P *Q, Q any](p P, q Q) { + // f(p) + // } + // + // In this example, the fact that the P used in the instantation f[P] has + // the same pointer identity as the P we are trying to solve for via + // unification is coincidental: there is nothing special about recursive + // calls that should cause them to conflate the identity of type arguments + // with type parameters. To put it another way: any such self-recursive + // call is equivalent to a mutually recursive call, which does not run into + // any problems of type parameter identity. For example, the following code + // is equivalent to the code above. + // + // func f[P interface{*Q}, Q any](p P, q Q) { + // f2(p) + // } + // + // func f2[P interface{*Q}, Q any](p P, q Q) { + // f(p) + // } + // + // We can turn the first example into the second example by renaming type + // parameters in the original signature to give them a new identity. As an + // optimization, we do this only for self-recursive calls. + + // We can detect if we are in a self-recursive call by comparing the + // identity of the first type parameter in the current function with the + // first type parameter in tparams. This works because type parameters are + // unique to their type parameter list. + selfRecursive := check.sig != nil && check.sig.tparams.Len() > 0 && tparams[0] == check.sig.tparams.At(0) + + if selfRecursive { + // In self-recursive inference, rename the type parameters with new type + // parameters that are the same but for their pointer identity. + tparams2 := make([]*TypeParam, len(tparams)) + for i, tparam := range tparams { + tname := NewTypeName(tparam.Obj().Pos(), tparam.Obj().Pkg(), tparam.Obj().Name(), nil) + tparams2[i] = NewTypeParam(tname, nil) + tparams2[i].index = tparam.index // == i + } + + renameMap := makeRenameMap(tparams, tparams2) + for i, tparam := range tparams { + tparams2[i].bound = check.subst(posn.Pos(), tparam.bound, renameMap, nil) + } + + tparams = tparams2 + params = check.subst(posn.Pos(), params, renameMap, nil).(*Tuple) + } + } + // If we have more than 2 arguments, we may have arguments with named and unnamed types. // If that is the case, permutate params and args such that the arguments with named // types are first in the list. This doesn't affect type inference if all types are taken @@ -402,6 +467,13 @@ func (w *tpWalker) isParameterizedTypeList(list []Type) bool { func (check *Checker) inferB(posn positioner, tparams []*TypeParam, targs []Type) (types []Type, index int) { assert(len(tparams) >= len(targs) && len(targs) > 0) + if traceInference { + check.dump("-- inferB %s ➞ %s", tparams, targs) + defer func() { + check.dump("=> inferB %s ➞ %s", tparams, types) + }() + } + // Setup bidirectional unification between constraints // and the corresponding type arguments (which may be nil!). u := newUnifier(false) @@ -415,27 +487,88 @@ func (check *Checker) inferB(posn positioner, tparams []*TypeParam, targs []Type } } - // If a constraint has a structural type, unify the corresponding type parameter with it. - for _, tpar := range tparams { - sbound := structuralType(tpar) - if sbound != nil { - // If the structural type is the underlying type of a single - // defined type in the constraint, use that defined type instead. - if named, _ := tpar.singleType().(*Named); named != nil { - sbound = named - } - if !u.unify(tpar, sbound) { - // TODO(gri) improve error message by providing the type arguments - // which we know already - check.errorf(posn, _InvalidTypeArg, "%s does not match %s", tpar, sbound) - return nil, 0 + // Repeatedly apply constraint type inference as long as + // there are still unknown type arguments and progress is + // being made. + // + // This is an O(n^2) algorithm where n is the number of + // type parameters: if there is progress (and iteration + // continues), at least one type argument is inferred + // per iteration and we have a doubly nested loop. + // In practice this is not a problem because the number + // of type parameters tends to be very small (< 5 or so). + // (It should be possible for unification to efficiently + // signal newly inferred type arguments; then the loops + // here could handle the respective type parameters only, + // but that will come at a cost of extra complexity which + // may not be worth it.) + for n := u.x.unknowns(); n > 0; { + nn := n + + for i, tpar := range tparams { + // If there is a core term (i.e., a core type with tilde information) + // unify the type parameter with the core type. + if core, single := coreTerm(tpar); core != nil { + // A type parameter can be unified with its core type in two cases. + tx := u.x.at(i) + switch { + case tx != nil: + // The corresponding type argument tx is known. + // In this case, if the core type has a tilde, the type argument's underlying + // type must match the core type, otherwise the type argument and the core type + // must match. + // If tx is an external type parameter, don't consider its underlying type + // (which is an interface). Core type unification will attempt to unify against + // core.typ. + // Note also that even with inexact unification we cannot leave away the under + // call here because it's possible that both tx and core.typ are named types, + // with under(tx) being a (named) basic type matching core.typ. Such cases do + // not match with inexact unification. + if core.tilde && !isTypeParam(tx) { + tx = under(tx) + } + if !u.unify(tx, core.typ) { + // TODO(gri) improve error message by providing the type arguments + // which we know already + // Don't use term.String() as it always qualifies types, even if they + // are in the current package. + tilde := "" + if core.tilde { + tilde = "~" + } + check.errorf(posn, _InvalidTypeArg, "%s does not match %s%s", tpar, tilde, core.typ) + return nil, 0 + } + + case single && !core.tilde: + // The corresponding type argument tx is unknown and there's a single + // specific type and no tilde. + // In this case the type argument must be that single type; set it. + u.x.set(i, core.typ) + + default: + // Unification is not possible and no progress was made. + continue + } + + // The number of known type arguments may have changed. + nn = u.x.unknowns() + if nn == 0 { + break // all type arguments are known + } } } + + assert(nn <= n) + if nn == n { + break // no progress + } + n = nn } // u.x.types() now contains the incoming type arguments plus any additional type - // arguments which were inferred from structural types. The newly inferred non- - // nil entries may still contain references to other type parameters. + // arguments which were inferred from core terms. The newly inferred non-nil + // entries may still contain references to other type parameters. // For instance, for [A any, B interface{ []C }, C interface{ *A }], if A == int // was given, unification produced the type list [int, []C, *A]. We eliminate the // remaining type parameters by substituting the type parameters in this type list @@ -503,8 +636,8 @@ func (check *Checker) inferB(posn positioner, tparams []*TypeParam, targs []Type } // Once nothing changes anymore, we may still have type parameters left; - // e.g., a structural constraint *P may match a type parameter Q but we - // don't have any type arguments to fill in for *P or Q (issue #45548). + // e.g., a constraint with core type *P may match a type parameter Q but + // we don't have any type arguments to fill in for *P or Q (issue #45548). // Don't let such inferences escape, instead nil them out. for i, typ := range types { if typ != nil && isParameterized(tparams, typ) { @@ -524,6 +657,42 @@ func (check *Checker) inferB(posn positioner, tparams []*TypeParam, targs []Type return } +// If the type parameter has a single specific type S, coreTerm returns (S, true). +// Otherwise, if tpar has a core type T, it returns a term corresponding to that +// core type and false. In that case, if any term of tpar has a tilde, the core +// term has a tilde. In all other cases coreTerm returns (nil, false). +func coreTerm(tpar *TypeParam) (*term, bool) { + n := 0 + var single *term // valid if n == 1 + var tilde bool + tpar.is(func(t *term) bool { + if t == nil { + assert(n == 0) + return false // no terms + } + n++ + single = t + if t.tilde { + tilde = true + } + return true + }) + if n == 1 { + if debug { + assert(debug && under(single.typ) == coreType(tpar)) + } + return single, true + } + if typ := coreType(tpar); typ != nil { + // A core type is always an underlying type. + // If any term of tpar has a tilde, we don't + // have a precise core type and we must return + // a tilde as well. + return &term{tilde, typ}, false + } + return nil, false +} + type cycleFinder struct { tparams []*TypeParam types []Type @@ -569,8 +738,6 @@ func (w *cycleFinder) typ(typ Type) { // in signatures where they are handled explicitly. case *Signature: - // There are no "method types" so we should never see a recv. - assert(t.recv == nil) if t.params != nil { w.varList(t.params.vars) } diff --git a/src/go/types/instantiate.go b/src/go/types/instantiate.go index 4aeaeb7f11..a481746657 100644 --- a/src/go/types/instantiate.go +++ b/src/go/types/instantiate.go @@ -15,10 +15,10 @@ import ( // Instantiate instantiates the type orig with the given type arguments targs. // orig must be a *Named or a *Signature type. If there is no error, the -// resulting Type is a new, instantiated (not parameterized) type of the same -// kind (either a *Named or a *Signature). Methods attached to a *Named type -// are also instantiated, and associated with a new *Func that has the same -// position as the original method, but nil function scope. +// resulting Type is an instantiated type of the same kind (either a *Named or +// a *Signature). Methods attached to a *Named type are also instantiated, and +// associated with a new *Func that has the same position as the original +// method, but nil function scope. // // If ctxt is non-nil, it may be used to de-duplicate the instance against // previous instances with the same identity. As a special case, generic @@ -204,7 +204,7 @@ func (check *Checker) implements(V, T Type) error { // If T is comparable, V must be comparable. // Remember as a pending error and report only if we don't have a more specific error. var pending error - if Ti.IsComparable() && ((Vi != nil && !Vi.IsComparable()) || (Vi == nil && !Comparable(V))) { + if Ti.IsComparable() && !comparable(V, false, nil, nil) { pending = errorf("%s does not implement comparable", V) } diff --git a/src/go/types/interface.go b/src/go/types/interface.go index b9d4660eb4..3db3580a91 100644 --- a/src/go/types/interface.go +++ b/src/go/types/interface.go @@ -56,7 +56,7 @@ func NewInterfaceType(methods []*Func, embeddeds []Type) *Interface { } // set method receivers if necessary - typ := new(Interface) + typ := (*Checker)(nil).newInterface() for _, m := range methods { if sig := m.typ.(*Signature); sig.recv == nil { sig.recv = NewVar(m.pos, m.pkg, "", typ) @@ -73,6 +73,15 @@ func NewInterfaceType(methods []*Func, embeddeds []Type) *Interface { return typ } +// check may be nil +func (check *Checker) newInterface() *Interface { + typ := &Interface{check: check} + if check != nil { + check.needsCleanup(typ) + } + return typ +} + // MarkImplicit marks the interface t as implicit, meaning this interface // corresponds to a constraint literal such as ~T or A|B without explicit // interface embedding. MarkImplicit should be called before any concurrent use @@ -141,6 +150,11 @@ func (t *Interface) String() string { return TypeString(t, nil) } // ---------------------------------------------------------------------------- // Implementation +func (t *Interface) cleanup() { + t.check = nil + t.embedPos = nil +} + func (check *Checker) interfaceType(ityp *Interface, iface *ast.InterfaceType, def *Named) { addEmbedded := func(pos token.Pos, typ Type) { ityp.embeddeds = append(ityp.embeddeds, typ) @@ -210,16 +224,10 @@ func (check *Checker) interfaceType(ityp *Interface, iface *ast.InterfaceType, d sortMethods(ityp.methods) // (don't sort embeddeds: they must correspond to *embedPos entries) - // Compute type set with a non-nil *Checker as soon as possible - // to report any errors. Subsequent uses of type sets will use - // this computed type set and won't need to pass in a *Checker. - // - // Pin the checker to the interface type in the interim, in case the type set - // must be used before delayed funcs are processed (see issue #48234). - // TODO(rfindley): clean up use of *Checker with computeInterfaceTypeSet - ityp.check = check + // Compute type set as soon as possible to report any errors. + // Subsequent uses of type sets will use this computed type + // set and won't need to pass in a *Checker. check.later(func() { computeInterfaceTypeSet(check, iface.Pos(), ityp) - ityp.check = nil }).describef(iface, "compute type set for %s", ityp) } diff --git a/src/go/types/lookup.go b/src/go/types/lookup.go index f2f38be266..335fada7b7 100644 --- a/src/go/types/lookup.go +++ b/src/go/types/lookup.go @@ -66,12 +66,13 @@ func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o obj, index, indirect = lookupFieldOrMethod(T, addressable, pkg, name, false) - // If we didn't find anything and if we have a type parameter with a structural constraint, - // see if there is a matching field (but not a method, those need to be declared explicitly - // in the constraint). If the structural constraint is a named pointer type (see above), we - // are ok here because only fields are accepted as results. - if obj == nil && isTypeParam(T) { - if t := structuralType(T); t != nil { + // If we didn't find anything and if we have a type parameter with a core type, + // see if there is a matching field (but not a method, those need to be declared + // explicitly in the constraint). If the constraint is a named pointer type (see + // above), we are ok here because only fields are accepted as results. + const enableTParamFieldLookup = false // see issue #51576 + if enableTParamFieldLookup && obj == nil && isTypeParam(T) { + if t := coreType(T); t != nil { obj, index, indirect = lookupFieldOrMethod(t, addressable, pkg, name, false) if _, ok := obj.(*Var); !ok { obj, index, indirect = nil, nil, false // accept fields (variables) only @@ -424,18 +425,31 @@ func (check *Checker) funcString(f *Func) string { // method required by V and whether it is missing or just has the wrong type. // The receiver may be nil if assertableTo is invoked through an exported API call // (such as AssertableTo), i.e., when all methods have been type-checked. -// If the global constant forceStrict is set, assertions that are known to fail -// are not permitted. +// TODO(gri) replace calls to this function with calls to newAssertableTo. func (check *Checker) assertableTo(V *Interface, T Type) (method, wrongType *Func) { // no static check is required if T is an interface // spec: "If T is an interface type, x.(T) asserts that the // dynamic type of x implements the interface T." - if IsInterface(T) && !forceStrict { + if IsInterface(T) { return } + // TODO(gri) fix this for generalized interfaces return check.missingMethod(T, V, false) } +// newAssertableTo reports whether a value of type V can be asserted to have type T. +// It also implements behavior for interfaces that currently are only permitted +// in constraint position (we have not yet defined that behavior in the spec). +func (check *Checker) newAssertableTo(V *Interface, T Type) error { + // no static check is required if T is an interface + // spec: "If T is an interface type, x.(T) asserts that the + // dynamic type of x implements the interface T." + if IsInterface(T) { + return nil + } + return check.implements(T, V) +} + // deref dereferences typ if it is a *Pointer and returns its base and true. // Otherwise it returns (typ, false). func deref(typ Type) (Type, bool) { diff --git a/src/go/types/named.go b/src/go/types/named.go index 5e84c39776..876f7e8551 100644 --- a/src/go/types/named.go +++ b/src/go/types/named.go @@ -72,18 +72,38 @@ func (check *Checker) newNamed(obj *TypeName, orig *Named, underlying Type, tpar } // Ensure that typ is always expanded and sanity-checked. if check != nil { - check.defTypes = append(check.defTypes, typ) + check.needsCleanup(typ) } return typ } +func (t *Named) cleanup() { + // Ensure that every defined type created in the course of type-checking has + // either non-*Named underlying, or is unresolved. + // + // This guarantees that we don't leak any types whose underlying is *Named, + // because any unresolved instances will lazily compute their underlying by + // substituting in the underlying of their origin. The origin must have + // either been imported or type-checked and expanded here, and in either case + // its underlying will be fully expanded. + switch t.underlying.(type) { + case nil: + if t.resolver == nil { + panic("nil underlying") + } + case *Named: + t.under() // t.under may add entries to check.cleaners + } + t.check = nil +} + // Obj returns the type name for the declaration defining the named type t. For -// instantiated types, this is the type name of the base type. +// instantiated types, this is same as the type name of the origin type. func (t *Named) Obj() *TypeName { return t.orig.obj // for non-instances this is the same as t.obj } -// Origin returns the parameterized type from which the named type t is +// Origin returns the generic type from which the named type t is // instantiated. If t is not an instantiated type, the result is t. func (t *Named) Origin() *Named { return t.orig } @@ -91,7 +111,7 @@ func (t *Named) Origin() *Named { return t.orig } // between parameterized instantiated and non-instantiated types. // TypeParams returns the type parameters of the named type t, or nil. -// The result is non-nil for an (originally) parameterized type even if it is instantiated. +// The result is non-nil for an (originally) generic type even if it is instantiated. func (t *Named) TypeParams() *TypeParamList { return t.resolve(nil).tparams } // SetTypeParams sets the type parameters of the named type t. @@ -104,7 +124,11 @@ func (t *Named) SetTypeParams(tparams []*TypeParam) { // TypeArgs returns the type arguments used to instantiate the named type t. func (t *Named) TypeArgs() *TypeList { return t.targs } -// NumMethods returns the number of explicit methods whose receiver is named type t. +// NumMethods returns the number of explicit methods defined for t. +// +// For an ordinary or instantiated type t, the receiver base type of these +// methods will be the named type t. For an uninstantiated generic type t, each +// method receiver will be instantiated with its receiver type parameters. func (t *Named) NumMethods() int { return t.resolve(nil).methods.Len() } // Method returns the i'th method of named type t for 0 <= i < t.NumMethods(). @@ -362,11 +386,11 @@ func expandNamed(ctxt *Context, n *Named, instPos token.Pos) (tparams *TypeParam // that it wasn't substituted. In this case we need to create a new // *Interface before modifying receivers. if iface == n.orig.underlying { - iface = &Interface{ - embeddeds: iface.embeddeds, - complete: iface.complete, - implicit: iface.implicit, // should be false but be conservative - } + old := iface + iface = check.newInterface() + iface.embeddeds = old.embeddeds + iface.complete = old.complete + iface.implicit = old.implicit // should be false but be conservative underlying = iface } iface.methods = methods diff --git a/src/go/types/predicates.go b/src/go/types/predicates.go index 23dcd7274d..0360f27ee6 100644 --- a/src/go/types/predicates.go +++ b/src/go/types/predicates.go @@ -33,7 +33,7 @@ func isBasic(t Type, info BasicInfo) bool { // The allX predicates below report whether t is an X. // If t is a type parameter the result is true if isX is true // for all specified types of the type parameter's type set. -// allX is an optimized version of isX(structuralType(t)) (which +// allX is an optimized version of isX(coreType(t)) (which // is the same as underIs(t, isX)). func allBoolean(typ Type) bool { return allBasic(typ, IsBoolean) } @@ -47,7 +47,7 @@ func allNumericOrString(typ Type) bool { return allBasic(typ, IsNumeric|IsString // allBasic reports whether under(t) is a basic type with the specified info. // If t is a type parameter, the result is true if isBasic(t, info) is true // for all specific types of the type parameter's type set. -// allBasic(t, info) is an optimized version of isBasic(structuralType(t), info). +// allBasic(t, info) is an optimized version of isBasic(coreType(t), info). func allBasic(t Type, info BasicInfo) bool { if tpar, _ := t.(*TypeParam); tpar != nil { return tpar.is(func(t *term) bool { return t != nil && isBasic(t.typ, info) }) @@ -104,11 +104,12 @@ func isGeneric(t Type) bool { // Comparable reports whether values of type T are comparable. func Comparable(T Type) bool { - return comparable(T, nil, nil) + return comparable(T, true, nil, nil) } +// If dynamic is set, non-type parameter interfaces are always comparable. // If reportf != nil, it may be used to report why T is not comparable. -func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{})) bool { +func comparable(T Type, dynamic bool, seen map[Type]bool, reportf func(string, ...interface{})) bool { if seen[T] { return true } @@ -126,7 +127,7 @@ func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{}) return true case *Struct: for _, f := range t.fields { - if !comparable(f.typ, seen, nil) { + if !comparable(f.typ, dynamic, seen, nil) { if reportf != nil { reportf("struct containing %s cannot be compared", f.typ) } @@ -135,7 +136,7 @@ func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{}) } return true case *Array: - if !comparable(t.elem, seen, nil) { + if !comparable(t.elem, dynamic, seen, nil) { if reportf != nil { reportf("%s cannot be compared", t) } @@ -143,7 +144,7 @@ func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{}) } return true case *Interface: - return !isTypeParam(T) || t.typeSet().IsComparable(seen) + return dynamic && !isTypeParam(T) || t.typeSet().IsComparable(seen) } return false } diff --git a/src/go/types/signature.go b/src/go/types/signature.go index 8f89e931fb..a340ac701e 100644 --- a/src/go/types/signature.go +++ b/src/go/types/signature.go @@ -112,7 +112,8 @@ func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftyp *ast // - the receiver specification acts as local declaration for its type parameters, which may be blank _, rname, rparams := check.unpackRecv(recvPar.List[0].Type, true) if len(rparams) > 0 { - sig.rparams = bindTParams(check.declareTypeParams(nil, rparams)) + tparams := check.declareTypeParams(nil, rparams) + sig.rparams = bindTParams(tparams) // Blank identifiers don't get declared, so naive type-checking of the // receiver type expression would fail in Checker.collectParams below, // when Checker.ident cannot resolve the _ to a type. @@ -122,11 +123,10 @@ func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftyp *ast // lookup in the scope. for i, p := range rparams { if p.Name == "_" { - tpar := sig.rparams.At(i) if check.recvTParamMap == nil { check.recvTParamMap = make(map[*ast.Ident]*TypeParam) } - check.recvTParamMap[p] = tpar + check.recvTParamMap[p] = tparams[i] } } // determine receiver type to get its type parameters @@ -142,22 +142,23 @@ func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftyp *ast } } // provide type parameter bounds - // - only do this if we have the right number (otherwise an error is reported elsewhere) - if sig.RecvTypeParams().Len() == len(recvTParams) { - // We have a list of *TypeNames but we need a list of Types. - list := make([]Type, sig.RecvTypeParams().Len()) - for i, t := range sig.RecvTypeParams().list() { - list[i] = t - check.mono.recordCanon(t, recvTParams[i]) - } - smap := makeSubstMap(recvTParams, list) - for i, tpar := range sig.RecvTypeParams().list() { - bound := recvTParams[i].bound - // bound is (possibly) parameterized in the context of the - // receiver type declaration. Substitute parameters for the - // current context. - tpar.bound = check.subst(tpar.obj.pos, bound, smap, nil) + if len(tparams) == len(recvTParams) { + smap := makeRenameMap(recvTParams, tparams) + for i, tpar := range tparams { + recvTPar := recvTParams[i] + check.mono.recordCanon(tpar, recvTPar) + // recvTPar.bound is (possibly) parameterized in the context of the + // receiver type declaration. Substitute parameters for the current + // context. + tpar.bound = check.subst(tpar.obj.pos, recvTPar.bound, smap, nil) } + } else if len(tparams) < len(recvTParams) { + // Reporting an error here is a stop-gap measure to avoid crashes in the + // compiler when a type parameter/argument cannot be inferred later. It + // may lead to follow-on errors (see issues #51339, #51343). + // TODO(gri) find a better solution + got := measure(len(tparams), "type parameter") + check.errorf(recvPar, _BadRecv, "got %s, but receiver base type declares %d", got, len(recvTParams)) } } } @@ -192,66 +193,77 @@ func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftyp *ast switch len(recvList) { case 0: // error reported by resolver - recv = NewParam(0, nil, "", Typ[Invalid]) // ignore recv below + recv = NewParam(token.NoPos, nil, "", Typ[Invalid]) // ignore recv below default: // more than one receiver - check.error(recvList[len(recvList)-1], _BadRecv, "method must have exactly one receiver") + check.error(recvList[len(recvList)-1], _InvalidRecv, "method must have exactly one receiver") fallthrough // continue with first receiver case 1: recv = recvList[0] } + sig.recv = recv - // TODO(gri) We should delay rtyp expansion to when we actually need the - // receiver; thus all checks here should be delayed to later. - rtyp, _ := deref(recv.typ) + // Delay validation of receiver type as it may cause premature expansion + // of types the receiver type is dependent on (see issues #51232, #51233). + check.later(func() { + rtyp, _ := deref(recv.typ) - // spec: "The receiver type must be of the form T or *T where T is a type name." - // (ignore invalid types - error was reported before) - if rtyp != Typ[Invalid] { - var err string - switch T := rtyp.(type) { - case *Named: - T.resolve(check.bestContext(nil)) - // The receiver type may be an instantiated type referred to - // by an alias (which cannot have receiver parameters for now). - if T.TypeArgs() != nil && sig.RecvTypeParams() == nil { - check.errorf(atPos(recv.pos), _InvalidRecv, "cannot define methods on instantiated type %s", recv.typ) - break - } - // spec: "The type denoted by T is called the receiver base type; it must not - // be a pointer or interface type and it must be declared in the same package - // as the method." - if T.obj.pkg != check.pkg { - err = "type not defined in this package" - } else { - // The underlying type of a receiver base type can be a type parameter; - // e.g. for methods with a generic receiver T[P] with type T[P any] P. - underIs(T, func(u Type) bool { - switch u := u.(type) { - case *Basic: - // unsafe.Pointer is treated like a regular pointer - if u.kind == UnsafePointer { - err = "unsafe.Pointer" + // spec: "The receiver type must be of the form T or *T where T is a type name." + // (ignore invalid types - error was reported before) + if rtyp != Typ[Invalid] { + var err string + switch T := rtyp.(type) { + case *Named: + T.resolve(check.bestContext(nil)) + // The receiver type may be an instantiated type referred to + // by an alias (which cannot have receiver parameters for now). + if T.TypeArgs() != nil && sig.RecvTypeParams() == nil { + check.errorf(recv, _InvalidRecv, "cannot define methods on instantiated type %s", recv.typ) + break + } + // spec: "The type denoted by T is called the receiver base type; it must not + // be a pointer or interface type and it must be declared in the same package + // as the method." + if T.obj.pkg != check.pkg { + err = "type not defined in this package" + if compilerErrorMessages { + check.errorf(recv, _InvalidRecv, "cannot define new methods on non-local type %s", recv.typ) + err = "" + } + } else { + // The underlying type of a receiver base type can be a type parameter; + // e.g. for methods with a generic receiver T[P] with type T[P any] P. + // TODO(gri) Such declarations are currently disallowed. + // Revisit the need for underIs. + underIs(T, func(u Type) bool { + switch u := u.(type) { + case *Basic: + // unsafe.Pointer is treated like a regular pointer + if u.kind == UnsafePointer { + err = "unsafe.Pointer" + return false + } + case *Pointer, *Interface: + err = "pointer or interface type" return false } - case *Pointer, *Interface: - err = "pointer or interface type" - return false - } - return true - }) + return true + }) + } + case *Basic: + err = "basic or unnamed type" + if compilerErrorMessages { + check.errorf(recv, _InvalidRecv, "cannot define new methods on non-local type %s", recv.typ) + err = "" + } + default: + check.errorf(recv, _InvalidRecv, "invalid receiver type %s", recv.typ) + } + if err != "" { + check.errorf(recv, _InvalidRecv, "invalid receiver type %s (%s)", recv.typ, err) } - case *Basic: - err = "basic or unnamed type" - default: - check.errorf(recv, _InvalidRecv, "invalid receiver type %s", recv.typ) } - if err != "" { - check.errorf(recv, _InvalidRecv, "invalid receiver type %s (%s)", recv.typ, err) - // ok to continue - } - } - sig.recv = recv + }).describef(recv, "validate receiver %s", recv) } sig.params = NewTuple(params...) diff --git a/src/go/types/stmt.go b/src/go/types/stmt.go index b32eb18bef..9ebfbb6d63 100644 --- a/src/go/types/stmt.go +++ b/src/go/types/stmt.go @@ -418,9 +418,9 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) { if ch.mode == invalid || val.mode == invalid { return } - u := structuralType(ch.typ) + u := coreType(ch.typ) if u == nil { - check.invalidOp(inNode(s, s.Arrow), _InvalidSend, "cannot send to %s: no structural type", &ch) + check.invalidOp(inNode(s, s.Arrow), _InvalidSend, "cannot send to %s: no core type", &ch) return } uch, _ := u.(*Chan) @@ -821,8 +821,6 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) { case *ast.RangeStmt: inner |= breakOk | continueOk - check.openScope(s, "for") - defer check.closeScope() // check expression to iterate over var x operand @@ -831,12 +829,12 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) { // determine key/value types var key, val Type if x.mode != invalid { - // Ranging over a type parameter is permitted if it has a structural type. + // Ranging over a type parameter is permitted if it has a core type. var cause string - u := structuralType(x.typ) + u := coreType(x.typ) switch t := u.(type) { case nil: - cause = check.sprintf("%s has no structural type", x.typ) + cause = check.sprintf("%s has no core type", x.typ) case *Chan: if s.Value != nil { check.softErrorf(s.Value, _InvalidIterVar, "range over %s permits only one iteration variable", &x) @@ -857,6 +855,11 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) { } } + // Open the for-statement block scope now, after the range clause. + // Iteration variables declared with := need to go in this scope (was issue #51437). + check.openScope(s, "range") + defer check.closeScope() + // check assignment to/declaration of iteration variables // (irregular assignment, cannot easily map to existing assignment checks) @@ -865,9 +868,7 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) { rhs := [2]Type{key, val} // key, val may be nil if s.Tok == token.DEFINE { - // short variable declaration; variable scope starts after the range clause - // (the for loop opens a new scope, so variables on the lhs never redeclare - // previously declared variables) + // short variable declaration var vars []*Var for i, lhs := range lhs { if lhs == nil { @@ -904,12 +905,8 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) { // declare variables if len(vars) > 0 { - scopePos := s.X.End() + scopePos := s.Body.Pos() for _, obj := range vars { - // spec: "The scope of a constant or variable identifier declared inside - // a function begins at the end of the ConstSpec or VarSpec (ShortVarDecl - // for short variable declarations) and ends at the end of the innermost - // containing block." check.declare(check.scope, nil /* recordDef already called */, obj, scopePos) } } else { diff --git a/src/go/types/subst.go b/src/go/types/subst.go index 0cce46ac46..4b4a0f4ad6 100644 --- a/src/go/types/subst.go +++ b/src/go/types/subst.go @@ -21,6 +21,17 @@ func makeSubstMap(tpars []*TypeParam, targs []Type) substMap { return proj } +// makeRenameMap is like makeSubstMap, but creates a map used to rename type +// parameters in from with the type parameters in to. +func makeRenameMap(from, to []*TypeParam) substMap { + assert(len(from) == len(to)) + proj := make(substMap, len(from)) + for i, tpar := range from { + proj[tpar] = to[i] + } + return proj +} + func (m substMap) empty() bool { return len(m) == 0 } @@ -149,7 +160,10 @@ func (subst *subster) typ(typ Type) Type { methods, mcopied := subst.funcList(t.methods) embeddeds, ecopied := subst.typeList(t.embeddeds) if mcopied || ecopied { - iface := &Interface{embeddeds: embeddeds, implicit: t.implicit, complete: t.complete} + iface := subst.check.newInterface() + iface.embeddeds = embeddeds + iface.implicit = t.implicit + iface.complete = t.complete // If we've changed the interface type, we may need to replace its // receiver if the receiver type is the original interface. Receivers of // *Named type are replaced during named type expansion. diff --git a/src/go/types/termlist.go b/src/go/types/termlist.go index c4ab0e037e..94e49caee0 100644 --- a/src/go/types/termlist.go +++ b/src/go/types/termlist.go @@ -92,15 +92,6 @@ func (xl termlist) norm() termlist { return rl } -// If the type set represented by xl is specified by a single (non-𝓤) term, -// singleType returns that type. Otherwise it returns nil. -func (xl termlist) singleType() Type { - if nl := xl.norm(); len(nl) == 1 { - return nl[0].typ // if nl.isAll() then typ is nil, which is ok - } - return nil -} - // union returns the union xl ∪ yl. func (xl termlist) union(yl termlist) termlist { return append(xl, yl...).norm() diff --git a/src/go/types/termlist_test.go b/src/go/types/termlist_test.go index dddca7a682..f0d58ac1bc 100644 --- a/src/go/types/termlist_test.go +++ b/src/go/types/termlist_test.go @@ -106,35 +106,6 @@ func TestTermlistNorm(t *testing.T) { } } -func TestTermlistSingleType(t *testing.T) { - // helper to deal with nil types - tstring := func(typ Type) string { - if typ == nil { - return "nil" - } - return typ.String() - } - - for test, want := range map[string]string{ - "∅": "nil", - "𝓤": "nil", - "int": "int", - "myInt": "myInt", - "~int": "int", - "~int ∪ string": "nil", - "~int ∪ myInt": "int", - "∅ ∪ int": "int", - "∅ ∪ ~int": "int", - "∅ ∪ ~int ∪ string": "nil", - } { - xl := maketl(test) - got := tstring(xl.singleType()) - if got != want { - t.Errorf("(%v).singleType() == %v; want %v", test, got, want) - } - } -} - func TestTermlistUnion(t *testing.T) { for _, test := range []struct { xl, yl, want string diff --git a/src/go/types/testdata/check/builtins.go2 b/src/go/types/testdata/check/builtins.go2 index c1accff016..861597399e 100644 --- a/src/go/types/testdata/check/builtins.go2 +++ b/src/go/types/testdata/check/builtins.go2 @@ -148,7 +148,7 @@ func _[ _ = make /* ERROR expects 2 or 3 arguments */ (S1) _ = make(S1, 10, 20) _ = make /* ERROR expects 2 or 3 arguments */ (S1, 10, 20, 30) - _ = make(S2 /* ERROR cannot make S2: no structural type */ , 10) + _ = make(S2 /* ERROR cannot make S2: no core type */ , 10) type M0 map[string]int _ = make(map[string]int) @@ -156,7 +156,7 @@ func _[ _ = make(M1) _ = make(M1, 10) _ = make/* ERROR expects 1 or 2 arguments */(M1, 10, 20) - _ = make(M2 /* ERROR cannot make M2: no structural type */ ) + _ = make(M2 /* ERROR cannot make M2: no core type */ ) type C0 chan int _ = make(chan int) @@ -164,7 +164,7 @@ func _[ _ = make(C1) _ = make(C1, 10) _ = make/* ERROR expects 1 or 2 arguments */(C1, 10, 20) - _ = make(C2 /* ERROR cannot make C2: no structural type */ ) + _ = make(C2 /* ERROR cannot make C2: no core type */ ) _ = make(C3) } diff --git a/src/go/types/testdata/check/builtins.src b/src/go/types/testdata/check/builtins.src index 7fd6a4b032..8a4c207a05 100644 --- a/src/go/types/testdata/check/builtins.src +++ b/src/go/types/testdata/check/builtins.src @@ -15,9 +15,9 @@ func append1() { var x int var s []byte _ = append() // ERROR not enough arguments - _ = append("foo" /* ERROR not a slice */ ) - _ = append(nil /* ERROR not a slice */ , s) - _ = append(x /* ERROR not a slice */ , s) + _ = append("foo" /* ERROR must be a slice */ ) + _ = append(nil /* ERROR must be a slice */ , s) + _ = append(x /* ERROR must be a slice */ , s) _ = append(s) _ = append(s, nil...) append /* ERROR not used */ (s) @@ -77,7 +77,7 @@ func append3() { _ = append(f2()) _ = append(f3()) _ = append(f5()) - _ = append(ff /* ERROR not a slice */ ()) // TODO(gri) better error message + _ = append(ff /* ERROR must be a slice */ ()) // TODO(gri) better error message } func cap1() { diff --git a/src/go/types/testdata/check/funcinference.go2 b/src/go/types/testdata/check/funcinference.go2 index f04b76ca1a..45d0781cd7 100644 --- a/src/go/types/testdata/check/funcinference.go2 +++ b/src/go/types/testdata/check/funcinference.go2 @@ -8,21 +8,21 @@ import "strconv" type any interface{} -func f0[A any, B interface{~*C}, C interface{~*D}, D interface{~*A}](A, B, C, D) {} +func f0[A any, B interface{*C}, C interface{*D}, D interface{*A}](A, B, C, D) {} func _() { f := f0[string] f("a", nil, nil, nil) f0("a", nil, nil, nil) } -func f1[A any, B interface{~*A}](A, B) {} +func f1[A any, B interface{*A}](A, B) {} func _() { f := f1[int] f(int(0), new(int)) f1(int(0), new(int)) } -func f2[A any, B interface{~[]A}](A, B) {} +func f2[A any, B interface{[]A}](A, B) {} func _() { f := f2[byte] f(byte(0), []byte{}) @@ -38,7 +38,7 @@ func _() { // f3(x, &x, &x) // } -func f4[A any, B interface{~[]C}, C interface{~*A}](A, B, C) {} +func f4[A any, B interface{[]C}, C interface{*A}](A, B, C) {} func _() { f := f4[int] var x int @@ -46,7 +46,7 @@ func _() { f4(x, []*int{}, &x) } -func f5[A interface{~struct{b B; c C}}, B any, C interface{~*B}](x B) A { panic(0) } +func f5[A interface{struct{b B; c C}}, B any, C interface{*B}](x B) A { panic(0) } func _() { x := f5(1.2) var _ float64 = x.b @@ -79,7 +79,7 @@ var _ = Double(MySlice{1}) type Setter[B any] interface { Set(string) - ~*B + *B } func FromStrings[T interface{}, PT Setter[T]](s []string) []T { diff --git a/src/go/types/testdata/check/typeinference.go2 b/src/go/types/testdata/check/typeinference.go2 index 8876ccaa4e..28f3e286b7 100644 --- a/src/go/types/testdata/check/typeinference.go2 +++ b/src/go/types/testdata/check/typeinference.go2 @@ -4,44 +4,46 @@ package typeInference +// As of issue #51527, type-type inference has been disabled. + // basic inference type Tb[P ~*Q, Q any] int func _() { - var x Tb[*int] + var x Tb /* ERROR got 1 arguments */ [*int] var y Tb[*int, int] - x = y + x = y /* ERROR cannot use y .* in assignment */ _ = x } // recursive inference -type Tr[A any, B ~*C, C ~*D, D ~*A] int +type Tr[A any, B *C, C *D, D *A] int func _() { - var x Tr[string] + var x Tr /* ERROR got 1 arguments */ [string] var y Tr[string, ***string, **string, *string] var z Tr[int, ***int, **int, *int] - x = y + x = y /* ERROR cannot use y .* in assignment */ x = z // ERROR cannot use z .* as Tr _ = x } // other patterns of inference -type To0[A any, B ~[]A] int -type To1[A any, B ~struct{a A}] int -type To2[A any, B ~[][]A] int -type To3[A any, B ~[3]*A] int -type To4[A any, B any, C ~struct{a A; b B}] int +type To0[A any, B []A] int +type To1[A any, B struct{a A}] int +type To2[A any, B [][]A] int +type To3[A any, B [3]*A] int +type To4[A any, B any, C struct{a A; b B}] int func _() { - var _ To0[int] - var _ To1[int] - var _ To2[int] - var _ To3[int] - var _ To4[int, string] + var _ To0 /* ERROR got 1 arguments */ [int] + var _ To1 /* ERROR got 1 arguments */ [int] + var _ To2 /* ERROR got 1 arguments */ [int] + var _ To3 /* ERROR got 1 arguments */ [int] + var _ To4 /* ERROR got 2 arguments */ [int, string] } // failed inference type Tf0[A, B any] int type Tf1[A any, B ~struct{a A; c C}, C any] int func _() { - var _ Tf0 /* ERROR cannot infer B */ /* ERROR got 1 arguments but 2 type parameters */ [int] - var _ Tf1 /* ERROR cannot infer B */ /* ERROR got 1 arguments but 3 type parameters */ [int] + var _ Tf0 /* ERROR got 1 arguments but 2 type parameters */ [int] + var _ Tf1 /* ERROR got 1 arguments but 3 type parameters */ [int] } diff --git a/src/go/types/testdata/check/typeparams.go2 b/src/go/types/testdata/check/typeparams.go2 index d5b9ed6e77..29a3b16cd6 100644 --- a/src/go/types/testdata/check/typeparams.go2 +++ b/src/go/types/testdata/check/typeparams.go2 @@ -134,11 +134,11 @@ func _[T interface{ ~string }] (x T, i, j, k int) { var _ T = x[i:j:k /* ERROR 3 type myByte1 []byte type myByte2 []byte func _[T interface{ []byte | myByte1 | myByte2 }] (x T, i, j, k int) { var _ T = x[i:j:k] } -func _[T interface{ []byte | myByte1 | []int }] (x T, i, j, k int) { var _ T = x /* ERROR no structural type */ [i:j:k] } +func _[T interface{ []byte | myByte1 | []int }] (x T, i, j, k int) { var _ T = x /* ERROR no core type */ [i:j:k] } func _[T interface{ []byte | myByte1 | myByte2 | string }] (x T, i, j, k int) { var _ T = x[i:j] } func _[T interface{ []byte | myByte1 | myByte2 | string }] (x T, i, j, k int) { var _ T = x[i:j:k /* ERROR 3-index slice of string */ ] } -func _[T interface{ []byte | myByte1 | []int | string }] (x T, i, j, k int) { var _ T = x /* ERROR no structural type */ [i:j] } +func _[T interface{ []byte | myByte1 | []int | string }] (x T, i, j, k int) { var _ T = x /* ERROR no core type */ [i:j] } // len/cap built-ins @@ -230,7 +230,7 @@ func _[ for _, _ = range s1 {} var s2 S2 - for range s2 /* ERROR cannot range over s2.*no structural type */ {} + for range s2 /* ERROR cannot range over s2.*no core type */ {} var a0 []int for range a0 {} @@ -243,7 +243,7 @@ func _[ for _, _ = range a1 {} var a2 A2 - for range a2 /* ERROR cannot range over a2.*no structural type */ {} + for range a2 /* ERROR cannot range over a2.*no core type */ {} var p0 *[10]int for range p0 {} @@ -256,7 +256,7 @@ func _[ for _, _ = range p1 {} var p2 P2 - for range p2 /* ERROR cannot range over p2.*no structural type */ {} + for range p2 /* ERROR cannot range over p2.*no core type */ {} var m0 map[string]int for range m0 {} @@ -269,7 +269,7 @@ func _[ for _, _ = range m1 {} var m2 M2 - for range m2 /* ERROR cannot range over m2.*no structural type */ {} + for range m2 /* ERROR cannot range over m2.*no core type */ {} } // type inference checks diff --git a/src/go/types/testdata/examples/inference.go2 b/src/go/types/testdata/examples/inference.go2 index ffa30ee2cb..e59a544660 100644 --- a/src/go/types/testdata/examples/inference.go2 +++ b/src/go/types/testdata/examples/inference.go2 @@ -78,7 +78,7 @@ func _() { related1(si, "foo" /* ERROR cannot use "foo" */ ) } -func related2[Elem any, Slice interface{~[]Elem}](e Elem, s Slice) {} +func related2[Elem any, Slice interface{[]Elem}](e Elem, s Slice) {} func _() { // related2 can be called with explicit instantiation. @@ -109,16 +109,8 @@ func _() { related3[int, []int]() related3[byte, List[byte]]() - // Alternatively, the 2nd type argument can be inferred - // from the first one through constraint type inference. - related3[int]() - - // The inferred type is the structural type of the Slice - // type parameter. - var _ []int = related3[int]() - - // It is not the defined parameterized type List. - type anotherList []float32 - var _ anotherList = related3[float32]() // valid - var _ anotherList = related3 /* ERROR cannot use .* \(value of type List\[float32\]\) as anotherList */ [float32, List[float32]]() + // The 2nd type argument cannot be inferred from the first + // one because there's two possible choices: []Elem and + // List[Elem]. + related3 /* ERROR cannot infer Slice */ [int]() } diff --git a/src/go/types/testdata/examples/methods.go2 b/src/go/types/testdata/examples/methods.go2 index 1d76d553dc..a46f789d60 100644 --- a/src/go/types/testdata/examples/methods.go2 +++ b/src/go/types/testdata/examples/methods.go2 @@ -35,7 +35,7 @@ func (t T1[[ /* ERROR must be an identifier */ ]int]) m2() {} // style. In m3 below, int is the name of the local receiver type parameter // and it shadows the predeclared identifier int which then cannot be used // anymore as expected. -// This is no different from locally redelaring a predeclared identifier +// This is no different from locally re-declaring a predeclared identifier // and usually should be avoided. There are some notable exceptions; e.g., // sometimes it makes sense to use the identifier "copy" which happens to // also be the name of a predeclared built-in function. diff --git a/src/go/types/testdata/examples/types.go2 b/src/go/types/testdata/examples/types.go2 index 33642fa42f..1e83f89883 100644 --- a/src/go/types/testdata/examples/types.go2 +++ b/src/go/types/testdata/examples/types.go2 @@ -298,7 +298,7 @@ func _[T interface {~int|~float64}]() { // It is possible to create composite literals of type parameter // type as long as it's possible to create a composite literal -// of the structural type of the type parameter's constraint. +// of the core type of the type parameter's constraint. func _[P interface{ ~[]int }]() P { return P{} return P{1, 2, 3} @@ -313,7 +313,7 @@ func _[P interface{ ~[]E }, E interface{ map[string]P } ]() P { } // This is a degenerate case with a singleton type set, but we can create -// composite literals even if the structural type is a defined type. +// composite literals even if the core type is a defined type. type MyInts []int func _[P MyInts]() P { diff --git a/src/go/types/testdata/examples/typesets.go2 b/src/go/types/testdata/examples/typesets.go2 index cf01072d8c..fcddf1f1a5 100644 --- a/src/go/types/testdata/examples/typesets.go2 +++ b/src/go/types/testdata/examples/typesets.go2 @@ -35,7 +35,7 @@ func _() int { return deref(p) } -func addrOfCopy[V any, P ~*V](v V) P { +func addrOfCopy[V any, P *V](v V) P { return &v } diff --git a/src/go/types/testdata/fixedbugs/issue41124.go2 b/src/go/types/testdata/fixedbugs/issue41124.go2 index 7f55ba85a6..4550dd732c 100644 --- a/src/go/types/testdata/fixedbugs/issue41124.go2 +++ b/src/go/types/testdata/fixedbugs/issue41124.go2 @@ -47,7 +47,7 @@ type _ struct{ } type _ struct{ - I3 // ERROR interface is .* comparable + I3 // ERROR interface contains type constraints } // General composite types. @@ -59,19 +59,19 @@ type ( _ []I1 // ERROR interface is .* comparable _ []I2 // ERROR interface contains type constraints - _ *I3 // ERROR interface is .* comparable + _ *I3 // ERROR interface contains type constraints _ map[I1 /* ERROR interface is .* comparable */ ]I2 // ERROR interface contains type constraints - _ chan I3 // ERROR interface is .* comparable + _ chan I3 // ERROR interface contains type constraints _ func(I1 /* ERROR interface is .* comparable */ ) _ func() I2 // ERROR interface contains type constraints ) // Other cases. -var _ = [...]I3 /* ERROR interface is .* comparable */ {} +var _ = [...]I3 /* ERROR interface contains type constraints */ {} func _(x interface{}) { - _ = x.(I3 /* ERROR interface is .* comparable */ ) + _ = x.(I3 /* ERROR interface contains type constraints */ ) } type T1[_ any] struct{} diff --git a/src/go/types/testdata/fixedbugs/issue43527.go2 b/src/go/types/testdata/fixedbugs/issue43527.go2 index e4bcee51fe..2955c261f9 100644 --- a/src/go/types/testdata/fixedbugs/issue43527.go2 +++ b/src/go/types/testdata/fixedbugs/issue43527.go2 @@ -9,7 +9,7 @@ const L = 10 type ( _ [L]struct{} _ [A /* ERROR undeclared name A for array length */ ]struct{} - _ [B /* ERROR not an expression */ ]struct{} + _ [B /* ERROR invalid array length B */ ]struct{} _[A any] struct{} B int diff --git a/src/go/types/testdata/fixedbugs/issue43671.go2 b/src/go/types/testdata/fixedbugs/issue43671.go2 index 46ac51ebdd..3c78f85aa4 100644 --- a/src/go/types/testdata/fixedbugs/issue43671.go2 +++ b/src/go/types/testdata/fixedbugs/issue43671.go2 @@ -12,7 +12,7 @@ type C4 interface{ chan int | chan<- int } type C5[T any] interface{ ~chan T | <-chan T } func _[T any](ch T) { - <-ch // ERROR cannot receive from ch .* no structural type + <-ch // ERROR cannot receive from ch .* no core type } func _[T C0](ch T) { @@ -28,7 +28,7 @@ func _[T C2](ch T) { } func _[T C3](ch T) { - <-ch // ERROR cannot receive from ch .* no structural type + <-ch // ERROR cannot receive from ch .* no core type } func _[T C4](ch T) { diff --git a/src/go/types/testdata/fixedbugs/issue45548.go2 b/src/go/types/testdata/fixedbugs/issue45548.go2 index b8ba0ad4a7..01c9672745 100644 --- a/src/go/types/testdata/fixedbugs/issue45548.go2 +++ b/src/go/types/testdata/fixedbugs/issue45548.go2 @@ -4,7 +4,7 @@ package p -func f[F interface{~*Q}, G interface{~*R}, Q, R any](q Q, r R) {} +func f[F interface{*Q}, G interface{*R}, Q, R any](q Q, r R) {} func _() { f[*float64, *int](1, 2) diff --git a/src/go/types/testdata/fixedbugs/issue47115.go2 b/src/go/types/testdata/fixedbugs/issue47115.go2 index f71e06c9b2..a0bfe38de8 100644 --- a/src/go/types/testdata/fixedbugs/issue47115.go2 +++ b/src/go/types/testdata/fixedbugs/issue47115.go2 @@ -12,7 +12,7 @@ type C4 interface{ chan int | chan<- int } type C5[T any] interface{ ~chan T | chan<- T } func _[T any](ch T) { - ch <- /* ERROR cannot send to ch .* no structural type */ 0 + ch <- /* ERROR cannot send to ch .* no core type */ 0 } func _[T C0](ch T) { @@ -28,7 +28,7 @@ func _[T C2](ch T) { } func _[T C3](ch T) { - ch <- /* ERROR cannot send to ch .* no structural type */ 0 + ch <- /* ERROR cannot send to ch .* no core type */ 0 } func _[T C4](ch T) { diff --git a/src/go/types/testdata/fixedbugs/issue48619.go2 b/src/go/types/testdata/fixedbugs/issue48619.go2 index d33040d78f..72eea1ef59 100644 --- a/src/go/types/testdata/fixedbugs/issue48619.go2 +++ b/src/go/types/testdata/fixedbugs/issue48619.go2 @@ -2,24 +2,19 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// This issue is still open: -// - the error messages could be better or are incorrect -// - unification fails due to stack overflow that is caught - package p func f[P any](a, _ P) { var x int // TODO(gri) these error messages, while correct, could be better - f(a, x /* ERROR type int of x does not match P */) + f(a, x /* ERROR type int of x does not match inferred type P for P */) f(x, a /* ERROR type P of a does not match inferred type int for P */) } func g[P any](a, b P) { g(a, b) - // TODO(gri) these error messages are incorrect because the code is valid - g(&a, & /* ERROR type \*P of &b does not match inferred type \*P for P */ b) - g([]P{}, [ /* ERROR type \[\]P of \(\[\]P literal\) does not match inferred type \[\]P for P */ ]P{}) + g(&a, &b) + g([]P{}, []P{}) // work-around: provide type argument explicitly g[*P](&a, &b) diff --git a/src/go/types/testdata/fixedbugs/issue48656.go2 b/src/go/types/testdata/fixedbugs/issue48656.go2 index 493f220e98..0f60f47120 100644 --- a/src/go/types/testdata/fixedbugs/issue48656.go2 +++ b/src/go/types/testdata/fixedbugs/issue48656.go2 @@ -2,14 +2,12 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// This issue is still open: -// - the error messages are unclear -// - unification fails due to stack overflow that is caught - package p func f[P *Q, Q any](P, Q) { - // TODO(gri) these error messages are unclear - _ = f /* ERROR P does not match \*Q */ [P] - _ = f /* ERROR cannot infer P */ [*P] + _ = f[P] +} + +func f2[P /* ERROR instantiation cycle */ *Q, Q any](P, Q) { + _ = f2[*P] } diff --git a/src/go/types/testdata/fixedbugs/issue49482.go2 b/src/go/types/testdata/fixedbugs/issue49482.go2 new file mode 100644 index 0000000000..4c6579ed68 --- /dev/null +++ b/src/go/types/testdata/fixedbugs/issue49482.go2 @@ -0,0 +1,28 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file is tested when running "go test -run Manual" +// without source arguments. Use for one-off debugging. + +package p + +// The following is OK, per the special handling for type literals discussed in issue #49482. +type _[P *struct{}] struct{} +type _[P *int,] int +type _[P (*int),] int + +const P = 2 // declare P to avoid noisy 'undeclared name' errors below. + +// The following parse as invalid array types. +type _[P *int /* ERROR "int \(type\) is not an expression" */ ] int +type _[P /* ERROR non-function P */ (*int)] int + +// The following should be parsed as a generic type, but is instead parsed as an array type. +type _[P *struct /* ERROR "expected expression" */ {}| int /* ERROR "not an expression" */ ] struct{} + +// The following fails to parse, due to the '~' +type _[P *struct /* ERROR "expected expression" */ {}|~ /* ERROR "expected operand" */ int] struct{} + +// This is fragile: 'var' synchronizes the parser, and we absorb the rest of the errors. +var /* ERROR "expected ']'" */ _ /* ERROR "value or type" */ diff --git a/src/go/types/testdata/fixedbugs/issue49541.go2 b/src/go/types/testdata/fixedbugs/issue49541.go2 index b7bf12a186..c8499c1b61 100644 --- a/src/go/types/testdata/fixedbugs/issue49541.go2 +++ b/src/go/types/testdata/fixedbugs/issue49541.go2 @@ -10,9 +10,10 @@ type S[A, B any] struct { func (S[A, B]) m() {} -// TODO(gri) We should only report one error below. See issue #50588. +// TODO(gri): with type-type inference enabled we should only report one error +// below. See issue #50588. -func _[A any](s S /* ERROR cannot infer B */ /* ERROR got 1 arguments but 2 type parameters */ [A]) { +func _[A any](s S /* ERROR got 1 arguments but 2 type parameters */ [A]) { // we should see no follow-on errors below s.f = 1 s.m() @@ -21,7 +22,7 @@ func _[A any](s S /* ERROR cannot infer B */ /* ERROR got 1 arguments but 2 type // another test case from the issue func _() { - X(Interface[*F /* ERROR cannot infer B */ /* ERROR got 1 arguments but 2 type parameters */ [string]](Impl{})) + X(Interface[*F /* ERROR got 1 arguments but 2 type parameters */ [string]](Impl{})) } func X[Q Qer](fs Interface[Q]) { diff --git a/src/go/types/testdata/fixedbugs/issue49735.go2 b/src/go/types/testdata/fixedbugs/issue49735.go2 new file mode 100644 index 0000000000..50870226e4 --- /dev/null +++ b/src/go/types/testdata/fixedbugs/issue49735.go2 @@ -0,0 +1,11 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +func _[P1 any, P2 ~byte](s1 P1, s2 P2) { + _ = append(nil /* ERROR first argument to append must be a slice; have untyped nil */ , 0) + _ = append(s1 /* ERROR s1 .* has no core type */ , 0) + _ = append(s2 /* ERROR s2 .* has core type byte */ , 0) +} diff --git a/src/go/types/testdata/fixedbugs/issue50417.go2 b/src/go/types/testdata/fixedbugs/issue50417.go2 index b6454ab003..2caef1b986 100644 --- a/src/go/types/testdata/fixedbugs/issue50417.go2 +++ b/src/go/types/testdata/fixedbugs/issue50417.go2 @@ -2,6 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// Field accesses through type parameters are disabled +// until we have a more thorough understanding of the +// implications on the spec. See issue #51576. + package p type Sf struct { @@ -9,13 +13,13 @@ type Sf struct { } func f0[P Sf](p P) { - _ = p.f - p.f = 0 + _ = p.f // ERROR p\.f undefined + p.f /* ERROR p\.f undefined */ = 0 } func f0t[P ~struct{f int}](p P) { - _ = p.f - p.f = 0 + _ = p.f // ERROR p\.f undefined + p.f /* ERROR p\.f undefined */ = 0 } var _ = f0[Sf] @@ -25,8 +29,8 @@ var _ = f0[Sm /* ERROR does not implement */ ] var _ = f0t[Sm /* ERROR does not implement */ ] func f1[P interface{ Sf; m() }](p P) { - _ = p.f - p.f = 0 + _ = p.f // ERROR p\.f undefined + p.f /* ERROR p\.f undefined */ = 0 p.m() } @@ -44,20 +48,20 @@ type Sfm struct { func (Sfm) m() {} func f2[P interface{ Sfm; m() }](p P) { - _ = p.f - p.f = 0 + _ = p.f // ERROR p\.f undefined + p.f /* ERROR p\.f undefined */ = 0 p.m() } var _ = f2[Sfm] -// special case: structural type is a named pointer type +// special case: core type is a named pointer type type PSfm *Sfm func f3[P interface{ PSfm }](p P) { - _ = p.f - p.f = 0 + _ = p.f // ERROR p\.f undefined + p.f /* ERROR p\.f undefined */ = 0 p.m /* ERROR type P has no field or method m */ () } diff --git a/src/go/types/testdata/fixedbugs/issue50755.go2 b/src/go/types/testdata/fixedbugs/issue50755.go2 new file mode 100644 index 0000000000..afc7b2414c --- /dev/null +++ b/src/go/types/testdata/fixedbugs/issue50755.go2 @@ -0,0 +1,47 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +// The core type of M2 unifies with the type of m1 +// during function argument type inference. +// M2's constraint is unnamed. +func f1[K1 comparable, E1 any](m1 map[K1]E1) {} + +func f2[M2 map[string]int](m2 M2) { + f1(m2) +} + +// The core type of M3 unifies with the type of m1 +// during function argument type inference. +// M3's constraint is named. +type Map3 map[string]int + +func f3[M3 Map3](m3 M3) { + f1(m3) +} + +// The core type of M5 unifies with the core type of M4 +// during constraint type inference. +func f4[M4 map[K4]int, K4 comparable](m4 M4) {} + +func f5[M5 map[K5]int, K5 comparable](m5 M5) { + f4(m5) +} + +// test case from issue + +func Copy[MC ~map[KC]VC, KC comparable, VC any](dst, src MC) { + for k, v := range src { + dst[k] = v + } +} + +func Merge[MM ~map[KM]VM, KM comparable, VM any](ms ...MM) MM { + result := MM{} + for _, m := range ms { + Copy(result, m) + } + return result +} diff --git a/src/go/types/testdata/fixedbugs/issue50782.go2 b/src/go/types/testdata/fixedbugs/issue50782.go2 index 8f41b84163..fd1ab11b8c 100644 --- a/src/go/types/testdata/fixedbugs/issue50782.go2 +++ b/src/go/types/testdata/fixedbugs/issue50782.go2 @@ -2,6 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// Field accesses through type parameters are disabled +// until we have a more thorough understanding of the +// implications on the spec. See issue #51576. + package p // The first example from the issue. @@ -18,9 +22,12 @@ type numericAbs[T Numeric] interface { // AbsDifference computes the absolute value of the difference of // a and b, where the absolute value is determined by the Abs method. func absDifference[T numericAbs[T /* ERROR T does not implement Numeric */]](a, b T) T { - // TODO: the error below should probably be positioned on the '-'. - d := a /* ERROR "invalid operation: operator - not defined" */ .Value - b.Value - return d.Abs() + // Field accesses are not permitted for now. Keep an error so + // we can find and fix this code once the situation changes. + return a.Value // ERROR a\.Value undefined + // TODO: The error below should probably be positioned on the '-'. + // d := a /* ERROR "invalid operation: operator - not defined" */ .Value - b.Value + // return d.Abs() } // The second example from the issue. diff --git a/src/go/types/testdata/fixedbugs/issue50929.go2 b/src/go/types/testdata/fixedbugs/issue50929.go2 index 941dbaa3c1..3629ecf104 100644 --- a/src/go/types/testdata/fixedbugs/issue50929.go2 +++ b/src/go/types/testdata/fixedbugs/issue50929.go2 @@ -16,7 +16,7 @@ func G[A, B any](F[A, B]) { func _() { // TODO(gri) only report one error below (issue #50932) - var x F /* ERROR cannot infer B */ /* ERROR got 1 arguments but 2 type parameters */ [int] + var x F /* ERROR got 1 arguments but 2 type parameters */ [int] G(x /* ERROR does not match */) } @@ -46,9 +46,9 @@ func NSG[G any](c RSC[G]) { fmt.Println(c) } -func MMD[Rc RC /* ERROR cannot infer RG */ /* ERROR got 1 arguments */ [RG], RG any, G any]() M /* ERROR got 2 arguments */ /* ERROR Rc does not match */ [Rc, RG] { +func MMD[Rc RC /* ERROR got 1 arguments */ [RG], RG any, G any]() M /* ERROR got 2 arguments */ [Rc, RG] { - var nFn NFn /* ERROR got 2 arguments */ /* ERROR Rc does not match */ [Rc, RG] + var nFn NFn /* ERROR got 2 arguments */ [Rc, RG] var empty Rc switch any(empty).(type) { diff --git a/src/go/types/testdata/fixedbugs/issue51145.go b/src/go/types/testdata/fixedbugs/issue51145.go new file mode 100644 index 0000000000..b84391df19 --- /dev/null +++ b/src/go/types/testdata/fixedbugs/issue51145.go @@ -0,0 +1,18 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +import "fmt" + +type ( + _ [fmt /* ERROR invalid array length fmt */ ]int + _ [float64 /* ERROR invalid array length float64 */ ]int + _ [f /* ERROR invalid array length f */ ]int + _ [nil /* ERROR invalid array length nil */ ]int +) + +func f() + +var _ fmt.Stringer // use fmt diff --git a/src/go/types/testdata/fixedbugs/issue51158.go2 b/src/go/types/testdata/fixedbugs/issue51158.go2 new file mode 100644 index 0000000000..3edc505382 --- /dev/null +++ b/src/go/types/testdata/fixedbugs/issue51158.go2 @@ -0,0 +1,18 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +// Type checking the following code should not cause an infinite recursion. +func f[M map[K]int, K comparable](m M) { + f(m) +} + +// Equivalent code using mutual recursion. +func f1[M map[K]int, K comparable](m M) { + f2(m) +} +func f2[M map[K]int, K comparable](m M) { + f1(m) +} diff --git a/src/go/types/testdata/fixedbugs/issue51229.go2 b/src/go/types/testdata/fixedbugs/issue51229.go2 new file mode 100644 index 0000000000..808b6471f6 --- /dev/null +++ b/src/go/types/testdata/fixedbugs/issue51229.go2 @@ -0,0 +1,164 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +// Constraint type inference should be independent of the +// ordering of the type parameter declarations. Try all +// permutations in the test case below. +// Permutations produced by https://go.dev/play/p/PHcZNGJTEBZ. + +func f00[S1 ~[]E1, S2 ~[]E2, E1 ~byte, E2 ~byte](S1, S2) {} +func f01[S2 ~[]E2, S1 ~[]E1, E1 ~byte, E2 ~byte](S1, S2) {} +func f02[E1 ~byte, S1 ~[]E1, S2 ~[]E2, E2 ~byte](S1, S2) {} +func f03[S1 ~[]E1, E1 ~byte, S2 ~[]E2, E2 ~byte](S1, S2) {} +func f04[S2 ~[]E2, E1 ~byte, S1 ~[]E1, E2 ~byte](S1, S2) {} +func f05[E1 ~byte, S2 ~[]E2, S1 ~[]E1, E2 ~byte](S1, S2) {} +func f06[E2 ~byte, S2 ~[]E2, S1 ~[]E1, E1 ~byte](S1, S2) {} +func f07[S2 ~[]E2, E2 ~byte, S1 ~[]E1, E1 ~byte](S1, S2) {} +func f08[S1 ~[]E1, E2 ~byte, S2 ~[]E2, E1 ~byte](S1, S2) {} +func f09[E2 ~byte, S1 ~[]E1, S2 ~[]E2, E1 ~byte](S1, S2) {} +func f10[S2 ~[]E2, S1 ~[]E1, E2 ~byte, E1 ~byte](S1, S2) {} +func f11[S1 ~[]E1, S2 ~[]E2, E2 ~byte, E1 ~byte](S1, S2) {} +func f12[S1 ~[]E1, E1 ~byte, E2 ~byte, S2 ~[]E2](S1, S2) {} +func f13[E1 ~byte, S1 ~[]E1, E2 ~byte, S2 ~[]E2](S1, S2) {} +func f14[E2 ~byte, S1 ~[]E1, E1 ~byte, S2 ~[]E2](S1, S2) {} +func f15[S1 ~[]E1, E2 ~byte, E1 ~byte, S2 ~[]E2](S1, S2) {} +func f16[E1 ~byte, E2 ~byte, S1 ~[]E1, S2 ~[]E2](S1, S2) {} +func f17[E2 ~byte, E1 ~byte, S1 ~[]E1, S2 ~[]E2](S1, S2) {} +func f18[E2 ~byte, E1 ~byte, S2 ~[]E2, S1 ~[]E1](S1, S2) {} +func f19[E1 ~byte, E2 ~byte, S2 ~[]E2, S1 ~[]E1](S1, S2) {} +func f20[S2 ~[]E2, E2 ~byte, E1 ~byte, S1 ~[]E1](S1, S2) {} +func f21[E2 ~byte, S2 ~[]E2, E1 ~byte, S1 ~[]E1](S1, S2) {} +func f22[E1 ~byte, S2 ~[]E2, E2 ~byte, S1 ~[]E1](S1, S2) {} +func f23[S2 ~[]E2, E1 ~byte, E2 ~byte, S1 ~[]E1](S1, S2) {} + +type myByte byte + +func _(a []byte, b []myByte) { + f00(a, b) + f01(a, b) + f02(a, b) + f03(a, b) + f04(a, b) + f05(a, b) + f06(a, b) + f07(a, b) + f08(a, b) + f09(a, b) + f10(a, b) + f11(a, b) + f12(a, b) + f13(a, b) + f14(a, b) + f15(a, b) + f16(a, b) + f17(a, b) + f18(a, b) + f19(a, b) + f20(a, b) + f21(a, b) + f22(a, b) + f23(a, b) +} + +// Constraint type inference may have to iterate. +// Again, the order of the type parameters shouldn't matter. + +func g0[S ~[]E, M ~map[string]S, E any](m M) {} +func g1[M ~map[string]S, S ~[]E, E any](m M) {} +func g2[E any, S ~[]E, M ~map[string]S](m M) {} +func g3[S ~[]E, E any, M ~map[string]S](m M) {} +func g4[M ~map[string]S, E any, S ~[]E](m M) {} +func g5[E any, M ~map[string]S, S ~[]E](m M) {} + +func _(m map[string][]byte) { + g0(m) + g1(m) + g2(m) + g3(m) + g4(m) + g5(m) +} + +// Worst-case scenario. +// There are 10 unknown type parameters. In each iteration of +// constraint type inference we infer one more, from right to left. +// Each iteration looks repeatedly at all 11 type parameters, +// requiring a total of 10*11 = 110 iterations with the current +// implementation. Pathological case. + +func h[K any, J ~*K, I ~*J, H ~*I, G ~*H, F ~*G, E ~*F, D ~*E, C ~*D, B ~*C, A ~*B](x A) {} + +func _(x **********int) { + h(x) +} + +// Examples with channel constraints and tilde. + +func ch1[P chan<- int]() (_ P) { return } // core(P) == chan<- int (single type, no tilde) +func ch2[P ~chan int]() { return } // core(P) == ~chan<- int (tilde) +func ch3[P chan E, E any](E) { return } // core(P) == chan<- E (single type, no tilde) +func ch4[P chan E | ~chan<- E, E any](E) { return } // core(P) == ~chan<- E (tilde) +func ch5[P chan int | chan<- int]() { return } // core(P) == chan<- int (not a single type) + +func _() { + // P can be inferred as there's a single specific type and no tilde. + var _ chan int = ch1 /* ERROR cannot use ch1.*value of type chan<- int */ () + var _ chan<- int = ch1() + + // P cannot be inferred as there's a tilde. + ch2 /* ERROR cannot infer P */ () + type myChan chan int + ch2[myChan]() + + // P can be inferred as there's a single specific type and no tilde. + var e int + ch3(e) + + // P cannot be inferred as there's more than one specific type and a tilde. + ch4 /* ERROR cannot infer P */ (e) + _ = ch4[chan int] + + // P cannot be inferred as there's more than one specific type. + ch5 /* ERROR cannot infer P */ () + ch5[chan<- int]() +} + +// test case from issue + +func equal[M1 ~map[K1]V1, M2 ~map[K2]V2, K1, K2 ~uint32, V1, V2 ~string](m1 M1, m2 M2) bool { + if len(m1) != len(m2) { + return false + } + for k, v1 := range m1 { + if v2, ok := m2[K2(k)]; !ok || V2(v1) != v2 { + return false + } + } + return true +} + +func equalFixed[K1, K2 ~uint32, V1, V2 ~string](m1 map[K1]V1, m2 map[K2]V2) bool { + if len(m1) != len(m2) { + return false + } + for k, v1 := range m1 { + if v2, ok := m2[K2(k)]; !ok || v1 != V1(v2) { + return false + } + } + return true +} + +type ( + someNumericID uint32 + someStringID string +) + +func _() { + foo := map[uint32]string{10: "bar"} + bar := map[someNumericID]someStringID{10: "bar"} + equal(foo, bar) +} diff --git a/src/go/types/testdata/fixedbugs/issue51232.go2 b/src/go/types/testdata/fixedbugs/issue51232.go2 new file mode 100644 index 0000000000..3fa6a05732 --- /dev/null +++ b/src/go/types/testdata/fixedbugs/issue51232.go2 @@ -0,0 +1,30 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type RC[RG any] interface { + ~[]RG +} + +type Fn[RCT RC[RG], RG any] func(RCT) + +type F[RCT RC[RG], RG any] interface { + Fn() Fn /* ERROR got 1 arguments */ [RCT] +} + +type concreteF[RCT RC[RG], RG any] struct { + makeFn func() Fn /* ERROR got 1 arguments */ [RCT] +} + +func (c *concreteF[RCT, RG]) Fn() Fn /* ERROR got 1 arguments */ [RCT] { + return c.makeFn() +} + +func NewConcrete[RCT RC[RG], RG any](Rc RCT) F /* ERROR got 1 arguments */ [RCT] { + // TODO(rfindley): eliminate the duplicate error below. + return & /* ERROR cannot use .* as F\[RCT\] */ concreteF /* ERROR got 1 arguments */ [RCT]{ + makeFn: nil, + } +} diff --git a/src/go/types/testdata/fixedbugs/issue51233.go2 b/src/go/types/testdata/fixedbugs/issue51233.go2 new file mode 100644 index 0000000000..9c15028c91 --- /dev/null +++ b/src/go/types/testdata/fixedbugs/issue51233.go2 @@ -0,0 +1,27 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +// As of issue #51527, type-type inference has been disabled. + +type RC[RG any] interface { + ~[]RG +} + +type Fn[RCT RC[RG], RG any] func(RCT) + +type FFn[RCT RC[RG], RG any] func() Fn /* ERROR got 1 arguments */ [RCT] + +type F[RCT RC[RG], RG any] interface { + Fn() Fn /* ERROR got 1 arguments */ [RCT] +} + +type concreteF[RCT RC[RG], RG any] struct { + makeFn FFn /* ERROR got 1 arguments */ [RCT] +} + +func (c *concreteF[RCT, RG]) Fn() Fn /* ERROR got 1 arguments */ [RCT] { + return c.makeFn() +} diff --git a/src/go/types/testdata/fixedbugs/issue51257.go2 b/src/go/types/testdata/fixedbugs/issue51257.go2 new file mode 100644 index 0000000000..8a3eb3278d --- /dev/null +++ b/src/go/types/testdata/fixedbugs/issue51257.go2 @@ -0,0 +1,46 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +func f[_ comparable]() {} + +type S1 struct{ x int } +type S2 struct{ x any } +type S3 struct{ x [10]interface{ m() } } + +func _[P1 comparable, P2 S2]() { + _ = f[S1] + _ = f[S2 /* ERROR S2 does not implement comparable */ ] + _ = f[S3 /* ERROR S3 does not implement comparable */ ] + + type L1 struct { x P1 } + type L2 struct { x P2 } + _ = f[L1] + _ = f[L2 /* ERROR L2 does not implement comparable */ ] +} + + +// example from issue + +type Set[T comparable] map[T]struct{} + +func NewSetFromSlice[T comparable](items []T) *Set[T] { + s := Set[T]{} + + for _, item := range items { + s[item] = struct{}{} + } + + return &s +} + +type T struct{ x any } + +func main() { + NewSetFromSlice /* ERROR T does not implement comparable */ ([]T{ + {"foo"}, + {5}, + }) +} diff --git a/src/go/types/testdata/fixedbugs/issue51335.go2 b/src/go/types/testdata/fixedbugs/issue51335.go2 new file mode 100644 index 0000000000..0b5a1af082 --- /dev/null +++ b/src/go/types/testdata/fixedbugs/issue51335.go2 @@ -0,0 +1,16 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type S1 struct{} +type S2 struct{} + +func _[P *S1|*S2]() { + _= []P{{ /* ERROR invalid composite literal element type P: no core type */ }} +} + +func _[P *S1|S1]() { + _= []P{{ /* ERROR invalid composite literal element type P: no core type */ }} +} diff --git a/src/go/types/testdata/fixedbugs/issue51339.go2 b/src/go/types/testdata/fixedbugs/issue51339.go2 new file mode 100644 index 0000000000..38f86109e3 --- /dev/null +++ b/src/go/types/testdata/fixedbugs/issue51339.go2 @@ -0,0 +1,18 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file is tested when running "go test -run Manual" +// without source arguments. Use for one-off debugging. + +package p + +type T[P any, B *P] struct{} + +func (T /* ERROR cannot use generic type */ ) m0() {} + +// TODO(rfindley): eliminate the duplicate errors here. +func (/* ERROR got 1 type parameter, but receiver base type declares 2 */ T /* ERROR got 1 arguments but 2 type parameters */ [_]) m1() {} +func (T[_, _]) m2() {} +// TODO(gri) this error is unfortunate (issue #51343) +func (T /* ERROR got 3 arguments but 2 type parameters */ [_, _, _]) m3() {} diff --git a/src/go/types/testdata/fixedbugs/issue51360.go b/src/go/types/testdata/fixedbugs/issue51360.go new file mode 100644 index 0000000000..fe3de04dbf --- /dev/null +++ b/src/go/types/testdata/fixedbugs/issue51360.go @@ -0,0 +1,13 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +func _() { + len.Println /* ERROR cannot select on len */ + len.Println /* ERROR cannot select on len */ () + _ = len.Println /* ERROR cannot select on len */ + _ = len /* ERROR cannot index len */ [0] + _ = *len /* ERROR cannot indirect len */ +} diff --git a/src/go/types/testdata/fixedbugs/issue51376.go2 b/src/go/types/testdata/fixedbugs/issue51376.go2 new file mode 100644 index 0000000000..d51607b7ab --- /dev/null +++ b/src/go/types/testdata/fixedbugs/issue51376.go2 @@ -0,0 +1,24 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type Map map[string]int + +func f[M ~map[K]V, K comparable, V any](M) {} +func g[M map[K]V, K comparable, V any](M) {} + +func _[M1 ~map[K]V, M2 map[K]V, K comparable, V any]() { + var m1 M1 + f(m1) + g /* ERROR M1 does not implement map\[K\]V */ (m1) // M1 has tilde + + var m2 M2 + f(m2) + g(m2) // M1 does not have tilde + + var m3 Map + f(m3) + g /* ERROR Map does not implement map\[string\]int */ (m3) // M in g does not have tilde +} diff --git a/src/go/types/testdata/fixedbugs/issue51386.go2 b/src/go/types/testdata/fixedbugs/issue51386.go2 new file mode 100644 index 0000000000..ef6223927a --- /dev/null +++ b/src/go/types/testdata/fixedbugs/issue51386.go2 @@ -0,0 +1,17 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type myString string + +func _[P ~string | ~[]byte | ~[]rune]() { + _ = P("") + const s myString = "" + _ = P(s) +} + +func _[P myString]() { + _ = P("") +} diff --git a/src/go/types/testdata/fixedbugs/issue51437.go b/src/go/types/testdata/fixedbugs/issue51437.go new file mode 100644 index 0000000000..376261516e --- /dev/null +++ b/src/go/types/testdata/fixedbugs/issue51437.go @@ -0,0 +1,17 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type T struct{} + +func (T) m() []int { return nil } + +func f(x T) { + for _, x := range func() []int { + return x.m() // x declared in parameter list of f + }() { + _ = x // x declared by range clause + } +} diff --git a/src/go/types/testdata/fixedbugs/issue51472.go2 b/src/go/types/testdata/fixedbugs/issue51472.go2 new file mode 100644 index 0000000000..3126770829 --- /dev/null +++ b/src/go/types/testdata/fixedbugs/issue51472.go2 @@ -0,0 +1,54 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +func _[T comparable](x T) { + _ = x == x +} + +func _[T interface{interface{comparable}}](x T) { + _ = x == x +} + +func _[T interface{comparable; interface{comparable}}](x T) { + _ = x == x +} + +func _[T interface{comparable; ~int}](x T) { + _ = x == x +} + +func _[T interface{comparable; ~[]byte}](x T) { + _ = x /* ERROR cannot compare */ == x +} + +// TODO(gri) The error message here should be better. See issue #51525. +func _[T interface{comparable; ~int; ~string}](x T) { + _ = x /* ERROR cannot compare */ == x +} + +// TODO(gri) The error message here should be better. See issue #51525. +func _[T interface{~int; ~string}](x T) { + _ = x /* ERROR cannot compare */ == x +} + +func _[T interface{comparable; interface{~int}; interface{int|float64}}](x T) { + _ = x == x +} + +func _[T interface{interface{comparable; ~int}; interface{~float64; comparable; m()}}](x T) { + _ = x /* ERROR cannot compare */ == x +} + +// test case from issue + +func f[T interface{comparable; []byte|string}](x T) { + _ = x == x +} + +func _(s []byte) { + f /* ERROR \[\]byte does not implement interface{comparable; \[\]byte\|string} */ (s) + _ = f[[ /* ERROR does not implement */ ]byte] +} diff --git a/src/go/types/testdata/fixedbugs/issue51509.go b/src/go/types/testdata/fixedbugs/issue51509.go new file mode 100644 index 0000000000..5ae47176d0 --- /dev/null +++ b/src/go/types/testdata/fixedbugs/issue51509.go @@ -0,0 +1,7 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type T /* ERROR illegal cycle */ T.x diff --git a/src/go/types/testdata/fixedbugs/issue51578.go2 b/src/go/types/testdata/fixedbugs/issue51578.go2 new file mode 100644 index 0000000000..5c204bae20 --- /dev/null +++ b/src/go/types/testdata/fixedbugs/issue51578.go2 @@ -0,0 +1,17 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +var _ = (*interface /* ERROR interface contains type constraints */ {int})(nil) + +// abbreviated test case from issue + +type TypeSet interface{ int | string } + +func _() { + f((*TypeSet /* ERROR interface contains type constraints */)(nil)) +} + +func f(any) {} \ No newline at end of file diff --git a/src/go/types/testdata/fixedbugs/issue51593.go2 b/src/go/types/testdata/fixedbugs/issue51593.go2 new file mode 100644 index 0000000000..e06c39fac0 --- /dev/null +++ b/src/go/types/testdata/fixedbugs/issue51593.go2 @@ -0,0 +1,13 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +func f[P interface{ m(R) }, R any]() {} + +type T = interface { m(int) } + +func _() { + _ = f /* ERROR cannot infer R */ [T] // don't crash in type inference +} diff --git a/src/go/types/type.go b/src/go/types/type.go index 3acb19c412..130637530b 100644 --- a/src/go/types/type.go +++ b/src/go/types/type.go @@ -7,9 +7,7 @@ package types // A Type represents a type of Go. // All types implement the Type interface. type Type interface { - // Underlying returns the underlying type of a type - // w/o following forwarding chains. Only used by - // client packages. + // Underlying returns the underlying type of a type. Underlying() Type // String returns a string representation of a type. @@ -27,13 +25,13 @@ func under(t Type) Type { return t.Underlying() } -// If t is not a type parameter, structuralType returns the underlying type. -// If t is a type parameter, structuralType returns the single underlying +// If t is not a type parameter, coreType returns the underlying type. +// If t is a type parameter, coreType returns the single underlying // type of all types in its type set if it exists, or nil otherwise. If the // type set contains only unrestricted and restricted channel types (with // identical element types), the single underlying type is the restricted // channel type if the restrictions are always the same, or nil otherwise. -func structuralType(t Type) Type { +func coreType(t Type) Type { tpar, _ := t.(*TypeParam) if tpar == nil { return under(t) @@ -59,10 +57,10 @@ func structuralType(t Type) Type { return nil } -// structuralString is like structuralType but also considers []byte +// coreString is like coreType but also considers []byte // and strings as identical. In this case, if successful and we saw // a string, the result is of type (possibly untyped) string. -func structuralString(t Type) Type { +func coreString(t Type) Type { tpar, _ := t.(*TypeParam) if tpar == nil { return under(t) // string or untyped string diff --git a/src/go/types/typeparam.go b/src/go/types/typeparam.go index 71e6861b87..40d96ac947 100644 --- a/src/go/types/typeparam.go +++ b/src/go/types/typeparam.go @@ -30,11 +30,13 @@ type TypeParam struct { // or Signature type by calling SetTypeParams. Setting a type parameter on more // than one type will result in a panic. // -// The constraint argument can be nil, and set later via SetConstraint. +// The constraint argument can be nil, and set later via SetConstraint. If the +// constraint is non-nil, it must be fully defined. func NewTypeParam(obj *TypeName, constraint Type) *TypeParam { return (*Checker)(nil).newTypeParam(obj, constraint) } +// check may be nil func (check *Checker) newTypeParam(obj *TypeName, constraint Type) *TypeParam { // Always increment lastID, even if it is not used. id := nextID() @@ -49,9 +51,7 @@ func (check *Checker) newTypeParam(obj *TypeName, constraint Type) *TypeParam { // iface may mutate typ.bound, so we must ensure that iface() is called // at least once before the resulting TypeParam escapes. if check != nil { - check.later(func() { - typ.iface() - }) + check.needsCleanup(typ) } else if constraint != nil { typ.iface() } @@ -74,8 +74,10 @@ func (t *TypeParam) Constraint() Type { // SetConstraint sets the type constraint for t. // -// SetConstraint should not be called concurrently, but once SetConstraint -// returns the receiver t is safe for concurrent use. +// It must be called by users of NewTypeParam after the bound's underlying is +// fully defined, and before using the type parameter in any way other than to +// form other types. Once SetConstraint returns the receiver, t is safe for +// concurrent use. func (t *TypeParam) SetConstraint(bound Type) { if bound == nil { panic("nil constraint") @@ -95,9 +97,12 @@ func (t *TypeParam) String() string { return TypeString(t, nil) } // ---------------------------------------------------------------------------- // Implementation +func (t *TypeParam) cleanup() { + t.iface() + t.check = nil +} + // iface returns the constraint interface of t. -// TODO(gri) If we make tparamIsIface the default, this should be renamed to under -// (similar to Named.under). func (t *TypeParam) iface() *Interface { bound := t.bound @@ -138,16 +143,6 @@ func (t *TypeParam) iface() *Interface { return ityp } -// singleType returns the single type of the type parameter constraint; or nil. -func (t *TypeParam) singleType() Type { - return t.iface().typeSet().singleType() -} - -// hasTerms reports whether the type parameter constraint has specific type terms. -func (t *TypeParam) hasTerms() bool { - return t.iface().typeSet().hasTerms() -} - // is calls f with the specific type terms of t's constraint and reports whether // all calls to f returned true. If there are no specific terms, is // returns the result of f(nil). diff --git a/src/go/types/typeset.go b/src/go/types/typeset.go index e1f73015b9..6603383ea3 100644 --- a/src/go/types/typeset.go +++ b/src/go/types/typeset.go @@ -15,18 +15,25 @@ import ( // API // A _TypeSet represents the type set of an interface. +// Because of existing language restrictions, methods can be "factored out" +// from the terms. The actual type set is the intersection of the type set +// implied by the methods and the type set described by the terms and the +// comparable bit. To test whether a type is included in a type set +// ("implements" relation), the type must implement all methods _and_ be +// an element of the type set described by the terms and the comparable bit. +// If the term list describes the set of all types and comparable is true, +// only comparable types are meant; in all other cases comparable is false. type _TypeSet struct { - comparable bool // if set, the interface is or embeds comparable - // TODO(gri) consider using a set for the methods for faster lookup - methods []*Func // all methods of the interface; sorted by unique ID - terms termlist // type terms of the type set + methods []*Func // all methods of the interface; sorted by unique ID + terms termlist // type terms of the type set + comparable bool // invariant: !comparable || terms.isAll() } // IsEmpty reports whether type set s is the empty set. func (s *_TypeSet) IsEmpty() bool { return s.terms.isEmpty() } // IsAll reports whether type set s is the set of all types (corresponding to the empty interface). -func (s *_TypeSet) IsAll() bool { return !s.comparable && len(s.methods) == 0 && s.terms.isAll() } +func (s *_TypeSet) IsAll() bool { return s.IsMethodSet() && len(s.methods) == 0 } // IsMethodSet reports whether the interface t is fully described by its method set. func (s *_TypeSet) IsMethodSet() bool { return !s.comparable && s.terms.isAll() } @@ -37,17 +44,10 @@ func (s *_TypeSet) IsComparable(seen map[Type]bool) bool { return s.comparable } return s.is(func(t *term) bool { - return t != nil && comparable(t.typ, seen, nil) + return t != nil && comparable(t.typ, false, seen, nil) }) } -// TODO(gri) IsTypeSet is not a great name for this predicate. Find a better one. - -// IsTypeSet reports whether the type set s is represented by a finite set of underlying types. -func (s *_TypeSet) IsTypeSet() bool { - return !s.comparable && len(s.methods) == 0 -} - // NumMethods returns the number of methods available. func (s *_TypeSet) NumMethods() int { return len(s.methods) } @@ -101,9 +101,6 @@ func (s *_TypeSet) String() string { // hasTerms reports whether the type set has specific type terms. func (s *_TypeSet) hasTerms() bool { return !s.terms.isEmpty() && !s.terms.isAll() } -// singleType returns the single type in s if there is exactly one; otherwise the result is nil. -func (s *_TypeSet) singleType() Type { return s.terms.singleType() } - // subsetOf reports whether s1 ⊆ s2. func (s1 *_TypeSet) subsetOf(s2 *_TypeSet) bool { return s1.terms.subsetOf(s2.terms) } @@ -220,12 +217,12 @@ func computeInterfaceTypeSet(check *Checker, pos token.Pos, ityp *Interface) *_T var todo []*Func var seen objset - var methods []*Func + var allMethods []*Func mpos := make(map[*Func]token.Pos) // method specification or method embedding position, for good error messages addMethod := func(pos token.Pos, m *Func, explicit bool) { switch other := seen.insert(m); { case other == nil: - methods = append(methods, m) + allMethods = append(allMethods, m) mpos[m] = pos case explicit: if check == nil { @@ -260,7 +257,8 @@ func computeInterfaceTypeSet(check *Checker, pos token.Pos, ityp *Interface) *_T } // collect embedded elements - var allTerms = allTermlist + allTerms := allTermlist + allComparable := false for i, typ := range ityp.embeddeds { // The embedding position is nil for imported interfaces // and also for interface copies after substitution (but @@ -269,6 +267,7 @@ func computeInterfaceTypeSet(check *Checker, pos token.Pos, ityp *Interface) *_T if ityp.embedPos != nil { pos = (*ityp.embedPos)[i] } + var comparable bool var terms termlist switch u := under(typ).(type) { case *Interface: @@ -280,9 +279,7 @@ func computeInterfaceTypeSet(check *Checker, pos token.Pos, ityp *Interface) *_T check.errorf(atPos(pos), _UnsupportedFeature, "embedding constraint interface %s requires go1.18 or later", typ) continue } - if tset.comparable { - ityp.tset.comparable = true - } + comparable = tset.comparable for _, m := range tset.methods { addMethod(pos, m, false) // use embedding position pos rather than m.pos } @@ -296,6 +293,8 @@ func computeInterfaceTypeSet(check *Checker, pos token.Pos, ityp *Interface) *_T if tset == &invalidTypeSet { continue // ignore invalid unions } + assert(!tset.comparable) + assert(len(tset.methods) == 0) terms = tset.terms default: if u == Typ[Invalid] { @@ -307,11 +306,11 @@ func computeInterfaceTypeSet(check *Checker, pos token.Pos, ityp *Interface) *_T } terms = termlist{{false, typ}} } - // The type set of an interface is the intersection - // of the type sets of all its elements. - // Intersection cannot produce longer termlists and - // thus cannot overflow. - allTerms = allTerms.intersect(terms) + + // The type set of an interface is the intersection of the type sets of all its elements. + // Due to language restrictions, only embedded interfaces can add methods, they are handled + // separately. Here we only need to intersect the term lists and comparable bits. + allTerms, allComparable = intersectTermLists(allTerms, allComparable, terms, comparable) } ityp.embedPos = nil // not needed anymore (errors have been reported) @@ -324,15 +323,46 @@ func computeInterfaceTypeSet(check *Checker, pos token.Pos, ityp *Interface) *_T } } - if methods != nil { - sort.Sort(byUniqueMethodName(methods)) - ityp.tset.methods = methods + ityp.tset.comparable = allComparable + if len(allMethods) != 0 { + sortMethods(allMethods) + ityp.tset.methods = allMethods } ityp.tset.terms = allTerms return ityp.tset } +// TODO(gri) The intersectTermLists function belongs to the termlist implementation. +// The comparable type set may also be best represented as a term (using +// a special type). + +// intersectTermLists computes the intersection of two term lists and respective comparable bits. +// xcomp, ycomp are valid only if xterms.isAll() and yterms.isAll() respectively. +func intersectTermLists(xterms termlist, xcomp bool, yterms termlist, ycomp bool) (termlist, bool) { + terms := xterms.intersect(yterms) + // If one of xterms or yterms is marked as comparable, + // the result must only include comparable types. + comp := xcomp || ycomp + if comp && !terms.isAll() { + // only keep comparable terms + i := 0 + for _, t := range terms { + assert(t.typ != nil) + if Comparable(t.typ) { + terms[i] = t + i++ + } + } + terms = terms[:i] + if !terms.isAll() { + comp = false + } + } + assert(!comp || terms.isAll()) // comparable invariant + return terms, comp +} + func sortMethods(list []*Func) { sort.Sort(byUniqueMethodName(list)) } diff --git a/src/go/types/typeset_test.go b/src/go/types/typeset_test.go index 1c0eeceb8c..2bbe611376 100644 --- a/src/go/types/typeset_test.go +++ b/src/go/types/typeset_test.go @@ -26,9 +26,9 @@ func TestTypeSetString(t *testing.T) { "{int; string}": "∅", "{comparable}": "{comparable}", - "{comparable; int}": "{comparable; int}", - "{~int; comparable}": "{comparable; ~int}", - "{int|string; comparable}": "{comparable; int ∪ string}", + "{comparable; int}": "{int}", + "{~int; comparable}": "{~int}", + "{int|string; comparable}": "{int ∪ string}", "{comparable; int; string}": "∅", "{m()}": "{func (p.T).m()}", @@ -38,8 +38,8 @@ func TestTypeSetString(t *testing.T) { "{m1(); comparable; m2() int }": "{comparable; func (p.T).m1(); func (p.T).m2() int}", "{comparable; error}": "{comparable; func (error).Error() string}", - "{m(); comparable; int|float32|string}": "{comparable; func (p.T).m(); int ∪ float32 ∪ string}", - "{m1(); int; m2(); comparable }": "{comparable; func (p.T).m1(); func (p.T).m2(); int}", + "{m(); comparable; int|float32|string}": "{func (p.T).m(); int ∪ float32 ∪ string}", + "{m1(); int; m2(); comparable }": "{func (p.T).m1(); func (p.T).m2(); int}", "{E}; type E interface{}": "𝓤", "{E}; type E interface{int;string}": "∅", diff --git a/src/go/types/typexpr.go b/src/go/types/typexpr.go index 00c250b5b6..5bb2d8f811 100644 --- a/src/go/types/typexpr.go +++ b/src/go/types/typexpr.go @@ -144,10 +144,16 @@ func (check *Checker) typ(e ast.Expr) Type { // constraint interface. func (check *Checker) varType(e ast.Expr) Type { typ := check.definedType(e, nil) + check.validVarType(e, typ) + return typ +} +// validVarType reports an error if typ is a constraint interface. +// The expression e is used for error reporting, if any. +func (check *Checker) validVarType(e ast.Expr, typ Type) { // If we have a type parameter there's nothing to do. if isTypeParam(typ) { - return typ + return } // We don't want to call under() or complete interfaces while we are in @@ -165,8 +171,6 @@ func (check *Checker) varType(e ast.Expr) Type { } } }) - - return typ } // definedType is like typ but also accepts a type name def. @@ -254,7 +258,7 @@ func (check *Checker) typInternal(e0 ast.Expr, def *Named) (T Type) { case *ast.SelectorExpr: var x operand - check.selector(&x, e) + check.selector(&x, e, def) switch x.mode { case typexpr: @@ -323,7 +327,7 @@ func (check *Checker) typInternal(e0 ast.Expr, def *Named) (T Type) { return typ case *ast.InterfaceType: - typ := new(Interface) + typ := check.newInterface() def.setUnderlying(typ) if def != nil { typ.obj = def.obj @@ -415,10 +419,14 @@ func (check *Checker) instantiatedType(ix *typeparams.IndexExpr, def *Named) (re // evaluate arguments targs := check.typeList(ix.Indices) if targs == nil { - def.setUnderlying(Typ[Invalid]) // avoid later errors due to lazy instantiation + def.setUnderlying(Typ[Invalid]) // avoid errors later due to lazy instantiation return Typ[Invalid] } + // enableTypeTypeInference controls whether to infer missing type arguments + // using constraint type inference. See issue #51527. + const enableTypeTypeInference = false + // create the instance ctxt := check.bestContext(nil) h := ctxt.instanceHash(orig, targs) @@ -438,19 +446,18 @@ func (check *Checker) instantiatedType(ix *typeparams.IndexExpr, def *Named) (re def.setUnderlying(inst) inst.resolver = func(ctxt *Context, n *Named) (*TypeParamList, Type, *methodList) { - tparams := orig.TypeParams().list() + tparams := n.orig.TypeParams().list() - inferred := targs - if len(targs) < len(tparams) { + targs := n.targs.list() + if enableTypeTypeInference && len(targs) < len(tparams) { // If inference fails, len(inferred) will be 0, and inst.underlying will // be set to Typ[Invalid] in expandNamed. - inferred = check.infer(ix.Orig, tparams, targs, nil, nil) + inferred := check.infer(ix.Orig, tparams, targs, nil, nil) if len(inferred) > len(targs) { - inst.targs = newTypeList(inferred) + n.targs = newTypeList(inferred) } } - check.recordInstance(ix.Orig, inferred, inst) return expandNamed(ctxt, n, pos) } @@ -463,6 +470,7 @@ func (check *Checker) instantiatedType(ix *typeparams.IndexExpr, def *Named) (re // Since check is non-nil, we can still mutate inst. Unpinning the resolver // frees some memory. inst.resolver = nil + check.recordInstance(ix.Orig, inst.TypeArgs().list(), inst) if check.validateTArgLen(pos, inst.tparams.Len(), inst.targs.Len()) { if i, err := check.verify(pos, inst.tparams.list(), inst.targs.list()); err != nil { @@ -487,12 +495,20 @@ func (check *Checker) instantiatedType(ix *typeparams.IndexExpr, def *Named) (re // and returns the constant length >= 0, or a value < 0 // to indicate an error (and thus an unknown length). func (check *Checker) arrayLength(e ast.Expr) int64 { - // If e is an undeclared identifier, the array declaration might be an - // attempt at a parameterized type declaration with missing constraint. - // Provide a better error message than just "undeclared name: X". - if name, _ := e.(*ast.Ident); name != nil && check.lookup(name.Name) == nil { - check.errorf(name, _InvalidArrayLen, "undeclared name %s for array length", name.Name) - return -1 + // If e is an identifier, the array declaration might be an + // attempt at a parameterized type declaration with missing + // constraint. Provide an error message that mentions array + // length. + if name, _ := e.(*ast.Ident); name != nil { + obj := check.lookup(name.Name) + if obj == nil { + check.errorf(name, _InvalidArrayLen, "undeclared name %s for array length", name.Name) + return -1 + } + if _, ok := obj.(*Const); !ok { + check.errorf(name, _InvalidArrayLen, "invalid array length %s", name.Name) + return -1 + } } var x operand diff --git a/src/go/types/unify.go b/src/go/types/unify.go index be2037ca81..7b9aeeee0a 100644 --- a/src/go/types/unify.go +++ b/src/go/types/unify.go @@ -9,6 +9,7 @@ package types import ( "bytes" "fmt" + "strings" ) // The unifier maintains two separate sets of type parameters x and y @@ -26,7 +27,7 @@ import ( // parameter P ("x" side), but the argument type P must be left alone so // that unification resolves the type parameter P to P. // -// For bidirection unification, both sets are provided. This enables +// For bidirectional unification, both sets are provided. This enables // unification to go from argument to parameter type and vice versa. // For constraint type inference, we use bidirectional unification // where both the x and y type parameters are identical. This is done @@ -41,6 +42,19 @@ const ( // Whether to panic when unificationDepthLimit is reached. Turn on when // investigating infinite recursion. panicAtUnificationDepthLimit = false + + // If enableCoreTypeUnification is set, unification will consider + // the core types, if any, of non-local (unbound) type parameters. + enableCoreTypeUnification = true + + // If traceInference is set, unification will print a trace of its operation. + // Interpretation of trace: + // x ≡ y attempt to unify types x and y + // p ➞ y type parameter p is set to type y (p is inferred to be y) + // p ⇄ q type parameters p and q match (p is inferred to be q and vice versa) + // x ≢ y types x and y cannot be unified + // [p, q, ...] ➞ [x, y, ...] mapping from type parameters to types + traceInference = false ) // A unifier maintains the current type parameters for x and y @@ -58,6 +72,7 @@ type unifier struct { // exactly. If exact is not set, a named type's underlying type // is considered if unification would fail otherwise, and the // direction of channels is ignored. +// TODO(gri) exact is not set anymore by a caller. Consider removing it. func newUnifier(exact bool) *unifier { u := &unifier{exact: exact} u.x.unifier = u @@ -70,6 +85,10 @@ func (u *unifier) unify(x, y Type) bool { return u.nify(x, y, nil) } +func (u *unifier) tracef(format string, args ...interface{}) { + fmt.Println(strings.Repeat(". ", u.depth) + sprintf(nil, nil, true, format, args...)) +} + // A tparamsList describes a list of type parameters and the types inferred for them. type tparamsList struct { unifier *unifier @@ -121,6 +140,9 @@ func (d *tparamsList) init(tparams []*TypeParam) { // If both type parameters already have a type associated with them and they are // not joined, join fails and returns false. func (u *unifier) join(i, j int) bool { + if traceInference { + u.tracef("%s ⇄ %s", u.x.tparams[i], u.y.tparams[j]) + } ti := u.x.indices[i] tj := u.y.indices[j] switch { @@ -210,6 +232,9 @@ func (d *tparamsList) at(i int) Type { func (d *tparamsList) set(i int, typ Type) { assert(typ != nil) u := d.unifier + if traceInference { + u.tracef("%s ➞ %s", d.tparams[i], typ) + } switch ti := d.indices[i]; { case ti < 0: u.types[-ti-1] = typ @@ -222,6 +247,17 @@ func (d *tparamsList) set(i int, typ Type) { } } +// unknowns returns the number of type parameters for which no type has been set yet. +func (d *tparamsList) unknowns() int { + n := 0 + for _, ti := range d.indices { + if ti <= 0 { + n++ + } + } + return n +} + // types returns the list of inferred types (via unification) for the type parameters // described by d, and an index. If all types were inferred, the returned index is < 0. // Otherwise, it is the index of the first type parameter which couldn't be inferred; @@ -247,9 +283,16 @@ func (u *unifier) nifyEq(x, y Type, p *ifacePair) bool { // adapted version of Checker.identical. For changes to that // code the corresponding changes should be made here. // Must not be called directly from outside the unifier. -func (u *unifier) nify(x, y Type, p *ifacePair) bool { +func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) { + if traceInference { + u.tracef("%s ≡ %s", x, y) + } + // Stop gap for cases where unification fails. if u.depth >= unificationDepthLimit { + if traceInference { + u.tracef("depth %d >= %d", u.depth, unificationDepthLimit) + } if panicAtUnificationDepthLimit { panic("unification reached recursion depth limit") } @@ -258,6 +301,9 @@ func (u *unifier) nify(x, y Type, p *ifacePair) bool { u.depth++ defer func() { u.depth-- + if traceInference && !result { + u.tracef("%s ≢ %s", x, y) + } }() if !u.exact { @@ -267,8 +313,14 @@ func (u *unifier) nify(x, y Type, p *ifacePair) bool { // (We use !hasName to exclude any type with a name, including // basic types and type parameters; the rest are unamed types.) if nx, _ := x.(*Named); nx != nil && !hasName(y) { + if traceInference { + u.tracef("under %s ≡ %s", nx, y) + } return u.nify(nx.under(), y, p) } else if ny, _ := y.(*Named); ny != nil && !hasName(x) { + if traceInference { + u.tracef("%s ≡ under %s", x, ny) + } return u.nify(x, ny.under(), p) } } @@ -302,6 +354,39 @@ func (u *unifier) nify(x, y Type, p *ifacePair) bool { return true } + // If we get here and x or y is a type parameter, they are type parameters + // from outside our declaration list. Try to unify their core types, if any + // (see issue #50755 for a test case). + if enableCoreTypeUnification && !u.exact { + if isTypeParam(x) && !hasName(y) { + // When considering the type parameter for unification + // we look at the adjusted core term (adjusted core type + // with tilde information). + // If the adjusted core type is a named type N; the + // corresponding core type is under(N). Since !u.exact + // and y doesn't have a name, unification will end up + // comparing under(N) to y, so we can just use the core + // type instead. And we can ignore the tilde because we + // already look at the underlying types on both sides + // and we have known types on both sides. + // Optimization. + if cx := coreType(x); cx != nil { + if traceInference { + u.tracef("core %s ≡ %s", x, y) + } + return u.nify(cx, y, p) + } + } else if isTypeParam(y) && !hasName(x) { + // see comment above + if cy := coreType(y); cy != nil { + if traceInference { + u.tracef("%s ≡ core %s", x, y) + } + return u.nify(x, cy, p) + } + } + } + // For type unification, do not shortcut (x == y) for identical // types. Instead keep comparing them element-wise to unify the // matching (and equal type parameter types). A simple test case @@ -490,7 +575,7 @@ func (u *unifier) nify(x, y Type, p *ifacePair) bool { // avoid a crash in case of nil type default: - panic(fmt.Sprintf("### u.nify(%s, %s), u.x.tparams = %s", x, y, u.x.tparams)) + panic(sprintf(nil, nil, true, "u.nify(%s, %s), u.x.tparams = %s", x, y, u.x.tparams)) } return false diff --git a/src/go/types/union.go b/src/go/types/union.go index 9c59279447..8397d65af0 100644 --- a/src/go/types/union.go +++ b/src/go/types/union.go @@ -103,25 +103,27 @@ func parseUnion(check *Checker, uexpr ast.Expr) Type { if !Identical(u, t.typ) { check.errorf(tlist[i], _InvalidUnion, "invalid use of ~ (underlying type of %s is %s)", t.typ, u) - continue // don't report another error for t + continue } } // Stand-alone embedded interfaces are ok and are handled by the single-type case // in the beginning. Embedded interfaces with tilde are excluded above. If we reach - // here, we must have at least two terms in the union. - if f != nil && !f.typeSet().IsTypeSet() { + // here, we must have at least two terms in the syntactic term list (but not necessarily + // in the term list of the union's type set). + if f != nil { + tset := f.typeSet() switch { - case f.typeSet().NumMethods() != 0: + case tset.NumMethods() != 0: check.errorf(tlist[i], _InvalidUnion, "cannot use %s in union (%s contains methods)", t, t) + continue case t.typ == universeComparable.Type(): check.error(tlist[i], _InvalidUnion, "cannot use comparable in union") - case f.typeSet().comparable: + continue + case tset.comparable: check.errorf(tlist[i], _InvalidUnion, "cannot use %s in union (%s embeds comparable)", t, t) - default: - panic("not a type set but no methods and not comparable") + continue } - continue // don't report another error for t } // Report overlapping (non-disjoint) terms such as diff --git a/src/go/types/universe.go b/src/go/types/universe.go index 3421634678..303ada4e57 100644 --- a/src/go/types/universe.go +++ b/src/go/types/universe.go @@ -112,7 +112,7 @@ func defPredeclaredTypes() { typ := NewNamed(obj, nil, nil) // interface{} // marked as comparable - ityp := &Interface{obj: obj, complete: true, tset: &_TypeSet{true, nil, allTermlist}} + ityp := &Interface{obj: obj, complete: true, tset: &_TypeSet{nil, allTermlist, true}} typ.SetUnderlying(ityp) def(obj) diff --git a/src/go/types/validtype.go b/src/go/types/validtype.go index c4ec2f2e0a..7d7029bce2 100644 --- a/src/go/types/validtype.go +++ b/src/go/types/validtype.go @@ -79,7 +79,7 @@ func (check *Checker) validType0(typ Type, env *tparamEnv, path []Object) typeIn // would have reported a type cycle and couldn't have been // imported in the first place. assert(t.obj.pkg == check.pkg) - t.underlying = Typ[Invalid] // t is in the current package (no race possibilty) + t.underlying = Typ[Invalid] // t is in the current package (no race possibility) // Find the starting point of the cycle and report it. for i, tn := range path { if tn == t.obj { diff --git a/src/internal/cfg/cfg.go b/src/internal/cfg/cfg.go index 4cb3fbd4f3..78664d7a96 100644 --- a/src/internal/cfg/cfg.go +++ b/src/internal/cfg/cfg.go @@ -62,6 +62,7 @@ const KnownEnv = ` GOTOOLDIR GOVCS GOWASM + GOWORK GO_EXTLINK_ENABLED PKG_CONFIG ` diff --git a/src/internal/fuzz/coverage.go b/src/internal/fuzz/coverage.go index 3dee73b81c..88f98a16b2 100644 --- a/src/internal/fuzz/coverage.go +++ b/src/internal/fuzz/coverage.go @@ -66,6 +66,17 @@ func countNewCoverageBits(base, snapshot []byte) int { return n } +// isCoverageSubset returns true if all the base coverage bits are set in +// snapshot +func isCoverageSubset(base, snapshot []byte) bool { + for i, v := range base { + if v&snapshot[i] != v { + return false + } + } + return true +} + // hasCoverageBit returns true if snapshot has at least one bit set that is // also set in base. func hasCoverageBit(base, snapshot []byte) bool { diff --git a/src/internal/fuzz/encoding.go b/src/internal/fuzz/encoding.go index 2bfa02b8c0..c95d9e088b 100644 --- a/src/internal/fuzz/encoding.go +++ b/src/internal/fuzz/encoding.go @@ -10,7 +10,9 @@ import ( "go/ast" "go/parser" "go/token" + "math" "strconv" + "unicode/utf8" ) // encVersion1 will be the first line of a file with version 1 encoding. @@ -27,13 +29,64 @@ func marshalCorpusFile(vals ...any) []byte { // instead of changing to byte and rune respectively. for _, val := range vals { switch t := val.(type) { - case int, int8, int16, int64, uint, uint16, uint32, uint64, float32, float64, bool: + case int, int8, int16, int64, uint, uint16, uint32, uint64, bool: fmt.Fprintf(b, "%T(%v)\n", t, t) + case float32: + if math.IsNaN(float64(t)) && math.Float32bits(t) != math.Float32bits(float32(math.NaN())) { + // We encode unusual NaNs as hex values, because that is how users are + // likely to encounter them in literature about floating-point encoding. + // This allows us to reproduce fuzz failures that depend on the specific + // NaN representation (for float32 there are about 2^24 possibilities!), + // not just the fact that the value is *a* NaN. + // + // Note that the specific value of float32(math.NaN()) can vary based on + // whether the architecture represents signaling NaNs using a low bit + // (as is common) or a high bit (as commonly implemented on MIPS + // hardware before around 2012). We believe that the increase in clarity + // from identifying "NaN" with math.NaN() is worth the slight ambiguity + // from a platform-dependent value. + fmt.Fprintf(b, "math.Float32frombits(0x%x)\n", math.Float32bits(t)) + } else { + // We encode all other values — including the NaN value that is + // bitwise-identical to float32(math.Nan()) — using the default + // formatting, which is equivalent to strconv.FormatFloat with format + // 'g' and can be parsed by strconv.ParseFloat. + // + // For an ordinary floating-point number this format includes + // sufficiently many digits to reconstruct the exact value. For positive + // or negative infinity it is the string "+Inf" or "-Inf". For positive + // or negative zero it is "0" or "-0". For NaN, it is the string "NaN". + fmt.Fprintf(b, "%T(%v)\n", t, t) + } + case float64: + if math.IsNaN(t) && math.Float64bits(t) != math.Float64bits(math.NaN()) { + fmt.Fprintf(b, "math.Float64frombits(0x%x)\n", math.Float64bits(t)) + } else { + fmt.Fprintf(b, "%T(%v)\n", t, t) + } case string: fmt.Fprintf(b, "string(%q)\n", t) case rune: // int32 - fmt.Fprintf(b, "rune(%q)\n", t) + // Although rune and int32 are represented by the same type, only a subset + // of valid int32 values can be expressed as rune literals. Notably, + // negative numbers, surrogate halves, and values above unicode.MaxRune + // have no quoted representation. + // + // fmt with "%q" (and the corresponding functions in the strconv package) + // would quote out-of-range values to the Unicode replacement character + // instead of the original value (see https://go.dev/issue/51526), so + // they must be treated as int32 instead. + // + // We arbitrarily draw the line at UTF-8 validity, which biases toward the + // "rune" interpretation. (However, we accept either format as input.) + if utf8.ValidRune(t) { + fmt.Fprintf(b, "rune(%q)\n", t) + } else { + fmt.Fprintf(b, "int32(%v)\n", t) + } case byte: // uint8 + // For bytes, we arbitrarily prefer the character interpretation. + // (Every byte has a valid character encoding.) fmt.Fprintf(b, "byte(%q)\n", t) case []byte: // []uint8 fmt.Fprintf(b, "[]byte(%q)\n", t) @@ -105,44 +158,78 @@ func parseCorpusValue(line []byte) (any, error) { return []byte(s), nil } - idType, ok := call.Fun.(*ast.Ident) - if !ok { - return nil, fmt.Errorf("expected []byte or primitive type") - } - if idType.Name == "bool" { - id, ok := arg.(*ast.Ident) + var idType *ast.Ident + if selector, ok := call.Fun.(*ast.SelectorExpr); ok { + xIdent, ok := selector.X.(*ast.Ident) + if !ok || xIdent.Name != "math" { + return nil, fmt.Errorf("invalid selector type") + } + switch selector.Sel.Name { + case "Float64frombits": + idType = &ast.Ident{Name: "float64-bits"} + case "Float32frombits": + idType = &ast.Ident{Name: "float32-bits"} + default: + return nil, fmt.Errorf("invalid selector type") + } + } else { + idType, ok = call.Fun.(*ast.Ident) if !ok { - return nil, fmt.Errorf("malformed bool") + return nil, fmt.Errorf("expected []byte or primitive type") } - if id.Name == "true" { - return true, nil - } else if id.Name == "false" { - return false, nil - } else { - return nil, fmt.Errorf("true or false required for type bool") + if idType.Name == "bool" { + id, ok := arg.(*ast.Ident) + if !ok { + return nil, fmt.Errorf("malformed bool") + } + if id.Name == "true" { + return true, nil + } else if id.Name == "false" { + return false, nil + } else { + return nil, fmt.Errorf("true or false required for type bool") + } } } + var ( val string kind token.Token ) if op, ok := arg.(*ast.UnaryExpr); ok { - // Special case for negative numbers. - lit, ok := op.X.(*ast.BasicLit) - if !ok || (lit.Kind != token.INT && lit.Kind != token.FLOAT) { + switch lit := op.X.(type) { + case *ast.BasicLit: + if op.Op != token.SUB { + return nil, fmt.Errorf("unsupported operation on int/float: %v", op.Op) + } + // Special case for negative numbers. + val = op.Op.String() + lit.Value // e.g. "-" + "124" + kind = lit.Kind + case *ast.Ident: + if lit.Name != "Inf" { + return nil, fmt.Errorf("expected operation on int or float type") + } + if op.Op == token.SUB { + val = "-Inf" + } else { + val = "+Inf" + } + kind = token.FLOAT + default: return nil, fmt.Errorf("expected operation on int or float type") } - if op.Op != token.SUB { - return nil, fmt.Errorf("unsupported operation on int: %v", op.Op) - } - val = op.Op.String() + lit.Value // e.g. "-" + "124" - kind = lit.Kind } else { - lit, ok := arg.(*ast.BasicLit) - if !ok { + switch lit := arg.(type) { + case *ast.BasicLit: + val, kind = lit.Value, lit.Kind + case *ast.Ident: + if lit.Name != "NaN" { + return nil, fmt.Errorf("literal value required for primitive type") + } + val, kind = "NaN", token.FLOAT + default: return nil, fmt.Errorf("literal value required for primitive type") } - val, kind = lit.Value, lit.Kind } switch typ := idType.Name; typ { @@ -152,6 +239,14 @@ func parseCorpusValue(line []byte) (any, error) { } return strconv.Unquote(val) case "byte", "rune": + if kind == token.INT { + switch typ { + case "rune": + return parseInt(val, typ) + case "byte": + return parseUint(val, typ) + } + } if kind != token.CHAR { return nil, fmt.Errorf("character literal required for byte/rune types") } @@ -191,6 +286,24 @@ func parseCorpusValue(line []byte) (any, error) { return nil, fmt.Errorf("float or integer literal required for float64 type") } return strconv.ParseFloat(val, 64) + case "float32-bits": + if kind != token.INT { + return nil, fmt.Errorf("integer literal required for math.Float32frombits type") + } + bits, err := parseUint(val, "uint32") + if err != nil { + return nil, err + } + return math.Float32frombits(bits.(uint32)), nil + case "float64-bits": + if kind != token.FLOAT && kind != token.INT { + return nil, fmt.Errorf("integer literal required for math.Float64frombits type") + } + bits, err := parseUint(val, "uint64") + if err != nil { + return nil, err + } + return math.Float64frombits(bits.(uint64)), nil default: return nil, fmt.Errorf("expected []byte or primitive type") } @@ -200,18 +313,24 @@ func parseCorpusValue(line []byte) (any, error) { func parseInt(val, typ string) (any, error) { switch typ { case "int": - return strconv.Atoi(val) + // The int type may be either 32 or 64 bits. If 32, the fuzz tests in the + // corpus may include 64-bit values produced by fuzzing runs on 64-bit + // architectures. When running those tests, we implicitly wrap the values to + // fit in a regular int. (The test case is still “interesting”, even if the + // specific values of its inputs are platform-dependent.) + i, err := strconv.ParseInt(val, 0, 64) + return int(i), err case "int8": - i, err := strconv.ParseInt(val, 10, 8) + i, err := strconv.ParseInt(val, 0, 8) return int8(i), err case "int16": - i, err := strconv.ParseInt(val, 10, 16) + i, err := strconv.ParseInt(val, 0, 16) return int16(i), err - case "int32": - i, err := strconv.ParseInt(val, 10, 32) + case "int32", "rune": + i, err := strconv.ParseInt(val, 0, 32) return int32(i), err case "int64": - return strconv.ParseInt(val, 10, 64) + return strconv.ParseInt(val, 0, 64) default: panic("unreachable") } @@ -221,19 +340,19 @@ func parseInt(val, typ string) (any, error) { func parseUint(val, typ string) (any, error) { switch typ { case "uint": - i, err := strconv.ParseUint(val, 10, 0) + i, err := strconv.ParseUint(val, 0, 64) return uint(i), err - case "uint8": - i, err := strconv.ParseUint(val, 10, 8) + case "uint8", "byte": + i, err := strconv.ParseUint(val, 0, 8) return uint8(i), err case "uint16": - i, err := strconv.ParseUint(val, 10, 16) + i, err := strconv.ParseUint(val, 0, 16) return uint16(i), err case "uint32": - i, err := strconv.ParseUint(val, 10, 32) + i, err := strconv.ParseUint(val, 0, 32) return uint32(i), err case "uint64": - return strconv.ParseUint(val, 10, 64) + return strconv.ParseUint(val, 0, 64) default: panic("unreachable") } diff --git a/src/internal/fuzz/encoding_test.go b/src/internal/fuzz/encoding_test.go index b429d429c6..8e3800eb77 100644 --- a/src/internal/fuzz/encoding_test.go +++ b/src/internal/fuzz/encoding_test.go @@ -5,85 +5,104 @@ package fuzz import ( + "math" "strconv" - "strings" "testing" + "unicode" ) func TestUnmarshalMarshal(t *testing.T) { var tests = []struct { - in string - ok bool + desc string + in string + reject bool + want string // if different from in }{ { - in: "int(1234)", - ok: false, // missing version + desc: "missing version", + in: "int(1234)", + reject: true, }, { + desc: "malformed string", in: `go test fuzz v1 string("a"bcad")`, - ok: false, // malformed + reject: true, }, { + desc: "empty value", in: `go test fuzz v1 int()`, - ok: false, // empty value + reject: true, }, { + desc: "negative uint", in: `go test fuzz v1 uint(-32)`, - ok: false, // invalid negative uint + reject: true, }, { + desc: "int8 too large", in: `go test fuzz v1 int8(1234456)`, - ok: false, // int8 too large + reject: true, }, { + desc: "multiplication in int value", in: `go test fuzz v1 int(20*5)`, - ok: false, // expression in int value + reject: true, }, { + desc: "double negation", in: `go test fuzz v1 int(--5)`, - ok: false, // expression in int value + reject: true, }, { + desc: "malformed bool", in: `go test fuzz v1 bool(0)`, - ok: false, // malformed bool + reject: true, }, { + desc: "malformed byte", in: `go test fuzz v1 byte('aa)`, - ok: false, // malformed byte + reject: true, }, { + desc: "byte out of range", in: `go test fuzz v1 byte('☃')`, - ok: false, // byte out of range + reject: true, }, { + desc: "extra newline", in: `go test fuzz v1 -string("has final newline") +string("has extra newline") `, - ok: true, // has final newline + want: `go test fuzz v1 +string("has extra newline")`, }, { + desc: "trailing spaces", in: `go test fuzz v1 string("extra") []byte("spacing") `, - ok: true, // extra spaces in the final newline + want: `go test fuzz v1 +string("extra") +[]byte("spacing")`, }, { + desc: "float types", in: `go test fuzz v1 float64(0) float32(0)`, - ok: true, // will be an integer literal since there is no decimal }, { + desc: "various types", in: `go test fuzz v1 int(-23) int8(-2) @@ -101,19 +120,112 @@ bool(true) string("hello\\xbd\\xb2=\\xbc ⌘") float64(-12.5) float32(2.5)`, - ok: true, + }, + { + desc: "float edge cases", + // The two IEEE 754 bit patterns used for the math.Float{64,32}frombits + // encodings are non-math.NAN quiet-NaN values. Since they are not equal + // to math.NaN(), they should be re-encoded to their bit patterns. They + // are, respectively: + // * math.Float64bits(math.NaN())+1 + // * math.Float32bits(float32(math.NaN()))+1 + in: `go test fuzz v1 +float32(-0) +float64(-0) +float32(+Inf) +float32(-Inf) +float32(NaN) +float64(+Inf) +float64(-Inf) +float64(NaN) +math.Float64frombits(0x7ff8000000000002) +math.Float32frombits(0x7fc00001)`, + }, + { + desc: "int variations", + // Although we arbitrarily choose default integer bases (0 or 16), we may + // want to change those arbitrary choices in the future and should not + // break the parser. Verify that integers in the opposite bases still + // parse correctly. + in: `go test fuzz v1 +int(0x0) +int32(0x41) +int64(0xfffffffff) +uint32(0xcafef00d) +uint64(0xffffffffffffffff) +uint8(0b0000000) +byte(0x0) +byte('\000') +byte('\u0000') +byte('\'') +math.Float64frombits(9221120237041090562) +math.Float32frombits(2143289345)`, + want: `go test fuzz v1 +int(0) +rune('A') +int64(68719476735) +uint32(3405705229) +uint64(18446744073709551615) +byte('\x00') +byte('\x00') +byte('\x00') +byte('\x00') +byte('\'') +math.Float64frombits(0x7ff8000000000002) +math.Float32frombits(0x7fc00001)`, + }, + { + desc: "rune validation", + in: `go test fuzz v1 +rune(0) +rune(0x41) +rune(-1) +rune(0xfffd) +rune(0xd800) +rune(0x10ffff) +rune(0x110000) +`, + want: `go test fuzz v1 +rune('\x00') +rune('A') +int32(-1) +rune('�') +int32(55296) +rune('\U0010ffff') +int32(1114112)`, + }, + { + desc: "int overflow", + in: `go test fuzz v1 +int(0x7fffffffffffffff) +uint(0xffffffffffffffff)`, + want: func() string { + switch strconv.IntSize { + case 32: + return `go test fuzz v1 +int(-1) +uint(4294967295)` + case 64: + return `go test fuzz v1 +int(9223372036854775807) +uint(18446744073709551615)` + default: + panic("unreachable") + } + }(), }, } for _, test := range tests { - t.Run(test.in, func(t *testing.T) { + t.Run(test.desc, func(t *testing.T) { vals, err := unmarshalCorpusFile([]byte(test.in)) - if test.ok && err != nil { - t.Fatalf("unmarshal unexpected error: %v", err) - } else if !test.ok && err == nil { - t.Fatalf("unmarshal unexpected success") + if test.reject { + if err == nil { + t.Fatalf("unmarshal unexpected success") + } + return } - if !test.ok { - return // skip the rest of the test + if err != nil { + t.Fatalf("unmarshal unexpected error: %v", err) } newB := marshalCorpusFile(vals...) if err != nil { @@ -122,9 +234,15 @@ float32(2.5)`, if newB[len(newB)-1] != '\n' { t.Error("didn't write final newline to corpus file") } - before, after := strings.TrimSpace(test.in), strings.TrimSpace(string(newB)) - if before != after { - t.Errorf("values changed after unmarshal then marshal\nbefore: %q\nafter: %q", before, after) + + want := test.want + if want == "" { + want = test.in + } + want += "\n" + got := string(newB) + if got != want { + t.Errorf("unexpected marshaled value\ngot:\n%s\nwant:\n%s", got, want) } }) } @@ -170,3 +288,117 @@ func BenchmarkUnmarshalCorpusFile(b *testing.B) { }) } } + +func TestByteRoundTrip(t *testing.T) { + for x := 0; x < 256; x++ { + b1 := byte(x) + buf := marshalCorpusFile(b1) + vs, err := unmarshalCorpusFile(buf) + if err != nil { + t.Fatal(err) + } + b2 := vs[0].(byte) + if b2 != b1 { + t.Fatalf("unmarshaled %v, want %v:\n%s", b2, b1, buf) + } + } +} + +func TestInt8RoundTrip(t *testing.T) { + for x := -128; x < 128; x++ { + i1 := int8(x) + buf := marshalCorpusFile(i1) + vs, err := unmarshalCorpusFile(buf) + if err != nil { + t.Fatal(err) + } + i2 := vs[0].(int8) + if i2 != i1 { + t.Fatalf("unmarshaled %v, want %v:\n%s", i2, i1, buf) + } + } +} + +func FuzzFloat64RoundTrip(f *testing.F) { + f.Add(math.Float64bits(0)) + f.Add(math.Float64bits(math.Copysign(0, -1))) + f.Add(math.Float64bits(math.MaxFloat64)) + f.Add(math.Float64bits(math.SmallestNonzeroFloat64)) + f.Add(math.Float64bits(math.NaN())) + f.Add(uint64(0x7FF0000000000001)) // signaling NaN + f.Add(math.Float64bits(math.Inf(1))) + f.Add(math.Float64bits(math.Inf(-1))) + + f.Fuzz(func(t *testing.T, u1 uint64) { + x1 := math.Float64frombits(u1) + + b := marshalCorpusFile(x1) + t.Logf("marshaled math.Float64frombits(0x%x):\n%s", u1, b) + + xs, err := unmarshalCorpusFile(b) + if err != nil { + t.Fatal(err) + } + if len(xs) != 1 { + t.Fatalf("unmarshaled %d values", len(xs)) + } + x2 := xs[0].(float64) + u2 := math.Float64bits(x2) + if u2 != u1 { + t.Errorf("unmarshaled %v (bits 0x%x)", x2, u2) + } + }) +} + +func FuzzRuneRoundTrip(f *testing.F) { + f.Add(rune(-1)) + f.Add(rune(0xd800)) + f.Add(rune(0xdfff)) + f.Add(rune(unicode.ReplacementChar)) + f.Add(rune(unicode.MaxASCII)) + f.Add(rune(unicode.MaxLatin1)) + f.Add(rune(unicode.MaxRune)) + f.Add(rune(unicode.MaxRune + 1)) + f.Add(rune(-0x80000000)) + f.Add(rune(0x7fffffff)) + + f.Fuzz(func(t *testing.T, r1 rune) { + b := marshalCorpusFile(r1) + t.Logf("marshaled rune(0x%x):\n%s", r1, b) + + rs, err := unmarshalCorpusFile(b) + if err != nil { + t.Fatal(err) + } + if len(rs) != 1 { + t.Fatalf("unmarshaled %d values", len(rs)) + } + r2 := rs[0].(rune) + if r2 != r1 { + t.Errorf("unmarshaled rune(0x%x)", r2) + } + }) +} + +func FuzzStringRoundTrip(f *testing.F) { + f.Add("") + f.Add("\x00") + f.Add(string([]rune{unicode.ReplacementChar})) + + f.Fuzz(func(t *testing.T, s1 string) { + b := marshalCorpusFile(s1) + t.Logf("marshaled %q:\n%s", s1, b) + + rs, err := unmarshalCorpusFile(b) + if err != nil { + t.Fatal(err) + } + if len(rs) != 1 { + t.Fatalf("unmarshaled %d values", len(rs)) + } + s2 := rs[0].(string) + if s2 != s1 { + t.Errorf("unmarshaled %q", s2) + } + }) +} diff --git a/src/internal/fuzz/worker.go b/src/internal/fuzz/worker.go index e984ba73b2..6e4c4e2d0f 100644 --- a/src/internal/fuzz/worker.go +++ b/src/internal/fuzz/worker.go @@ -800,6 +800,7 @@ func (ws *workerServer) minimize(ctx context.Context, args minimizeArgs) (resp m if err != nil { panic(err) } + inpHash := sha256.Sum256(mem.valueCopy()) if args.Timeout != 0 { var cancel func() ctx, cancel = context.WithTimeout(ctx, args.Timeout) @@ -811,12 +812,22 @@ func (ws *workerServer) minimize(ctx context.Context, args minimizeArgs) (resp m success, err := ws.minimizeInput(ctx, vals, mem, args) if success { writeToMem(vals, mem) + outHash := sha256.Sum256(mem.valueCopy()) mem.header().rawInMem = false resp.WroteToMem = true if err != nil { resp.Err = err.Error() } else { - resp.CoverageData = coverageSnapshot + // If the values didn't change during minimization then coverageSnapshot is likely + // a dirty snapshot which represents the very last step of minimization, not the + // coverage for the initial input. In that case just return the coverage we were + // given initially, since it more accurately represents the coverage map for the + // input we are returning. + if outHash != inpHash { + resp.CoverageData = coverageSnapshot + } else { + resp.CoverageData = args.KeepCoverage + } } } return resp @@ -883,7 +894,8 @@ func (ws *workerServer) minimizeInput(ctx context.Context, vals []any, mem *shar } return true } - if keepCoverage != nil && hasCoverageBit(keepCoverage, coverageSnapshot) { + // Minimization should preserve coverage bits. + if keepCoverage != nil && isCoverageSubset(keepCoverage, coverageSnapshot) { return true } vals[args.Index] = prev diff --git a/src/net/dnsclient_unix.go b/src/net/dnsclient_unix.go index 21aa91f665..9a4a6ee68c 100644 --- a/src/net/dnsclient_unix.go +++ b/src/net/dnsclient_unix.go @@ -30,6 +30,10 @@ const ( // to be used as a useTCP parameter to exchange useTCPOnly = true useUDPOrTCP = false + + // Maximum DNS packet size. + // Value taken from https://dnsflagday.net/2020/. + maxDNSPacketSize = 1232 ) var ( @@ -82,7 +86,7 @@ func dnsPacketRoundTrip(c Conn, id uint16, query dnsmessage.Question, b []byte) return dnsmessage.Parser{}, dnsmessage.Header{}, err } - b = make([]byte, 512) // see RFC 1035 + b = make([]byte, maxDNSPacketSize) for { n, err := c.Read(b) if err != nil { diff --git a/src/net/dnsclient_unix_test.go b/src/net/dnsclient_unix_test.go index 14366eca8c..e46decab16 100644 --- a/src/net/dnsclient_unix_test.go +++ b/src/net/dnsclient_unix_test.go @@ -881,7 +881,7 @@ func (f *fakeDNSPacketConn) Close() error { func TestIgnoreDNSForgeries(t *testing.T) { c, s := Pipe() go func() { - b := make([]byte, 512) + b := make([]byte, maxDNSPacketSize) n, err := s.Read(b) if err != nil { t.Error(err) diff --git a/src/net/http/transport.go b/src/net/http/transport.go index 5fe3e6ebb4..e41b20a15b 100644 --- a/src/net/http/transport.go +++ b/src/net/http/transport.go @@ -606,6 +606,9 @@ func (t *Transport) roundTrip(req *Request) (*Response, error) { } else if !pconn.shouldRetryRequest(req, err) { // Issue 16465: return underlying net.Conn.Read error from peek, // as we've historically done. + if e, ok := err.(nothingWrittenError); ok { + err = e.error + } if e, ok := err.(transportReadFromServerError); ok { err = e.err } @@ -2032,6 +2035,9 @@ func (pc *persistConn) mapRoundTripError(req *transportRequest, startBytesWritte } if _, ok := err.(transportReadFromServerError); ok { + if pc.nwrite == startBytesWritten { + return nothingWrittenError{err} + } // Don't decorate return err } diff --git a/src/net/http/transport_internal_test.go b/src/net/http/transport_internal_test.go index 1cce27235d..2ed637e9f0 100644 --- a/src/net/http/transport_internal_test.go +++ b/src/net/http/transport_internal_test.go @@ -52,8 +52,8 @@ func TestTransportPersistConnReadLoopEOF(t *testing.T) { conn.Close() // simulate the server hanging up on the client _, err = pc.roundTrip(treq) - if !isTransportReadFromServerError(err) && err != errServerClosedIdle { - t.Errorf("roundTrip = %#v, %v; want errServerClosedIdle or transportReadFromServerError", err, err) + if !isNothingWrittenError(err) && !isTransportReadFromServerError(err) && err != errServerClosedIdle { + t.Errorf("roundTrip = %#v, %v; want errServerClosedIdle, transportReadFromServerError, or nothingWrittenError", err, err) } <-pc.closech @@ -63,6 +63,11 @@ func TestTransportPersistConnReadLoopEOF(t *testing.T) { } } +func isNothingWrittenError(err error) bool { + _, ok := err.(nothingWrittenError) + return ok +} + func isTransportReadFromServerError(err error) bool { _, ok := err.(transportReadFromServerError) return ok diff --git a/src/net/lookup_test.go b/src/net/lookup_test.go index 063d650c60..3a31f56bea 100644 --- a/src/net/lookup_test.go +++ b/src/net/lookup_test.go @@ -883,21 +883,66 @@ func TestLookupNonLDH(t *testing.T) { func TestLookupContextCancel(t *testing.T) { mustHaveExternalNetwork(t) - defer dnsWaitGroup.Wait() + testenv.SkipFlakyNet(t) - ctx, ctxCancel := context.WithCancel(context.Background()) - ctxCancel() - _, err := DefaultResolver.LookupIPAddr(ctx, "google.com") - if err.(*DNSError).Err != errCanceled.Error() { - testenv.SkipFlakyNet(t) - t.Fatal(err) + origTestHookLookupIP := testHookLookupIP + defer func() { + dnsWaitGroup.Wait() + testHookLookupIP = origTestHookLookupIP + }() + + lookupCtx, cancelLookup := context.WithCancel(context.Background()) + unblockLookup := make(chan struct{}) + + // Set testHookLookupIP to start a new, concurrent call to LookupIPAddr + // and cancel the original one, then block until the canceled call has returned + // (ensuring that it has performed any synchronous cleanup). + testHookLookupIP = func( + ctx context.Context, + fn func(context.Context, string, string) ([]IPAddr, error), + network string, + host string, + ) ([]IPAddr, error) { + select { + case <-unblockLookup: + default: + // Start a concurrent LookupIPAddr for the same host while the caller is + // still blocked, and sleep a little to give it time to be deduplicated + // before we cancel (and unblock) the caller. + // (If the timing doesn't quite work out, we'll end up testing sequential + // calls instead of concurrent ones, but the test should still pass.) + t.Logf("starting concurrent LookupIPAddr") + dnsWaitGroup.Add(1) + go func() { + defer dnsWaitGroup.Done() + _, err := DefaultResolver.LookupIPAddr(context.Background(), host) + if err != nil { + t.Error(err) + } + }() + time.Sleep(1 * time.Millisecond) + } + + cancelLookup() + <-unblockLookup + // If the concurrent lookup above is deduplicated to this one + // (as we expect to happen most of the time), it is important + // that the original call does not cancel the shared Context. + // (See https://go.dev/issue/22724.) Explicitly check for + // cancellation now, just in case fn itself doesn't notice it. + if err := ctx.Err(); err != nil { + t.Logf("testHookLookupIP canceled") + return nil, err + } + t.Logf("testHookLookupIP performing lookup") + return fn(ctx, network, host) } - ctx = context.Background() - _, err = DefaultResolver.LookupIPAddr(ctx, "google.com") - if err != nil { - testenv.SkipFlakyNet(t) - t.Fatal(err) + + _, err := DefaultResolver.LookupIPAddr(lookupCtx, "google.com") + if dnsErr, ok := err.(*DNSError); !ok || dnsErr.Err != errCanceled.Error() { + t.Errorf("unexpected error from canceled, blocked LookupIPAddr: %v", err) } + close(unblockLookup) } // Issue 24330: treat the nil *Resolver like a zero value. Verify nothing diff --git a/src/net/net.go b/src/net/net.go index 77e54a9125..d91e743a01 100644 --- a/src/net/net.go +++ b/src/net/net.go @@ -703,6 +703,12 @@ var ( _ io.Reader = (*Buffers)(nil) ) +// WriteTo writes contents of the buffers to w. +// +// WriteTo implements io.WriterTo for Buffers. +// +// WriteTo modifies the slice v as well as v[i] for 0 <= i < len(v), +// but does not modify v[i][j] for any i, j. func (v *Buffers) WriteTo(w io.Writer) (n int64, err error) { if wv, ok := w.(buffersWriter); ok { return wv.writeBuffers(v) @@ -719,6 +725,12 @@ func (v *Buffers) WriteTo(w io.Writer) (n int64, err error) { return n, nil } +// Read from the buffers. +// +// Read implements io.Reader for Buffers. +// +// Read modifies the slice v as well as v[i] for 0 <= i < len(v), +// but does not modify v[i][j] for any i, j. func (v *Buffers) Read(p []byte) (n int, err error) { for len(p) > 0 && len(*v) > 0 { n0 := copy(p, (*v)[0]) diff --git a/src/net/net_test.go b/src/net/net_test.go index 7b169916f1..76a9c8b151 100644 --- a/src/net/net_test.go +++ b/src/net/net_test.go @@ -9,7 +9,6 @@ package net import ( "errors" "fmt" - "internal/testenv" "io" "net/internal/socktest" "os" @@ -515,35 +514,50 @@ func TestCloseUnblocksRead(t *testing.T) { // Issue 24808: verify that ECONNRESET is not temporary for read. func TestNotTemporaryRead(t *testing.T) { - if runtime.GOOS == "freebsd" { - testenv.SkipFlaky(t, 25289) - } - if runtime.GOOS == "aix" { - testenv.SkipFlaky(t, 29685) - } t.Parallel() - server := func(cs *TCPConn) error { - cs.SetLinger(0) - // Give the client time to get stuck in a Read. - time.Sleep(50 * time.Millisecond) - cs.Close() - return nil - } - client := func(ss *TCPConn) error { - _, err := ss.Read([]byte{0}) - if err == nil { - return errors.New("Read succeeded unexpectedly") - } else if err == io.EOF { - // This happens on Plan 9. - return nil - } else if ne, ok := err.(Error); !ok { - return fmt.Errorf("unexpected error %v", err) - } else if ne.Temporary() { - return fmt.Errorf("unexpected temporary error %v", err) + + ln := newLocalListener(t, "tcp") + serverDone := make(chan struct{}) + dialed := make(chan struct{}) + go func() { + defer close(serverDone) + + cs, err := ln.Accept() + if err != nil { + return } - return nil + <-dialed + cs.(*TCPConn).SetLinger(0) + cs.Close() + + ln.Close() + }() + defer func() { <-serverDone }() + + ss, err := Dial("tcp", ln.Addr().String()) + if err != nil { + t.Fatal(err) + } + defer ss.Close() + close(dialed) + _, err = ss.Read([]byte{0}) + if err == nil { + t.Fatal("Read succeeded unexpectedly") + } else if err == io.EOF { + // This happens on Plan 9, but for some reason (prior to CL 385314) it was + // accepted everywhere else too. + if runtime.GOOS == "plan9" { + return + } + // TODO: during an open development cycle, try making this a failure + // and see whether it causes the test to become flaky anywhere else. + return + } + if ne, ok := err.(Error); !ok { + t.Errorf("Read error does not implement net.Error: %v", err) + } else if ne.Temporary() { + t.Errorf("Read error is unexpectedly temporary: %v", err) } - withTCPConnPair(t, client, server) } // The various errors should implement the Error interface. diff --git a/src/net/smtp/auth.go b/src/net/smtp/auth.go index fd1a472f93..7a32ef6a2e 100644 --- a/src/net/smtp/auth.go +++ b/src/net/smtp/auth.go @@ -16,8 +16,7 @@ type Auth interface { // Start begins an authentication with a server. // It returns the name of the authentication protocol // and optionally data to include in the initial AUTH message - // sent to the server. It can return proto == "" to indicate - // that the authentication should be skipped. + // sent to the server. // If it returns a non-nil error, the SMTP client aborts // the authentication attempt and closes the connection. Start(server *ServerInfo) (proto string, toServer []byte, err error) diff --git a/src/os/example_test.go b/src/os/example_test.go index e8554b0b12..53e3c5227b 100644 --- a/src/os/example_test.go +++ b/src/os/example_test.go @@ -241,3 +241,25 @@ func ExampleWriteFile() { log.Fatal(err) } } + +func ExampleMkdir() { + err := os.Mkdir("testdir", 0750) + if err != nil && !os.IsExist(err) { + log.Fatal(err) + } + err = os.WriteFile("testdir/testfile.txt", []byte("Hello, Gophers!"), 0660) + if err != nil { + log.Fatal(err) + } +} + +func ExampleMkdirAll() { + err := os.MkdirAll("test/subdir", 0750) + if err != nil && !os.IsExist(err) { + log.Fatal(err) + } + err = os.WriteFile("test/subdir/testfile.txt", []byte("Hello, Gophers!"), 0660) + if err != nil { + log.Fatal(err) + } +} diff --git a/src/os/os_test.go b/src/os/os_test.go index 82ca6f987d..63427deb6e 100644 --- a/src/os/os_test.go +++ b/src/os/os_test.go @@ -28,6 +28,16 @@ import ( "time" ) +func TestMain(m *testing.M) { + if Getenv("GO_OS_TEST_DRAIN_STDIN") == "1" { + os.Stdout.Close() + io.Copy(io.Discard, os.Stdin) + Exit(0) + } + + Exit(m.Run()) +} + var dot = []string{ "dir_unix.go", "env.go", @@ -2259,9 +2269,18 @@ func testKillProcess(t *testing.T, processKiller func(p *Process)) { testenv.MustHaveExec(t) t.Parallel() - // Re-exec the test binary itself to emulate "sleep 1". - cmd := osexec.Command(Args[0], "-test.run", "TestSleep") - err := cmd.Start() + // Re-exec the test binary to start a process that hangs until stdin is closed. + cmd := osexec.Command(Args[0]) + cmd.Env = append(os.Environ(), "GO_OS_TEST_DRAIN_STDIN=1") + stdout, err := cmd.StdoutPipe() + if err != nil { + t.Fatal(err) + } + stdin, err := cmd.StdinPipe() + if err != nil { + t.Fatal(err) + } + err = cmd.Start() if err != nil { t.Fatalf("Failed to start test process: %v", err) } @@ -2270,19 +2289,14 @@ func testKillProcess(t *testing.T, processKiller func(p *Process)) { if err := cmd.Wait(); err == nil { t.Errorf("Test process succeeded, but expected to fail") } + stdin.Close() // Keep stdin alive until the process has finished dying. }() - time.Sleep(100 * time.Millisecond) - processKiller(cmd.Process) -} + // Wait for the process to be started. + // (It will close its stdout when it reaches TestMain.) + io.Copy(io.Discard, stdout) -// TestSleep emulates "sleep 1". It is a helper for testKillProcess, so we -// don't have to rely on an external "sleep" command being available. -func TestSleep(t *testing.T) { - if testing.Short() { - t.Skip("Skipping in short mode") - } - time.Sleep(time.Second) + processKiller(cmd.Process) } func TestKillStartProcess(t *testing.T) { diff --git a/src/reflect/asm_arm64.s b/src/reflect/asm_arm64.s index 5b9b3573fa..812b8a02c3 100644 --- a/src/reflect/asm_arm64.s +++ b/src/reflect/asm_arm64.s @@ -33,9 +33,14 @@ TEXT ·makeFuncStub(SB),(NOSPLIT|WRAPPER),$432 ADD $LOCAL_REGARGS, RSP, R20 CALL runtime·spillArgs(SB) MOVD R26, 32(RSP) // outside of moveMakeFuncArgPtrs's arg area +#ifdef GOEXPERIMENT_regabiargs + MOVD R26, R0 + MOVD R20, R1 +#else MOVD R26, 8(RSP) MOVD R20, 16(RSP) - CALL ·moveMakeFuncArgPtrs(SB) +#endif + CALL ·moveMakeFuncArgPtrs(SB) MOVD 32(RSP), R26 MOVD R26, 8(RSP) MOVD $argframe+0(FP), R3 @@ -61,9 +66,14 @@ TEXT ·methodValueCall(SB),(NOSPLIT|WRAPPER),$432 ADD $LOCAL_REGARGS, RSP, R20 CALL runtime·spillArgs(SB) MOVD R26, 32(RSP) // outside of moveMakeFuncArgPtrs's arg area +#ifdef GOEXPERIMENT_regabiargs + MOVD R26, R0 + MOVD R20, R1 +#else MOVD R26, 8(RSP) MOVD R20, 16(RSP) - CALL ·moveMakeFuncArgPtrs(SB) +#endif + CALL ·moveMakeFuncArgPtrs(SB) MOVD 32(RSP), R26 MOVD R26, 8(RSP) MOVD $argframe+0(FP), R3 diff --git a/src/regexp/syntax/parse.go b/src/regexp/syntax/parse.go index 06a92fb3d7..0f6587ab27 100644 --- a/src/regexp/syntax/parse.go +++ b/src/regexp/syntax/parse.go @@ -76,13 +76,29 @@ const ( opVerticalBar ) +// maxHeight is the maximum height of a regexp parse tree. +// It is somewhat arbitrarily chosen, but the idea is to be large enough +// that no one will actually hit in real use but at the same time small enough +// that recursion on the Regexp tree will not hit the 1GB Go stack limit. +// The maximum amount of stack for a single recursive frame is probably +// closer to 1kB, so this could potentially be raised, but it seems unlikely +// that people have regexps nested even this deeply. +// We ran a test on Google's C++ code base and turned up only +// a single use case with depth > 100; it had depth 128. +// Using depth 1000 should be plenty of margin. +// As an optimization, we don't even bother calculating heights +// until we've allocated at least maxHeight Regexp structures. +const maxHeight = 1000 + type parser struct { flags Flags // parse mode flags stack []*Regexp // stack of parsed expressions free *Regexp numCap int // number of capturing groups seen wholeRegexp string - tmpClass []rune // temporary char class work space + tmpClass []rune // temporary char class work space + numRegexp int // number of regexps allocated + height map[*Regexp]int // regexp height for height limit check } func (p *parser) newRegexp(op Op) *Regexp { @@ -92,16 +108,52 @@ func (p *parser) newRegexp(op Op) *Regexp { *re = Regexp{} } else { re = new(Regexp) + p.numRegexp++ } re.Op = op return re } func (p *parser) reuse(re *Regexp) { + if p.height != nil { + delete(p.height, re) + } re.Sub0[0] = p.free p.free = re } +func (p *parser) checkHeight(re *Regexp) { + if p.numRegexp < maxHeight { + return + } + if p.height == nil { + p.height = make(map[*Regexp]int) + for _, re := range p.stack { + p.checkHeight(re) + } + } + if p.calcHeight(re, true) > maxHeight { + panic(ErrInternalError) + } +} + +func (p *parser) calcHeight(re *Regexp, force bool) int { + if !force { + if h, ok := p.height[re]; ok { + return h + } + } + h := 1 + for _, sub := range re.Sub { + hsub := p.calcHeight(sub, false) + if h < 1+hsub { + h = 1 + hsub + } + } + p.height[re] = h + return h +} + // Parse stack manipulation. // push pushes the regexp re onto the parse stack and returns the regexp. @@ -137,6 +189,7 @@ func (p *parser) push(re *Regexp) *Regexp { } p.stack = append(p.stack, re) + p.checkHeight(re) return re } @@ -246,6 +299,7 @@ func (p *parser) repeat(op Op, min, max int, before, after, lastRepeat string) ( re.Sub = re.Sub0[:1] re.Sub[0] = sub p.stack[n-1] = re + p.checkHeight(re) if op == OpRepeat && (min >= 2 || max >= 2) && !repeatIsValid(re, 1000) { return "", &Error{ErrInvalidRepeatSize, before[:len(before)-len(after)]} @@ -693,6 +747,21 @@ func literalRegexp(s string, flags Flags) *Regexp { // Flags, and returns a regular expression parse tree. The syntax is // described in the top-level comment. func Parse(s string, flags Flags) (*Regexp, error) { + return parse(s, flags) +} + +func parse(s string, flags Flags) (_ *Regexp, err error) { + defer func() { + switch r := recover(); r { + default: + panic(r) + case nil: + // ok + case ErrInternalError: + err = &Error{Code: ErrInternalError, Expr: s} + } + }() + if flags&Literal != 0 { // Trivial parser for literal string. if err := checkUTF8(s); err != nil { @@ -704,7 +773,6 @@ func Parse(s string, flags Flags) (*Regexp, error) { // Otherwise, must do real work. var ( p parser - err error c rune op Op lastRepeat string diff --git a/src/regexp/syntax/parse_test.go b/src/regexp/syntax/parse_test.go index 5581ba1ca5..1ef6d8a3fe 100644 --- a/src/regexp/syntax/parse_test.go +++ b/src/regexp/syntax/parse_test.go @@ -207,6 +207,11 @@ var parseTests = []parseTest{ // Valid repetitions. {`((((((((((x{2}){2}){2}){2}){2}){2}){2}){2}){2}))`, ``}, {`((((((((((x{1}){2}){2}){2}){2}){2}){2}){2}){2}){2})`, ``}, + + // Valid nesting. + {strings.Repeat("(", 999) + strings.Repeat(")", 999), ``}, + {strings.Repeat("(?:", 999) + strings.Repeat(")*", 999), ``}, + {"(" + strings.Repeat("|", 12345) + ")", ``}, // not nested at all } const testFlags = MatchNL | PerlX | UnicodeGroups @@ -482,6 +487,8 @@ var invalidRegexps = []string{ `a{100000}`, `a{100000,}`, "((((((((((x{2}){2}){2}){2}){2}){2}){2}){2}){2}){2})", + strings.Repeat("(", 1000) + strings.Repeat(")", 1000), + strings.Repeat("(?:", 1000) + strings.Repeat(")*", 1000), `\Q\E*`, } diff --git a/src/runtime/asm_386.s b/src/runtime/asm_386.s index 594cd5ed0d..e16880c950 100644 --- a/src/runtime/asm_386.s +++ b/src/runtime/asm_386.s @@ -937,8 +937,9 @@ aes0to15: PAND masks<>(SB)(BX*8), X1 final1: - AESENC X0, X1 // scramble input, xor in seed - AESENC X1, X1 // scramble combo 2 times + PXOR X0, X1 // xor data with seed + AESENC X1, X1 // scramble combo 3 times + AESENC X1, X1 AESENC X1, X1 MOVL X1, (DX) RET @@ -971,9 +972,13 @@ aes17to32: MOVOU (AX), X2 MOVOU -16(AX)(BX*1), X3 + // xor with seed + PXOR X0, X2 + PXOR X1, X3 + // scramble 3 times - AESENC X0, X2 - AESENC X1, X3 + AESENC X2, X2 + AESENC X3, X3 AESENC X2, X2 AESENC X3, X3 AESENC X2, X2 @@ -1000,10 +1005,15 @@ aes33to64: MOVOU -32(AX)(BX*1), X6 MOVOU -16(AX)(BX*1), X7 - AESENC X0, X4 - AESENC X1, X5 - AESENC X2, X6 - AESENC X3, X7 + PXOR X0, X4 + PXOR X1, X5 + PXOR X2, X6 + PXOR X3, X7 + + AESENC X4, X4 + AESENC X5, X5 + AESENC X6, X6 + AESENC X7, X7 AESENC X4, X4 AESENC X5, X5 @@ -1069,7 +1079,12 @@ aesloop: DECL BX JNE aesloop - // 2 more scrambles to finish + // 3 more scrambles to finish + AESENC X4, X4 + AESENC X5, X5 + AESENC X6, X6 + AESENC X7, X7 + AESENC X4, X4 AESENC X5, X5 AESENC X6, X6 diff --git a/src/runtime/crash_cgo_test.go b/src/runtime/crash_cgo_test.go index 8c250f72d6..c9c9406a15 100644 --- a/src/runtime/crash_cgo_test.go +++ b/src/runtime/crash_cgo_test.go @@ -626,13 +626,11 @@ func TestSegv(t *testing.T) { // a VDSO call via asmcgocall. testenv.SkipFlaky(t, 50504) } - if testenv.Builder() == "linux-mips64le-mengzhuo" && strings.Contains(got, "runtime: unknown pc") { - // Runtime sometimes throw "unknown pc" when generating the traceback. - // Curiously, that doesn't seem to happen on the linux-mips64le-rtrk - // builder. - testenv.SkipFlaky(t, 50605) - } } + if test == "SegvInCgo" && strings.Contains(got, "runtime: unknown pc") { + testenv.SkipFlaky(t, 50979) + } + nowant := "runtime: " if strings.Contains(got, nowant) { t.Errorf("unexpectedly saw %q in output", nowant) diff --git a/src/runtime/crash_unix_test.go b/src/runtime/crash_unix_test.go index 1eb10f9b60..a218205af4 100644 --- a/src/runtime/crash_unix_test.go +++ b/src/runtime/crash_unix_test.go @@ -132,7 +132,7 @@ func TestCrashDumpsAllThreads(t *testing.T) { out := outbuf.Bytes() n := bytes.Count(out, []byte("main.crashDumpsAllThreadsLoop(")) if n != 4 { - t.Errorf("found %d instances of main.loop; expected 4", n) + t.Errorf("found %d instances of main.crashDumpsAllThreadsLoop; expected 4", n) t.Logf("%s", out) } } diff --git a/src/runtime/debug/mod.go b/src/runtime/debug/mod.go index 14a496a8eb..688e2581ed 100644 --- a/src/runtime/debug/mod.go +++ b/src/runtime/debug/mod.go @@ -5,9 +5,9 @@ package debug import ( - "bytes" "fmt" "runtime" + "strconv" "strings" ) @@ -23,8 +23,8 @@ func ReadBuildInfo() (info *BuildInfo, ok bool) { return nil, false } data = data[16 : len(data)-16] - bi := &BuildInfo{} - if err := bi.UnmarshalText([]byte(data)); err != nil { + bi, err := ParseBuildInfo(data) + if err != nil { return nil, false } @@ -63,8 +63,18 @@ type BuildSetting struct { Key, Value string } -func (bi *BuildInfo) MarshalText() ([]byte, error) { - buf := &bytes.Buffer{} +// quoteKey reports whether key is required to be quoted. +func quoteKey(key string) bool { + return len(key) == 0 || strings.ContainsAny(key, "= \t\r\n\"`") +} + +// quoteValue reports whether value is required to be quoted. +func quoteValue(value string) bool { + return strings.ContainsAny(value, " \t\r\n\"`") +} + +func (bi *BuildInfo) String() string { + buf := new(strings.Builder) if bi.GoVersion != "" { fmt.Fprintf(buf, "go\t%s\n", bi.GoVersion) } @@ -76,12 +86,8 @@ func (bi *BuildInfo) MarshalText() ([]byte, error) { buf.WriteString(word) buf.WriteByte('\t') buf.WriteString(m.Path) - mv := m.Version - if mv == "" { - mv = "(devel)" - } buf.WriteByte('\t') - buf.WriteString(mv) + buf.WriteString(m.Version) if m.Replace == nil { buf.WriteByte('\t') buf.WriteString(m.Sum) @@ -91,27 +97,28 @@ func (bi *BuildInfo) MarshalText() ([]byte, error) { } buf.WriteByte('\n') } - if bi.Main.Path != "" { + if bi.Main != (Module{}) { formatMod("mod", bi.Main) } for _, dep := range bi.Deps { formatMod("dep", *dep) } for _, s := range bi.Settings { - if strings.ContainsAny(s.Key, "= \t\n") { - return nil, fmt.Errorf("invalid build setting key %q", s.Key) + key := s.Key + if quoteKey(key) { + key = strconv.Quote(key) } - if strings.Contains(s.Value, "\n") { - return nil, fmt.Errorf("invalid build setting value for key %q: contains newline", s.Value) + value := s.Value + if quoteValue(value) { + value = strconv.Quote(value) } - fmt.Fprintf(buf, "build\t%s=%s\n", s.Key, s.Value) + fmt.Fprintf(buf, "build\t%s=%s\n", key, value) } - return buf.Bytes(), nil + return buf.String() } -func (bi *BuildInfo) UnmarshalText(data []byte) (err error) { - *bi = BuildInfo{} +func ParseBuildInfo(data string) (bi *BuildInfo, err error) { lineNum := 1 defer func() { if err != nil { @@ -120,67 +127,69 @@ func (bi *BuildInfo) UnmarshalText(data []byte) (err error) { }() var ( - pathLine = []byte("path\t") - modLine = []byte("mod\t") - depLine = []byte("dep\t") - repLine = []byte("=>\t") - buildLine = []byte("build\t") - newline = []byte("\n") - tab = []byte("\t") + pathLine = "path\t" + modLine = "mod\t" + depLine = "dep\t" + repLine = "=>\t" + buildLine = "build\t" + newline = "\n" + tab = "\t" ) - readModuleLine := func(elem [][]byte) (Module, error) { + readModuleLine := func(elem []string) (Module, error) { if len(elem) != 2 && len(elem) != 3 { return Module{}, fmt.Errorf("expected 2 or 3 columns; got %d", len(elem)) } + version := elem[1] sum := "" if len(elem) == 3 { - sum = string(elem[2]) + sum = elem[2] } return Module{ - Path: string(elem[0]), - Version: string(elem[1]), + Path: elem[0], + Version: version, Sum: sum, }, nil } + bi = new(BuildInfo) var ( last *Module - line []byte + line string ok bool ) // Reverse of BuildInfo.String(), except for go version. for len(data) > 0 { - line, data, ok = bytes.Cut(data, newline) + line, data, ok = strings.Cut(data, newline) if !ok { break } switch { - case bytes.HasPrefix(line, pathLine): + case strings.HasPrefix(line, pathLine): elem := line[len(pathLine):] bi.Path = string(elem) - case bytes.HasPrefix(line, modLine): - elem := bytes.Split(line[len(modLine):], tab) + case strings.HasPrefix(line, modLine): + elem := strings.Split(line[len(modLine):], tab) last = &bi.Main *last, err = readModuleLine(elem) if err != nil { - return err + return nil, err } - case bytes.HasPrefix(line, depLine): - elem := bytes.Split(line[len(depLine):], tab) + case strings.HasPrefix(line, depLine): + elem := strings.Split(line[len(depLine):], tab) last = new(Module) bi.Deps = append(bi.Deps, last) *last, err = readModuleLine(elem) if err != nil { - return err + return nil, err } - case bytes.HasPrefix(line, repLine): - elem := bytes.Split(line[len(repLine):], tab) + case strings.HasPrefix(line, repLine): + elem := strings.Split(line[len(repLine):], tab) if len(elem) != 3 { - return fmt.Errorf("expected 3 columns for replacement; got %d", len(elem)) + return nil, fmt.Errorf("expected 3 columns for replacement; got %d", len(elem)) } if last == nil { - return fmt.Errorf("replacement with no module on previous line") + return nil, fmt.Errorf("replacement with no module on previous line") } last.Replace = &Module{ Path: string(elem[0]), @@ -188,17 +197,63 @@ func (bi *BuildInfo) UnmarshalText(data []byte) (err error) { Sum: string(elem[2]), } last = nil - case bytes.HasPrefix(line, buildLine): - key, val, ok := strings.Cut(string(line[len(buildLine):]), "=") - if !ok { - return fmt.Errorf("invalid build line") + case strings.HasPrefix(line, buildLine): + kv := line[len(buildLine):] + if len(kv) < 1 { + return nil, fmt.Errorf("build line missing '='") } - if key == "" { - return fmt.Errorf("empty key") + + var key, rawValue string + switch kv[0] { + case '=': + return nil, fmt.Errorf("build line with missing key") + + case '`', '"': + rawKey, err := strconv.QuotedPrefix(kv) + if err != nil { + return nil, fmt.Errorf("invalid quoted key in build line") + } + if len(kv) == len(rawKey) { + return nil, fmt.Errorf("build line missing '=' after quoted key") + } + if c := kv[len(rawKey)]; c != '=' { + return nil, fmt.Errorf("unexpected character after quoted key: %q", c) + } + key, _ = strconv.Unquote(rawKey) + rawValue = kv[len(rawKey)+1:] + + default: + var ok bool + key, rawValue, ok = strings.Cut(kv, "=") + if !ok { + return nil, fmt.Errorf("build line missing '=' after key") + } + if quoteKey(key) { + return nil, fmt.Errorf("unquoted key %q must be quoted", key) + } } - bi.Settings = append(bi.Settings, BuildSetting{Key: key, Value: val}) + + var value string + if len(rawValue) > 0 { + switch rawValue[0] { + case '`', '"': + var err error + value, err = strconv.Unquote(rawValue) + if err != nil { + return nil, fmt.Errorf("invalid quoted value in build line") + } + + default: + value = rawValue + if quoteValue(value) { + return nil, fmt.Errorf("unquoted value %q must be quoted", value) + } + } + } + + bi.Settings = append(bi.Settings, BuildSetting{Key: key, Value: value}) } lineNum++ } - return nil + return bi, nil } diff --git a/src/runtime/debug/mod_test.go b/src/runtime/debug/mod_test.go new file mode 100644 index 0000000000..b2917692f4 --- /dev/null +++ b/src/runtime/debug/mod_test.go @@ -0,0 +1,75 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package debug_test + +import ( + "reflect" + "runtime/debug" + "strings" + "testing" +) + +// strip removes two leading tabs after each newline of s. +func strip(s string) string { + replaced := strings.ReplaceAll(s, "\n\t\t", "\n") + if len(replaced) > 0 && replaced[0] == '\n' { + replaced = replaced[1:] + } + return replaced +} + +func FuzzParseBuildInfoRoundTrip(f *testing.F) { + // Package built from outside a module, missing some fields.. + f.Add(strip(` + path rsc.io/fortune + mod rsc.io/fortune v1.0.0 + `)) + + // Package built from the standard library, missing some fields.. + f.Add(`path cmd/test2json`) + + // Package built from inside a module. + f.Add(strip(` + go 1.18 + path example.com/m + mod example.com/m (devel) + build -compiler=gc + `)) + + // Package built in GOPATH mode. + f.Add(strip(` + go 1.18 + path example.com/m + build -compiler=gc + `)) + + // Escaped build info. + f.Add(strip(` + go 1.18 + path example.com/m + build CRAZY_ENV="requires\nescaping" + `)) + + f.Fuzz(func(t *testing.T, s string) { + bi, err := debug.ParseBuildInfo(s) + if err != nil { + // Not a round-trippable BuildInfo string. + t.Log(err) + return + } + + // s2 could have different escaping from s. + // However, it should parse to exactly the same contents. + s2 := bi.String() + bi2, err := debug.ParseBuildInfo(s2) + if err != nil { + t.Fatalf("%v:\n%s", err, s2) + } + + if !reflect.DeepEqual(bi2, bi) { + t.Fatalf("Parsed representation differs.\ninput:\n%s\noutput:\n%s", s, s2) + } + }) +} diff --git a/src/runtime/defs_linux.go b/src/runtime/defs_linux.go index fa94e388f4..67fb2115d9 100644 --- a/src/runtime/defs_linux.go +++ b/src/runtime/defs_linux.go @@ -91,6 +91,8 @@ const ( SIGPWR = C.SIGPWR SIGSYS = C.SIGSYS + SIGRTMIN = C.SIGRTMIN + FPE_INTDIV = C.FPE_INTDIV FPE_INTOVF = C.FPE_INTOVF FPE_FLTDIV = C.FPE_FLTDIV diff --git a/src/runtime/defs_linux_386.go b/src/runtime/defs_linux_386.go index 24fb58bbf8..d24d00febb 100644 --- a/src/runtime/defs_linux_386.go +++ b/src/runtime/defs_linux_386.go @@ -64,6 +64,8 @@ const ( _SIGPWR = 0x1e _SIGSYS = 0x1f + _SIGRTMIN = 0x20 + _FPE_INTDIV = 0x1 _FPE_INTOVF = 0x2 _FPE_FLTDIV = 0x3 diff --git a/src/runtime/defs_linux_amd64.go b/src/runtime/defs_linux_amd64.go index 36da22f8ce..47fb468621 100644 --- a/src/runtime/defs_linux_amd64.go +++ b/src/runtime/defs_linux_amd64.go @@ -64,6 +64,8 @@ const ( _SIGPWR = 0x1e _SIGSYS = 0x1f + _SIGRTMIN = 0x20 + _FPE_INTDIV = 0x1 _FPE_INTOVF = 0x2 _FPE_FLTDIV = 0x3 diff --git a/src/runtime/defs_linux_arm.go b/src/runtime/defs_linux_arm.go index 13d06969e3..ed387e6eff 100644 --- a/src/runtime/defs_linux_arm.go +++ b/src/runtime/defs_linux_arm.go @@ -63,6 +63,7 @@ const ( _SIGIO = 0x1d _SIGPWR = 0x1e _SIGSYS = 0x1f + _SIGRTMIN = 0x20 _FPE_INTDIV = 0x1 _FPE_INTOVF = 0x2 _FPE_FLTDIV = 0x3 diff --git a/src/runtime/defs_linux_arm64.go b/src/runtime/defs_linux_arm64.go index f9ee9cbc35..97b3a9600f 100644 --- a/src/runtime/defs_linux_arm64.go +++ b/src/runtime/defs_linux_arm64.go @@ -64,6 +64,8 @@ const ( _SIGPWR = 0x1e _SIGSYS = 0x1f + _SIGRTMIN = 0x20 + _FPE_INTDIV = 0x1 _FPE_INTOVF = 0x2 _FPE_FLTDIV = 0x3 diff --git a/src/runtime/defs_linux_mips64x.go b/src/runtime/defs_linux_mips64x.go index 2601082ee1..67f28ddc2b 100644 --- a/src/runtime/defs_linux_mips64x.go +++ b/src/runtime/defs_linux_mips64x.go @@ -66,6 +66,8 @@ const ( _SIGXCPU = 0x1e _SIGXFSZ = 0x1f + _SIGRTMIN = 0x20 + _FPE_INTDIV = 0x1 _FPE_INTOVF = 0x2 _FPE_FLTDIV = 0x3 diff --git a/src/runtime/defs_linux_mipsx.go b/src/runtime/defs_linux_mipsx.go index 37651ef7e4..b5c0d7f568 100644 --- a/src/runtime/defs_linux_mipsx.go +++ b/src/runtime/defs_linux_mipsx.go @@ -66,6 +66,8 @@ const ( _SIGXCPU = 0x1e _SIGXFSZ = 0x1f + _SIGRTMIN = 0x20 + _FPE_INTDIV = 0x1 _FPE_INTOVF = 0x2 _FPE_FLTDIV = 0x3 diff --git a/src/runtime/defs_linux_ppc64.go b/src/runtime/defs_linux_ppc64.go index c7aa7234c1..c077868cf8 100644 --- a/src/runtime/defs_linux_ppc64.go +++ b/src/runtime/defs_linux_ppc64.go @@ -63,6 +63,8 @@ const ( _SIGPWR = 0x1e _SIGSYS = 0x1f + _SIGRTMIN = 0x20 + _FPE_INTDIV = 0x1 _FPE_INTOVF = 0x2 _FPE_FLTDIV = 0x3 diff --git a/src/runtime/defs_linux_ppc64le.go b/src/runtime/defs_linux_ppc64le.go index c7aa7234c1..c077868cf8 100644 --- a/src/runtime/defs_linux_ppc64le.go +++ b/src/runtime/defs_linux_ppc64le.go @@ -63,6 +63,8 @@ const ( _SIGPWR = 0x1e _SIGSYS = 0x1f + _SIGRTMIN = 0x20 + _FPE_INTDIV = 0x1 _FPE_INTOVF = 0x2 _FPE_FLTDIV = 0x3 diff --git a/src/runtime/defs_linux_riscv64.go b/src/runtime/defs_linux_riscv64.go index 747e26bc4b..30bf1770d7 100644 --- a/src/runtime/defs_linux_riscv64.go +++ b/src/runtime/defs_linux_riscv64.go @@ -65,6 +65,8 @@ const ( _SIGPWR = 0x1e _SIGSYS = 0x1f + _SIGRTMIN = 0x20 + _FPE_INTDIV = 0x1 _FPE_INTOVF = 0x2 _FPE_FLTDIV = 0x3 diff --git a/src/runtime/defs_linux_s390x.go b/src/runtime/defs_linux_s390x.go index 740d8100c5..224136a463 100644 --- a/src/runtime/defs_linux_s390x.go +++ b/src/runtime/defs_linux_s390x.go @@ -64,6 +64,8 @@ const ( _SIGPWR = 0x1e _SIGSYS = 0x1f + _SIGRTMIN = 0x20 + _FPE_INTDIV = 0x1 _FPE_INTOVF = 0x2 _FPE_FLTDIV = 0x3 diff --git a/src/runtime/export_test.go b/src/runtime/export_test.go index 0f21838721..0ac15ce82c 100644 --- a/src/runtime/export_test.go +++ b/src/runtime/export_test.go @@ -1199,6 +1199,8 @@ func (th *TimeHistogram) Record(duration int64) { (*timeHistogram)(th).record(duration) } +var TimeHistogramMetricsBuckets = timeHistogramMetricsBuckets + func SetIntArgRegs(a int) int { lock(&finlock) old := intArgRegs @@ -1330,3 +1332,21 @@ func Releasem() { } var Timediv = timediv + +type PIController struct { + piController +} + +func NewPIController(kp, ti, tt, min, max float64) *PIController { + return &PIController{piController{ + kp: kp, + ti: ti, + tt: tt, + min: min, + max: max, + }} +} + +func (c *PIController) Next(input, setpoint, period float64) (float64, bool) { + return c.piController.next(input, setpoint, period) +} diff --git a/src/runtime/funcdata.h b/src/runtime/funcdata.h index a454dcaa69..2e2bb30446 100644 --- a/src/runtime/funcdata.h +++ b/src/runtime/funcdata.h @@ -20,6 +20,7 @@ #define FUNCDATA_OpenCodedDeferInfo 4 /* info for func with open-coded defers */ #define FUNCDATA_ArgInfo 5 #define FUNCDATA_ArgLiveInfo 6 +#define FUNCDATA_WrapInfo 7 // Pseudo-assembly statements. diff --git a/src/runtime/histogram.go b/src/runtime/histogram.go index 0cccbcca16..cd7e29a8c8 100644 --- a/src/runtime/histogram.go +++ b/src/runtime/histogram.go @@ -47,7 +47,7 @@ const ( // │ └---- Next 4 bits -> sub-bucket 1 // └------- Bit 5 set -> super-bucket 2 // - // Following this pattern, bucket 45 will have the bit 48 set. We don't + // Following this pattern, super-bucket 44 will have the bit 47 set. We don't // have any buckets for higher values, so the highest sub-bucket will // contain values of 2^48-1 nanoseconds or approx. 3 days. This range is // more than enough to handle durations produced by the runtime. @@ -139,36 +139,30 @@ func float64NegInf() float64 { func timeHistogramMetricsBuckets() []float64 { b := make([]float64, timeHistTotalBuckets+1) b[0] = float64NegInf() - for i := 0; i < timeHistNumSuperBuckets; i++ { - superBucketMin := uint64(0) - // The (inclusive) minimum for the first non-negative bucket is 0. - if i > 0 { - // The minimum for the second bucket will be - // 1 << timeHistSubBucketBits, indicating that all - // sub-buckets are represented by the next timeHistSubBucketBits - // bits. - // Thereafter, we shift up by 1 each time, so we can represent - // this pattern as (i-1)+timeHistSubBucketBits. - superBucketMin = uint64(1) << uint(i-1+timeHistSubBucketBits) - } - // subBucketShift is the amount that we need to shift the sub-bucket - // index to combine it with the bucketMin. - subBucketShift := uint(0) - if i > 1 { - // The first two super buckets are exact with respect to integers, - // so we'll never have to shift the sub-bucket index. Thereafter, - // we shift up by 1 with each subsequent bucket. - subBucketShift = uint(i - 2) - } + // Super-bucket 0 has no bits above timeHistSubBucketBits + // set, so just iterate over each bucket and assign the + // incrementing bucket. + for i := 0; i < timeHistNumSubBuckets; i++ { + bucketNanos := uint64(i) + b[i+1] = float64(bucketNanos) / 1e9 + } + // Generate the rest of the super-buckets. It's easier to reason + // about if we cut out the 0'th bucket, so subtract one since + // we just handled that bucket. + for i := 0; i < timeHistNumSuperBuckets-1; i++ { for j := 0; j < timeHistNumSubBuckets; j++ { - // j is the sub-bucket index. By shifting the index into position to - // combine with the bucket minimum, we obtain the minimum value for that - // sub-bucket. - subBucketMin := superBucketMin + (uint64(j) << subBucketShift) - - // Convert the subBucketMin which is in nanoseconds to a float64 seconds value. + // Set the super-bucket bit. + bucketNanos := uint64(1) << (i + timeHistSubBucketBits) + // Set the sub-bucket bits. + bucketNanos |= uint64(j) << i + // The index for this bucket is going to be the (i+1)'th super bucket + // (note that we're starting from zero, but handled the first super-bucket + // earlier, so we need to compensate), and the j'th sub bucket. + // Add 1 because we left space for -Inf. + bucketIndex := (i+1)*timeHistNumSubBuckets + j + 1 + // Convert nanoseconds to seconds via a division. // These values will all be exactly representable by a float64. - b[i*timeHistNumSubBuckets+j+1] = float64(subBucketMin) / 1e9 + b[bucketIndex] = float64(bucketNanos) / 1e9 } } b[len(b)-1] = float64Inf() diff --git a/src/runtime/histogram_test.go b/src/runtime/histogram_test.go index dbc64fa559..b12b65a41e 100644 --- a/src/runtime/histogram_test.go +++ b/src/runtime/histogram_test.go @@ -68,3 +68,43 @@ func TestTimeHistogram(t *testing.T) { dummyTimeHistogram = TimeHistogram{} } + +func TestTimeHistogramMetricsBuckets(t *testing.T) { + buckets := TimeHistogramMetricsBuckets() + + nonInfBucketsLen := TimeHistNumSubBuckets * TimeHistNumSuperBuckets + expBucketsLen := nonInfBucketsLen + 2 // Count -Inf and +Inf. + if len(buckets) != expBucketsLen { + t.Fatalf("unexpected length of buckets: got %d, want %d", len(buckets), expBucketsLen) + } + // Check the first non-Inf 2*TimeHistNumSubBuckets buckets in order, skipping the + // first bucket which should be -Inf (checked later). + // + // Because of the way this scheme works, the bottom TimeHistNumSubBuckets + // buckets are fully populated, and then the next TimeHistNumSubBuckets + // have the TimeHistSubBucketBits'th bit set, while the bottom are once + // again fully populated. + for i := 1; i <= 2*TimeHistNumSubBuckets+1; i++ { + if got, want := buckets[i], float64(i-1)/1e9; got != want { + t.Errorf("expected bucket %d to have value %e, got %e", i, want, got) + } + } + // Check some values. + idxToBucket := map[int]float64{ + 0: math.Inf(-1), + 33: float64(0x10<<1) / 1e9, + 34: float64(0x11<<1) / 1e9, + 49: float64(0x10<<2) / 1e9, + 58: float64(0x19<<2) / 1e9, + 65: float64(0x10<<3) / 1e9, + 513: float64(0x10<<31) / 1e9, + 519: float64(0x16<<31) / 1e9, + expBucketsLen - 2: float64(0x1f<<43) / 1e9, + expBucketsLen - 1: math.Inf(1), + } + for idx, bucket := range idxToBucket { + if got, want := buckets[idx], bucket; got != want { + t.Errorf("expected bucket %d to have value %e, got %e", idx, want, got) + } + } +} diff --git a/src/runtime/internal/atomic/atomic_arm.s b/src/runtime/internal/atomic/atomic_arm.s index be3fd3a395..92cbe8a34f 100644 --- a/src/runtime/internal/atomic/atomic_arm.s +++ b/src/runtime/internal/atomic/atomic_arm.s @@ -229,16 +229,22 @@ store64loop: // functions tail-call into the appropriate implementation, which // means they must not open a frame. Hence, when they go down the // panic path, at that point they push the LR to create a real frame -// (they don't need to pop it because panic won't return). +// (they don't need to pop it because panic won't return; however, we +// do need to set the SP delta back). + +// Check if R1 is 8-byte aligned, panic if not. +// Clobbers R2. +#define CHECK_ALIGN \ + AND.S $7, R1, R2 \ + BEQ 4(PC) \ + MOVW.W R14, -4(R13) /* prepare a real frame */ \ + BL ·panicUnaligned(SB) \ + ADD $4, R13 /* compensate SP delta */ TEXT ·Cas64(SB),NOSPLIT,$-4-21 NO_LOCAL_POINTERS MOVW addr+0(FP), R1 - // make unaligned atomic access panic - AND.S $7, R1, R2 - BEQ 3(PC) - MOVW.W R14, -4(R13) // prepare a real frame - BL ·panicUnaligned(SB) + CHECK_ALIGN MOVB runtime·goarm(SB), R11 CMP $7, R11 @@ -249,11 +255,7 @@ TEXT ·Cas64(SB),NOSPLIT,$-4-21 TEXT ·Xadd64(SB),NOSPLIT,$-4-20 NO_LOCAL_POINTERS MOVW addr+0(FP), R1 - // make unaligned atomic access panic - AND.S $7, R1, R2 - BEQ 3(PC) - MOVW.W R14, -4(R13) // prepare a real frame - BL ·panicUnaligned(SB) + CHECK_ALIGN MOVB runtime·goarm(SB), R11 CMP $7, R11 @@ -264,11 +266,7 @@ TEXT ·Xadd64(SB),NOSPLIT,$-4-20 TEXT ·Xchg64(SB),NOSPLIT,$-4-20 NO_LOCAL_POINTERS MOVW addr+0(FP), R1 - // make unaligned atomic access panic - AND.S $7, R1, R2 - BEQ 3(PC) - MOVW.W R14, -4(R13) // prepare a real frame - BL ·panicUnaligned(SB) + CHECK_ALIGN MOVB runtime·goarm(SB), R11 CMP $7, R11 @@ -279,11 +277,7 @@ TEXT ·Xchg64(SB),NOSPLIT,$-4-20 TEXT ·Load64(SB),NOSPLIT,$-4-12 NO_LOCAL_POINTERS MOVW addr+0(FP), R1 - // make unaligned atomic access panic - AND.S $7, R1, R2 - BEQ 3(PC) - MOVW.W R14, -4(R13) // prepare a real frame - BL ·panicUnaligned(SB) + CHECK_ALIGN MOVB runtime·goarm(SB), R11 CMP $7, R11 @@ -294,11 +288,7 @@ TEXT ·Load64(SB),NOSPLIT,$-4-12 TEXT ·Store64(SB),NOSPLIT,$-4-12 NO_LOCAL_POINTERS MOVW addr+0(FP), R1 - // make unaligned atomic access panic - AND.S $7, R1, R2 - BEQ 3(PC) - MOVW.W R14, -4(R13) // prepare a real frame - BL ·panicUnaligned(SB) + CHECK_ALIGN MOVB runtime·goarm(SB), R11 CMP $7, R11 diff --git a/src/runtime/internal/syscall/asm_linux_386.s b/src/runtime/internal/syscall/asm_linux_386.s new file mode 100644 index 0000000000..15aae4d8bd --- /dev/null +++ b/src/runtime/internal/syscall/asm_linux_386.s @@ -0,0 +1,34 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include "textflag.h" + +// See ../sys_linux_386.s for the reason why we always use int 0x80 +// instead of the glibc-specific "CALL 0x10(GS)". +#define INVOKE_SYSCALL INT $0x80 + +// func Syscall6(num, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, errno uintptr) +// +// Syscall # in AX, args in BX CX DX SI DI BP, return in AX +TEXT ·Syscall6(SB),NOSPLIT,$0-40 + MOVL num+0(FP), AX // syscall entry + MOVL a1+4(FP), BX + MOVL a2+8(FP), CX + MOVL a3+12(FP), DX + MOVL a4+16(FP), SI + MOVL a5+20(FP), DI + MOVL a6+24(FP), BP + INVOKE_SYSCALL + CMPL AX, $0xfffff001 + JLS ok + MOVL $-1, r1+28(FP) + MOVL $0, r2+32(FP) + NEGL AX + MOVL AX, errno+36(FP) + RET +ok: + MOVL AX, r1+28(FP) + MOVL DX, r2+32(FP) + MOVL $0, errno+36(FP) + RET diff --git a/src/runtime/internal/syscall/asm_linux_amd64.s b/src/runtime/internal/syscall/asm_linux_amd64.s new file mode 100644 index 0000000000..961d9bd640 --- /dev/null +++ b/src/runtime/internal/syscall/asm_linux_amd64.s @@ -0,0 +1,33 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include "textflag.h" + +// func Syscall6(num, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, errno uintptr) +// +// Syscall # in AX, args in DI SI DX R10 R8 R9, return in AX DX. +// +// Note that this differs from "standard" ABI convention, which would pass 4th +// arg in CX, not R10. +TEXT ·Syscall6(SB),NOSPLIT,$0-80 + MOVQ num+0(FP), AX // syscall entry + MOVQ a1+8(FP), DI + MOVQ a2+16(FP), SI + MOVQ a3+24(FP), DX + MOVQ a4+32(FP), R10 + MOVQ a5+40(FP), R8 + MOVQ a6+48(FP), R9 + SYSCALL + CMPQ AX, $0xfffffffffffff001 + JLS ok + MOVQ $-1, r1+56(FP) + MOVQ $0, r2+64(FP) + NEGQ AX + MOVQ AX, errno+72(FP) + RET +ok: + MOVQ AX, r1+56(FP) + MOVQ DX, r2+64(FP) + MOVQ $0, errno+72(FP) + RET diff --git a/src/runtime/internal/syscall/asm_linux_arm.s b/src/runtime/internal/syscall/asm_linux_arm.s new file mode 100644 index 0000000000..dbf1826d94 --- /dev/null +++ b/src/runtime/internal/syscall/asm_linux_arm.s @@ -0,0 +1,32 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include "textflag.h" + +// func Syscall6(num, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, errno uintptr) +TEXT ·Syscall6(SB),NOSPLIT,$0-40 + MOVW num+0(FP), R7 // syscall entry + MOVW a1+4(FP), R0 + MOVW a2+8(FP), R1 + MOVW a3+12(FP), R2 + MOVW a4+16(FP), R3 + MOVW a5+20(FP), R4 + MOVW a6+24(FP), R5 + SWI $0 + MOVW $0xfffff001, R6 + CMP R6, R0 + BLS ok + MOVW $-1, R1 + MOVW R1, r1+28(FP) + MOVW $0, R2 + MOVW R2, r2+32(FP) + RSB $0, R0, R0 + MOVW R0, errno+36(FP) + RET +ok: + MOVW R0, r1+28(FP) + MOVW R1, r2+32(FP) + MOVW $0, R0 + MOVW R0, errno+36(FP) + RET diff --git a/src/runtime/internal/syscall/asm_linux_arm64.s b/src/runtime/internal/syscall/asm_linux_arm64.s new file mode 100644 index 0000000000..83e862ff72 --- /dev/null +++ b/src/runtime/internal/syscall/asm_linux_arm64.s @@ -0,0 +1,29 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include "textflag.h" + +// func Syscall6(num, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, errno uintptr) +TEXT ·Syscall6(SB),NOSPLIT,$0-80 + MOVD num+0(FP), R8 // syscall entry + MOVD a1+8(FP), R0 + MOVD a2+16(FP), R1 + MOVD a3+24(FP), R2 + MOVD a4+32(FP), R3 + MOVD a5+40(FP), R4 + MOVD a6+48(FP), R5 + SVC + CMN $4095, R0 + BCC ok + MOVD $-1, R4 + MOVD R4, r1+56(FP) + MOVD ZR, r2+64(FP) + NEG R0, R0 + MOVD R0, errno+72(FP) + RET +ok: + MOVD R0, r1+56(FP) + MOVD R1, r2+64(FP) + MOVD ZR, errno+72(FP) + RET diff --git a/src/runtime/internal/syscall/asm_linux_mips64x.s b/src/runtime/internal/syscall/asm_linux_mips64x.s new file mode 100644 index 0000000000..0e88a2d8ac --- /dev/null +++ b/src/runtime/internal/syscall/asm_linux_mips64x.s @@ -0,0 +1,29 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build linux && (mips64 || mips64le) + +#include "textflag.h" + +// func Syscall6(num, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, errno uintptr) +TEXT ·Syscall6(SB),NOSPLIT,$0-80 + MOVV num+0(FP), R2 // syscall entry + MOVV a1+8(FP), R4 + MOVV a2+16(FP), R5 + MOVV a3+24(FP), R6 + MOVV a4+32(FP), R7 + MOVV a5+40(FP), R8 + MOVV a6+48(FP), R9 + SYSCALL + BEQ R7, ok + MOVV $-1, R1 + MOVV R1, r1+56(FP) + MOVV R0, r2+64(FP) + MOVV R2, errno+72(FP) + RET +ok: + MOVV R2, r1+56(FP) + MOVV R3, r2+64(FP) + MOVV R0, errno+72(FP) + RET diff --git a/src/runtime/internal/syscall/asm_linux_mipsx.s b/src/runtime/internal/syscall/asm_linux_mipsx.s new file mode 100644 index 0000000000..050029eaa1 --- /dev/null +++ b/src/runtime/internal/syscall/asm_linux_mipsx.s @@ -0,0 +1,34 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build linux && (mips || mipsle) + +#include "textflag.h" + +// func Syscall6(num, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, errno uintptr) +// +// The 5th and 6th arg go at sp+16, sp+20. +// Note that frame size of 20 means that 24 bytes gets reserved on stack. +TEXT ·Syscall6(SB),NOSPLIT,$20-40 + MOVW num+0(FP), R2 // syscall entry + MOVW a1+4(FP), R4 + MOVW a2+8(FP), R5 + MOVW a3+12(FP), R6 + MOVW a4+16(FP), R7 + MOVW a5+20(FP), R8 + MOVW a6+24(FP), R9 + MOVW R8, 16(R29) + MOVW R9, 20(R29) + SYSCALL + BEQ R7, ok + MOVW $-1, R1 + MOVW R1, r1+28(FP) + MOVW R0, r2+32(FP) + MOVW R2, errno+36(FP) + RET +ok: + MOVW R2, r1+28(FP) + MOVW R3, r2+32(FP) + MOVW R0, errno+36(FP) + RET diff --git a/src/runtime/internal/syscall/asm_linux_ppc64x.s b/src/runtime/internal/syscall/asm_linux_ppc64x.s new file mode 100644 index 0000000000..8e8463810d --- /dev/null +++ b/src/runtime/internal/syscall/asm_linux_ppc64x.s @@ -0,0 +1,29 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build linux && (ppc64 || ppc64le) + +#include "textflag.h" + +// func Syscall6(num, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, errno uintptr) +TEXT ·Syscall6(SB),NOSPLIT,$0-80 + MOVD num+0(FP), R9 // syscall entry + MOVD a1+8(FP), R3 + MOVD a2+16(FP), R4 + MOVD a3+24(FP), R5 + MOVD a4+32(FP), R6 + MOVD a5+40(FP), R7 + MOVD a6+48(FP), R8 + SYSCALL R9 + BVC ok + MOVD $-1, R4 + MOVD R4, r1+56(FP) + MOVD R0, r2+64(FP) + MOVD R3, errno+72(FP) + RET +ok: + MOVD R3, r1+56(FP) + MOVD R4, r2+64(FP) + MOVD R0, errno+72(FP) + RET diff --git a/src/runtime/internal/syscall/asm_linux_riscv64.s b/src/runtime/internal/syscall/asm_linux_riscv64.s new file mode 100644 index 0000000000..a8652fdd6b --- /dev/null +++ b/src/runtime/internal/syscall/asm_linux_riscv64.s @@ -0,0 +1,29 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include "textflag.h" + +// func Syscall6(num, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, errno uintptr) +TEXT ·Syscall6(SB),NOSPLIT,$0-80 + MOV num+0(FP), A7 // syscall entry + MOV a1+8(FP), A0 + MOV a2+16(FP), A1 + MOV a3+24(FP), A2 + MOV a4+32(FP), A3 + MOV a5+40(FP), A4 + MOV a6+48(FP), A5 + ECALL + MOV $-4096, T0 + BLTU T0, A0, err + MOV A0, r1+56(FP) + MOV A1, r2+64(FP) + MOV ZERO, errno+72(FP) + RET +err: + MOV $-1, T0 + MOV T0, r1+56(FP) + MOV ZERO, r2+64(FP) + SUB A0, ZERO, A0 + MOV A0, errno+72(FP) + RET diff --git a/src/runtime/internal/syscall/asm_linux_s390x.s b/src/runtime/internal/syscall/asm_linux_s390x.s new file mode 100644 index 0000000000..1b27f29390 --- /dev/null +++ b/src/runtime/internal/syscall/asm_linux_s390x.s @@ -0,0 +1,28 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include "textflag.h" + +// func Syscall6(num, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, errno uintptr) +TEXT ·Syscall6(SB),NOSPLIT,$0-80 + MOVD num+0(FP), R1 // syscall entry + MOVD a1+8(FP), R2 + MOVD a2+16(FP), R3 + MOVD a3+24(FP), R4 + MOVD a4+32(FP), R5 + MOVD a5+40(FP), R6 + MOVD a6+48(FP), R7 + SYSCALL + MOVD $0xfffffffffffff001, R8 + CMPUBLT R2, R8, ok + MOVD $-1, r1+56(FP) + MOVD $0, r2+64(FP) + NEG R2, R2 + MOVD R2, errno+72(FP) + RET +ok: + MOVD R2, r1+56(FP) + MOVD R3, r2+64(FP) + MOVD $0, errno+72(FP) + RET diff --git a/src/runtime/internal/syscall/syscall_linux.go b/src/runtime/internal/syscall/syscall_linux.go new file mode 100644 index 0000000000..06d5f21e7c --- /dev/null +++ b/src/runtime/internal/syscall/syscall_linux.go @@ -0,0 +1,12 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package syscall provides the syscall primitives required for the runtime. +package syscall + +// TODO(https://go.dev/issue/51087): This package is incomplete and currently +// only contains very minimal support for Linux. + +// Syscall6 calls system call number 'num' with arguments a1-6. +func Syscall6(num, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, errno uintptr) diff --git a/src/runtime/mfinal.go b/src/runtime/mfinal.go index e2ac5d4993..10623e4d67 100644 --- a/src/runtime/mfinal.go +++ b/src/runtime/mfinal.go @@ -187,21 +187,15 @@ func runfinq() { f := &fb.fin[i-1] var regs abi.RegArgs - var framesz uintptr - if argRegs > 0 { - // The args can always be passed in registers if they're - // available, because platforms we support always have no - // argument registers available, or more than 2. - // - // But unfortunately because we can have an arbitrary - // amount of returns and it would be complex to try and - // figure out how many of those can get passed in registers, - // just conservatively assume none of them do. - framesz = f.nret - } else { - // Need to pass arguments on the stack too. - framesz = unsafe.Sizeof((any)(nil)) + f.nret - } + // The args may be passed in registers or on stack. Even for + // the register case, we still need the spill slots. + // TODO: revisit if we remove spill slots. + // + // Unfortunately because we can have an arbitrary + // amount of returns and it would be complex to try and + // figure out how many of those can get passed in registers, + // just conservatively assume none of them do. + framesz := unsafe.Sizeof((any)(nil)) + f.nret if framecap < framesz { // The frame does not contain pointers interesting for GC, // all not yet finalized objects are stored in finq. diff --git a/src/runtime/mfinal_test.go b/src/runtime/mfinal_test.go index 04ba7a6830..902ccc57f8 100644 --- a/src/runtime/mfinal_test.go +++ b/src/runtime/mfinal_test.go @@ -42,6 +42,15 @@ func TestFinalizerType(t *testing.T) { {func(x *int) any { return Tintptr(x) }, func(v *int) { finalize(v) }}, {func(x *int) any { return (*Tint)(x) }, func(v *Tint) { finalize((*int)(v)) }}, {func(x *int) any { return (*Tint)(x) }, func(v Tinter) { finalize((*int)(v.(*Tint))) }}, + // Test case for argument spill slot. + // If the spill slot was not counted for the frame size, it will (incorrectly) choose + // call32 as the result has (exactly) 32 bytes. When the argument actually spills, + // it clobbers the caller's frame (likely the return PC). + {func(x *int) any { return x }, func(v any) [4]int64 { + print() // force spill + finalize(v.(*int)) + return [4]int64{} + }}, } for i, tt := range finalizerTests { diff --git a/src/runtime/mgcpacer.go b/src/runtime/mgcpacer.go index f06560201a..d54dbc26c2 100644 --- a/src/runtime/mgcpacer.go +++ b/src/runtime/mgcpacer.go @@ -154,6 +154,8 @@ type gcControllerState struct { // For goexperiment.PacerRedesign. consMarkController piController + _ uint32 // Padding for atomics on 32-bit platforms. + // heapGoal is the goal heapLive for when next GC ends. // Set to ^uint64(0) if disabled. // @@ -670,10 +672,31 @@ func (c *gcControllerState) endCycle(now int64, procs int, userForced bool) floa currentConsMark := (float64(c.heapLive-c.trigger) * (utilization + idleUtilization)) / (float64(scanWork) * (1 - utilization)) - // Update cons/mark controller. - // Period for this is 1 GC cycle. + // Update cons/mark controller. The time period for this is 1 GC cycle. + // + // This use of a PI controller might seem strange. So, here's an explanation: + // + // currentConsMark represents the consMark we *should've* had to be perfectly + // on-target for this cycle. Given that we assume the next GC will be like this + // one in the steady-state, it stands to reason that we should just pick that + // as our next consMark. In practice, however, currentConsMark is too noisy: + // we're going to be wildly off-target in each GC cycle if we do that. + // + // What we do instead is make a long-term assumption: there is some steady-state + // consMark value, but it's obscured by noise. By constantly shooting for this + // noisy-but-perfect consMark value, the controller will bounce around a bit, + // but its average behavior, in aggregate, should be less noisy and closer to + // the true long-term consMark value, provided its tuned to be slightly overdamped. + var ok bool oldConsMark := c.consMark - c.consMark = c.consMarkController.next(c.consMark, currentConsMark, 1.0) + c.consMark, ok = c.consMarkController.next(c.consMark, currentConsMark, 1.0) + if !ok { + // The error spiraled out of control. This is incredibly unlikely seeing + // as this controller is essentially just a smoothing function, but it might + // mean that something went very wrong with how currentConsMark was calculated. + // Just reset consMark and keep going. + c.consMark = 0 + } if debug.gcpacertrace > 0 { printlock() @@ -681,6 +704,9 @@ func (c *gcControllerState) endCycle(now int64, procs int, userForced bool) floa print("pacer: ", int(utilization*100), "% CPU (", int(goal), " exp.) for ") print(c.heapScanWork.Load(), "+", c.stackScanWork.Load(), "+", c.globalsScanWork.Load(), " B work (", c.lastHeapScan+c.stackScan+c.globalsScan, " B exp.) ") print("in ", c.trigger, " B -> ", c.heapLive, " B (∆goal ", int64(c.heapLive)-int64(c.heapGoal), ", cons/mark ", oldConsMark, ")") + if !ok { + print("[controller reset]") + } println() printunlock() } @@ -1263,15 +1289,38 @@ type piController struct { // PI controller state. errIntegral float64 // Integral of the error from t=0 to now. + + // Error flags. + errOverflow bool // Set if errIntegral ever overflowed. + inputOverflow bool // Set if an operation with the input overflowed. } -func (c *piController) next(input, setpoint, period float64) float64 { +// next provides a new sample to the controller. +// +// input is the sample, setpoint is the desired point, and period is how much +// time (in whatever unit makes the most sense) has passed since the last sample. +// +// Returns a new value for the variable it's controlling, and whether the operation +// completed successfully. One reason this might fail is if error has been growing +// in an unbounded manner, to the point of overflow. +// +// In the specific case of an error overflow occurs, the errOverflow field will be +// set and the rest of the controller's internal state will be fully reset. +func (c *piController) next(input, setpoint, period float64) (float64, bool) { // Compute the raw output value. prop := c.kp * (setpoint - input) rawOutput := prop + c.errIntegral // Clamp rawOutput into output. output := rawOutput + if isInf(output) || isNaN(output) { + // The input had a large enough magnitude that either it was already + // overflowed, or some operation with it overflowed. + // Set a flag and reset. That's the safest thing to do. + c.reset() + c.inputOverflow = true + return c.min, false + } if output < c.min { output = c.min } else if output > c.max { @@ -1281,6 +1330,19 @@ func (c *piController) next(input, setpoint, period float64) float64 { // Update the controller's state. if c.ti != 0 && c.tt != 0 { c.errIntegral += (c.kp*period/c.ti)*(setpoint-input) + (period/c.tt)*(output-rawOutput) + if isInf(c.errIntegral) || isNaN(c.errIntegral) { + // So much error has accumulated that we managed to overflow. + // The assumptions around the controller have likely broken down. + // Set a flag and reset. That's the safest thing to do. + c.reset() + c.errOverflow = true + return c.min, false + } } - return output + return output, true +} + +// reset resets the controller state, except for controller error flags. +func (c *piController) reset() { + c.errIntegral = 0 } diff --git a/src/runtime/mgcpacer_test.go b/src/runtime/mgcpacer_test.go index 9ec0e5172b..10a8ca2520 100644 --- a/src/runtime/mgcpacer_test.go +++ b/src/runtime/mgcpacer_test.go @@ -715,3 +715,48 @@ func (f float64Stream) limit(min, max float64) float64Stream { return v } } + +func FuzzPIController(f *testing.F) { + isNormal := func(x float64) bool { + return !math.IsInf(x, 0) && !math.IsNaN(x) + } + isPositive := func(x float64) bool { + return isNormal(x) && x > 0 + } + // Seed with constants from controllers in the runtime. + // It's not critical that we keep these in sync, they're just + // reasonable seed inputs. + f.Add(0.3375, 3.2e6, 1e9, 0.001, 1000.0, 0.01) + f.Add(0.9, 4.0, 1000.0, -1000.0, 1000.0, 0.84) + f.Fuzz(func(t *testing.T, kp, ti, tt, min, max, setPoint float64) { + // Ignore uninteresting invalid parameters. These parameters + // are constant, so in practice surprising values will be documented + // or will be other otherwise immediately visible. + // + // We just want to make sure that given a non-Inf, non-NaN input, + // we always get a non-Inf, non-NaN output. + if !isPositive(kp) || !isPositive(ti) || !isPositive(tt) { + return + } + if !isNormal(min) || !isNormal(max) || min > max { + return + } + // Use a random source, but make it deterministic. + rs := rand.New(rand.NewSource(800)) + randFloat64 := func() float64 { + return math.Float64frombits(rs.Uint64()) + } + p := NewPIController(kp, ti, tt, min, max) + state := float64(0) + for i := 0; i < 100; i++ { + input := randFloat64() + // Ignore the "ok" parameter. We're just trying to break it. + // state is intentionally completely uncorrelated with the input. + var ok bool + state, ok = p.Next(input, setPoint, 1.0) + if !isNormal(state) { + t.Fatalf("got NaN or Inf result from controller: %f %v", state, ok) + } + } + }) +} diff --git a/src/runtime/mgcscavenge.go b/src/runtime/mgcscavenge.go index c27e189af9..5f50378adf 100644 --- a/src/runtime/mgcscavenge.go +++ b/src/runtime/mgcscavenge.go @@ -165,11 +165,12 @@ func gcPaceScavenger(heapGoal, lastHeapGoal uint64) { // Sleep/wait state of the background scavenger. var scavenge struct { - lock mutex - g *g - parked bool - timer *timer - sysmonWake uint32 // Set atomically. + lock mutex + g *g + parked bool + timer *timer + sysmonWake uint32 // Set atomically. + printControllerReset bool // Whether the scavenger is in cooldown. } // readyForScavenger signals sysmon to wake the scavenger because @@ -295,8 +296,14 @@ func bgscavenge(c chan int) { max: 1000.0, // 1000:1 } // It doesn't really matter what value we start at, but we can't be zero, because - // that'll cause divide-by-zero issues. - critSleepRatio := 0.001 + // that'll cause divide-by-zero issues. Pick something conservative which we'll + // also use as a fallback. + const startingCritSleepRatio = 0.001 + critSleepRatio := startingCritSleepRatio + // Duration left in nanoseconds during which we avoid using the controller and + // we hold critSleepRatio at a conservative value. Used if the controller's + // assumptions fail to hold. + controllerCooldown := int64(0) for { released := uintptr(0) crit := float64(0) @@ -383,9 +390,22 @@ func bgscavenge(c chan int) { // because of the additional overheads of using scavenged memory. crit *= 1 + scavengeCostRatio - // Go to sleep for our current sleepNS. + // Go to sleep based on how much time we spent doing work. slept := scavengeSleep(int64(crit / critSleepRatio)) + // Stop here if we're cooling down from the controller. + if controllerCooldown > 0 { + // crit and slept aren't exact measures of time, but it's OK to be a bit + // sloppy here. We're just hoping we're avoiding some transient bad behavior. + t := slept + int64(crit) + if t > controllerCooldown { + controllerCooldown = 0 + } else { + controllerCooldown -= t + } + continue + } + // Calculate the CPU time spent. // // This may be slightly inaccurate with respect to GOMAXPROCS, but we're @@ -395,7 +415,20 @@ func bgscavenge(c chan int) { cpuFraction := float64(crit) / ((float64(slept) + crit) * float64(gomaxprocs)) // Update the critSleepRatio, adjusting until we reach our ideal fraction. - critSleepRatio = critSleepController.next(cpuFraction, idealFraction, float64(slept)+crit) + var ok bool + critSleepRatio, ok = critSleepController.next(cpuFraction, idealFraction, float64(slept)+crit) + if !ok { + // The core assumption of the controller, that we can get a proportional + // response, broke down. This may be transient, so temporarily switch to + // sleeping a fixed, conservative amount. + critSleepRatio = startingCritSleepRatio + controllerCooldown = 5e9 // 5 seconds. + + // Signal the scav trace printer to output this. + lock(&scavenge.lock) + scavenge.printControllerReset = true + unlock(&scavenge.lock) + } } } @@ -434,7 +467,11 @@ func (p *pageAlloc) scavenge(nbytes uintptr) uintptr { // released should be the amount of memory released since the last time this // was called, and forced indicates whether the scavenge was forced by the // application. +// +// scavenge.lock must be held. func printScavTrace(gen uint32, released uintptr, forced bool) { + assertLockHeld(&scavenge.lock) + printlock() print("scav ", gen, " ", released>>10, " KiB work, ", @@ -443,6 +480,9 @@ func printScavTrace(gen uint32, released uintptr, forced bool) { ) if forced { print(" (forced)") + } else if scavenge.printControllerReset { + print(" [controller reset]") + scavenge.printControllerReset = false } println() printunlock() diff --git a/src/runtime/os3_solaris.go b/src/runtime/os3_solaris.go index 2e946656d0..4aba0ff64b 100644 --- a/src/runtime/os3_solaris.go +++ b/src/runtime/os3_solaris.go @@ -634,3 +634,12 @@ func sysauxv(auxv []uintptr) { } } } + +// sigPerThreadSyscall is only used on linux, so we assign a bogus signal +// number. +const sigPerThreadSyscall = 1 << 31 + +//go:nosplit +func runPerThreadSyscall() { + throw("runPerThreadSyscall only valid on linux") +} diff --git a/src/runtime/os_aix.go b/src/runtime/os_aix.go index aeff593d50..292ff94795 100644 --- a/src/runtime/os_aix.go +++ b/src/runtime/os_aix.go @@ -373,3 +373,12 @@ func setNonblock(fd int32) { flags := fcntl(fd, _F_GETFL, 0) fcntl(fd, _F_SETFL, flags|_O_NONBLOCK) } + +// sigPerThreadSyscall is only used on linux, so we assign a bogus signal +// number. +const sigPerThreadSyscall = 1 << 31 + +//go:nosplit +func runPerThreadSyscall() { + throw("runPerThreadSyscall only valid on linux") +} diff --git a/src/runtime/os_darwin.go b/src/runtime/os_darwin.go index 0f0eb6c6fd..9065b76375 100644 --- a/src/runtime/os_darwin.go +++ b/src/runtime/os_darwin.go @@ -459,3 +459,12 @@ func sysargs(argc int32, argv **byte) { func signalM(mp *m, sig int) { pthread_kill(pthread(mp.procid), uint32(sig)) } + +// sigPerThreadSyscall is only used on linux, so we assign a bogus signal +// number. +const sigPerThreadSyscall = 1 << 31 + +//go:nosplit +func runPerThreadSyscall() { + throw("runPerThreadSyscall only valid on linux") +} diff --git a/src/runtime/os_dragonfly.go b/src/runtime/os_dragonfly.go index cba2e42ab0..152d94cf43 100644 --- a/src/runtime/os_dragonfly.go +++ b/src/runtime/os_dragonfly.go @@ -324,3 +324,12 @@ func raise(sig uint32) { func signalM(mp *m, sig int) { lwp_kill(-1, int32(mp.procid), sig) } + +// sigPerThreadSyscall is only used on linux, so we assign a bogus signal +// number. +const sigPerThreadSyscall = 1 << 31 + +//go:nosplit +func runPerThreadSyscall() { + throw("runPerThreadSyscall only valid on linux") +} diff --git a/src/runtime/os_freebsd.go b/src/runtime/os_freebsd.go index c63b0e3d69..d908a80cd1 100644 --- a/src/runtime/os_freebsd.go +++ b/src/runtime/os_freebsd.go @@ -460,3 +460,12 @@ func raise(sig uint32) { func signalM(mp *m, sig int) { thr_kill(thread(mp.procid), sig) } + +// sigPerThreadSyscall is only used on linux, so we assign a bogus signal +// number. +const sigPerThreadSyscall = 1 << 31 + +//go:nosplit +func runPerThreadSyscall() { + throw("runPerThreadSyscall only valid on linux") +} diff --git a/src/runtime/os_linux.go b/src/runtime/os_linux.go index 32a1e1b4f7..eb8aa076e9 100644 --- a/src/runtime/os_linux.go +++ b/src/runtime/os_linux.go @@ -8,9 +8,15 @@ import ( "internal/abi" "internal/goarch" "runtime/internal/atomic" + "runtime/internal/syscall" "unsafe" ) +// sigPerThreadSyscall is the same signal (SIGSETXID) used by glibc for +// per-thread syscalls on Linux. We use it for the same purpose in non-cgo +// binaries. +const sigPerThreadSyscall = _SIGRTMIN + 1 + type mOS struct { // profileTimer holds the ID of the POSIX interval timer for profiling CPU // usage on this thread. @@ -21,6 +27,10 @@ type mOS struct { // are in signal handling code, access to that field uses atomic operations. profileTimer int32 profileTimerValid uint32 + + // needPerThreadSyscall indicates that a per-thread syscall is required + // for doAllThreadsSyscall. + needPerThreadSyscall atomic.Uint8 } //go:noescape @@ -664,3 +674,205 @@ func setThreadCPUProfiler(hz int32) { mp.profileTimer = timerid atomic.Store(&mp.profileTimerValid, 1) } + +// perThreadSyscallArgs contains the system call number, arguments, and +// expected return values for a system call to be executed on all threads. +type perThreadSyscallArgs struct { + trap uintptr + a1 uintptr + a2 uintptr + a3 uintptr + a4 uintptr + a5 uintptr + a6 uintptr + r1 uintptr + r2 uintptr +} + +// perThreadSyscall is the system call to execute for the ongoing +// doAllThreadsSyscall. +// +// perThreadSyscall may only be written while mp.needPerThreadSyscall == 0 on +// all Ms. +var perThreadSyscall perThreadSyscallArgs + +// syscall_runtime_doAllThreadsSyscall and executes a specified system call on +// all Ms. +// +// The system call is expected to succeed and return the same value on every +// thread. If any threads do not match, the runtime throws. +// +//go:linkname syscall_runtime_doAllThreadsSyscall syscall.runtime_doAllThreadsSyscall +//go:uintptrescapes +func syscall_runtime_doAllThreadsSyscall(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr) { + if iscgo { + // In cgo, we are not aware of threads created in C, so this approach will not work. + panic("doAllThreadsSyscall not supported with cgo enabled") + } + + // STW to guarantee that user goroutines see an atomic change to thread + // state. Without STW, goroutines could migrate Ms while change is in + // progress and e.g., see state old -> new -> old -> new. + // + // N.B. Internally, this function does not depend on STW to + // successfully change every thread. It is only needed for user + // expectations, per above. + stopTheWorld("doAllThreadsSyscall") + + // This function depends on several properties: + // + // 1. All OS threads that already exist are associated with an M in + // allm. i.e., we won't miss any pre-existing threads. + // 2. All Ms listed in allm will eventually have an OS thread exist. + // i.e., they will set procid and be able to receive signals. + // 3. OS threads created after we read allm will clone from a thread + // that has executed the system call. i.e., they inherit the + // modified state. + // + // We achieve these through different mechanisms: + // + // 1. Addition of new Ms to allm in allocm happens before clone of its + // OS thread later in newm. + // 2. newm does acquirem to avoid being preempted, ensuring that new Ms + // created in allocm will eventually reach OS thread clone later in + // newm. + // 3. We take allocmLock for write here to prevent allocation of new Ms + // while this function runs. Per (1), this prevents clone of OS + // threads that are not yet in allm. + allocmLock.lock() + + // Disable preemption, preventing us from changing Ms, as we handle + // this M specially. + // + // N.B. STW and lock() above do this as well, this is added for extra + // clarity. + acquirem() + + // N.B. allocmLock also prevents concurrent execution of this function, + // serializing use of perThreadSyscall, mp.needPerThreadSyscall, and + // ensuring all threads execute system calls from multiple calls in the + // same order. + + r1, r2, errno := syscall.Syscall6(trap, a1, a2, a3, a4, a5, a6) + if GOARCH == "ppc64" || GOARCH == "ppc64le" { + // TODO(https://go.dev/issue/51192 ): ppc64 doesn't use r2. + r2 = 0 + } + if errno != 0 { + releasem(getg().m) + allocmLock.unlock() + startTheWorld() + return r1, r2, errno + } + + perThreadSyscall = perThreadSyscallArgs{ + trap: trap, + a1: a1, + a2: a2, + a3: a3, + a4: a4, + a5: a5, + a6: a6, + r1: r1, + r2: r2, + } + + // Wait for all threads to start. + // + // As described above, some Ms have been added to allm prior to + // allocmLock, but not yet completed OS clone and set procid. + // + // At minimum we must wait for a thread to set procid before we can + // send it a signal. + // + // We take this one step further and wait for all threads to start + // before sending any signals. This prevents system calls from getting + // applied twice: once in the parent and once in the child, like so: + // + // A B C + // add C to allm + // doAllThreadsSyscall + // allocmLock.lock() + // signal B + // + // execute syscall + // + // clone C + // + // set procid + // signal C + // + // execute syscall + // + // + // In this case, thread C inherited the syscall-modified state from + // thread B and did not need to execute the syscall, but did anyway + // because doAllThreadsSyscall could not be sure whether it was + // required. + // + // Some system calls may not be idempotent, so we ensure each thread + // executes the system call exactly once. + for mp := allm; mp != nil; mp = mp.alllink { + for atomic.Load64(&mp.procid) == 0 { + // Thread is starting. + osyield() + } + } + + // Signal every other thread, where they will execute perThreadSyscall + // from the signal handler. + gp := getg() + tid := gp.m.procid + for mp := allm; mp != nil; mp = mp.alllink { + if atomic.Load64(&mp.procid) == tid { + // Our thread already performed the syscall. + continue + } + mp.needPerThreadSyscall.Store(1) + signalM(mp, sigPerThreadSyscall) + } + + // Wait for all threads to complete. + for mp := allm; mp != nil; mp = mp.alllink { + if mp.procid == tid { + continue + } + for mp.needPerThreadSyscall.Load() != 0 { + osyield() + } + } + + perThreadSyscall = perThreadSyscallArgs{} + + releasem(getg().m) + allocmLock.unlock() + startTheWorld() + + return r1, r2, errno +} + +// runPerThreadSyscall runs perThreadSyscall for this M if required. +// +// This function throws if the system call returns with anything other than the +// expected values. +//go:nosplit +func runPerThreadSyscall() { + gp := getg() + if gp.m.needPerThreadSyscall.Load() == 0 { + return + } + + args := perThreadSyscall + r1, r2, errno := syscall.Syscall6(args.trap, args.a1, args.a2, args.a3, args.a4, args.a5, args.a6) + if GOARCH == "ppc64" || GOARCH == "ppc64le" { + // TODO(https://go.dev/issue/51192 ): ppc64 doesn't use r2. + r2 = 0 + } + if errno != 0 || r1 != args.r1 || r2 != args.r2 { + print("trap:", args.trap, ", a123456=[", args.a1, ",", args.a2, ",", args.a3, ",", args.a4, ",", args.a5, ",", args.a6, "]\n") + print("results: got {r1=", r1, ",r2=", r2, ",errno=", errno, "}, want {r1=", args.r1, ",r2=", args.r2, ",errno=0\n") + throw("AllThreadsSyscall6 results differ between threads; runtime corrupted") + } + + gp.m.needPerThreadSyscall.Store(0) +} diff --git a/src/runtime/os_netbsd.go b/src/runtime/os_netbsd.go index cd9508c706..c4e69fb189 100644 --- a/src/runtime/os_netbsd.go +++ b/src/runtime/os_netbsd.go @@ -428,3 +428,12 @@ func raise(sig uint32) { func signalM(mp *m, sig int) { lwp_kill(int32(mp.procid), sig) } + +// sigPerThreadSyscall is only used on linux, so we assign a bogus signal +// number. +const sigPerThreadSyscall = 1 << 31 + +//go:nosplit +func runPerThreadSyscall() { + throw("runPerThreadSyscall only valid on linux") +} diff --git a/src/runtime/os_openbsd.go b/src/runtime/os_openbsd.go index 2d0e71de53..1a00b890db 100644 --- a/src/runtime/os_openbsd.go +++ b/src/runtime/os_openbsd.go @@ -286,3 +286,12 @@ func raise(sig uint32) { func signalM(mp *m, sig int) { thrkill(int32(mp.procid), sig) } + +// sigPerThreadSyscall is only used on linux, so we assign a bogus signal +// number. +const sigPerThreadSyscall = 1 << 31 + +//go:nosplit +func runPerThreadSyscall() { + throw("runPerThreadSyscall only valid on linux") +} diff --git a/src/runtime/pprof/pprof_test.go b/src/runtime/pprof/pprof_test.go index 1a44ab7ad7..322579cdc4 100644 --- a/src/runtime/pprof/pprof_test.go +++ b/src/runtime/pprof/pprof_test.go @@ -794,7 +794,7 @@ func use(x [8 << 18]byte) {} func TestBlockProfile(t *testing.T) { type TestCase struct { name string - f func() + f func(*testing.T) stk []string re string } @@ -903,7 +903,7 @@ func TestBlockProfile(t *testing.T) { runtime.SetBlockProfileRate(1) defer runtime.SetBlockProfileRate(0) for _, test := range tests { - test.f() + test.f(t) } t.Run("debug=1", func(t *testing.T) { @@ -979,42 +979,73 @@ func containsStack(got [][]string, want []string) bool { return false } -const blockDelay = 10 * time.Millisecond +// awaitBlockedGoroutine spins on runtime.Gosched until a runtime stack dump +// shows a goroutine in the given state with a stack frame in +// runtime/pprof.. +func awaitBlockedGoroutine(t *testing.T, state, fName string) { + re := fmt.Sprintf(`(?m)^goroutine \d+ \[%s\]:\n(?:.+\n\t.+\n)*runtime/pprof\.%s`, regexp.QuoteMeta(state), fName) + r := regexp.MustCompile(re) -func blockChanRecv() { + if deadline, ok := t.Deadline(); ok { + if d := time.Until(deadline); d > 1*time.Second { + timer := time.AfterFunc(d-1*time.Second, func() { + debug.SetTraceback("all") + panic(fmt.Sprintf("timed out waiting for %#q", re)) + }) + defer timer.Stop() + } + } + + buf := make([]byte, 64<<10) + for { + runtime.Gosched() + n := runtime.Stack(buf, true) + if n == len(buf) { + // Buffer wasn't large enough for a full goroutine dump. + // Resize it and try again. + buf = make([]byte, 2*len(buf)) + continue + } + if r.Match(buf[:n]) { + return + } + } +} + +func blockChanRecv(t *testing.T) { c := make(chan bool) go func() { - time.Sleep(blockDelay) + awaitBlockedGoroutine(t, "chan receive", "blockChanRecv") c <- true }() <-c } -func blockChanSend() { +func blockChanSend(t *testing.T) { c := make(chan bool) go func() { - time.Sleep(blockDelay) + awaitBlockedGoroutine(t, "chan send", "blockChanSend") <-c }() c <- true } -func blockChanClose() { +func blockChanClose(t *testing.T) { c := make(chan bool) go func() { - time.Sleep(blockDelay) + awaitBlockedGoroutine(t, "chan receive", "blockChanClose") close(c) }() <-c } -func blockSelectRecvAsync() { +func blockSelectRecvAsync(t *testing.T) { const numTries = 3 c := make(chan bool, 1) c2 := make(chan bool, 1) go func() { for i := 0; i < numTries; i++ { - time.Sleep(blockDelay) + awaitBlockedGoroutine(t, "select", "blockSelectRecvAsync") c <- true } }() @@ -1026,11 +1057,11 @@ func blockSelectRecvAsync() { } } -func blockSelectSendSync() { +func blockSelectSendSync(t *testing.T) { c := make(chan bool) c2 := make(chan bool) go func() { - time.Sleep(blockDelay) + awaitBlockedGoroutine(t, "select", "blockSelectSendSync") <-c }() select { @@ -1039,11 +1070,11 @@ func blockSelectSendSync() { } } -func blockMutex() { +func blockMutex(t *testing.T) { var mu sync.Mutex mu.Lock() go func() { - time.Sleep(blockDelay) + awaitBlockedGoroutine(t, "semacquire", "blockMutex") mu.Unlock() }() // Note: Unlock releases mu before recording the mutex event, @@ -1053,12 +1084,12 @@ func blockMutex() { mu.Lock() } -func blockCond() { +func blockCond(t *testing.T) { var mu sync.Mutex c := sync.NewCond(&mu) mu.Lock() go func() { - time.Sleep(blockDelay) + awaitBlockedGoroutine(t, "sync.Cond.Wait", "blockCond") mu.Lock() c.Signal() mu.Unlock() @@ -1144,7 +1175,7 @@ func TestMutexProfile(t *testing.T) { t.Fatalf("need MutexProfileRate 0, got %d", old) } - blockMutex() + blockMutex(t) t.Run("debug=1", func(t *testing.T) { var w bytes.Buffer diff --git a/src/runtime/proc.go b/src/runtime/proc.go index 1be7a60830..b997a467ba 100644 --- a/src/runtime/proc.go +++ b/src/runtime/proc.go @@ -167,10 +167,6 @@ func main() { mainStarted = true if GOARCH != "wasm" { // no threads on wasm yet, so no sysmon - // For runtime_syscall_doAllThreadsSyscall, we - // register sysmon is not ready for the world to be - // stopped. - atomic.Store(&sched.sysmonStarting, 1) systemstack(func() { newm(sysmon, nil, -1) }) @@ -187,7 +183,6 @@ func main() { if g.m != &m0 { throw("runtime.main not on m0") } - m0.doesPark = true // Record when the world started. // Must be before doInit for tracing init. @@ -802,8 +797,18 @@ func mcommoninit(mp *m, id int64) { mp.id = mReserveID() } - // cputicks is not very random in startup virtual machine - mp.fastrand = uint64(int64Hash(uint64(mp.id), fastrandseed^uintptr(cputicks()))) + lo := uint32(int64Hash(uint64(mp.id), fastrandseed)) + hi := uint32(int64Hash(uint64(cputicks()), ^fastrandseed)) + if lo|hi == 0 { + hi = 1 + } + // Same behavior as for 1.17. + // TODO: Simplify ths. + if goarch.BigEndian { + mp.fastrand = uint64(lo)<<32 | uint64(hi) + } else { + mp.fastrand = uint64(hi)<<32 | uint64(lo) + } mpreinit(mp) if mp.gsignal != nil { @@ -1437,22 +1442,12 @@ func mstartm0() { initsig(false) } -// mPark causes a thread to park itself - temporarily waking for -// fixups but otherwise waiting to be fully woken. This is the -// only way that m's should park themselves. +// mPark causes a thread to park itself, returning once woken. //go:nosplit func mPark() { - g := getg() - for { - notesleep(&g.m.park) - // Note, because of signal handling by this parked m, - // a preemptive mDoFixup() may actually occur via - // mDoFixupAndOSYield(). (See golang.org/issue/44193) - noteclear(&g.m.park) - if !mDoFixup() { - return - } - } + gp := getg() + notesleep(&gp.m.park) + noteclear(&gp.m.park) } // mexit tears down and exits the current thread. @@ -1659,145 +1654,6 @@ func forEachP(fn func(*p)) { releasem(mp) } -// syscall_runtime_doAllThreadsSyscall serializes Go execution and -// executes a specified fn() call on all m's. -// -// The boolean argument to fn() indicates whether the function's -// return value will be consulted or not. That is, fn(true) should -// return true if fn() succeeds, and fn(true) should return false if -// it failed. When fn(false) is called, its return status will be -// ignored. -// -// syscall_runtime_doAllThreadsSyscall first invokes fn(true) on a -// single, coordinating, m, and only if it returns true does it go on -// to invoke fn(false) on all of the other m's known to the process. -// -//go:linkname syscall_runtime_doAllThreadsSyscall syscall.runtime_doAllThreadsSyscall -func syscall_runtime_doAllThreadsSyscall(fn func(bool) bool) { - if iscgo { - panic("doAllThreadsSyscall not supported with cgo enabled") - } - if fn == nil { - return - } - for atomic.Load(&sched.sysmonStarting) != 0 { - osyield() - } - - // We don't want this thread to handle signals for the - // duration of this critical section. The underlying issue - // being that this locked coordinating m is the one monitoring - // for fn() execution by all the other m's of the runtime, - // while no regular go code execution is permitted (the world - // is stopped). If this present m were to get distracted to - // run signal handling code, and find itself waiting for a - // second thread to execute go code before being able to - // return from that signal handling, a deadlock will result. - // (See golang.org/issue/44193.) - lockOSThread() - var sigmask sigset - sigsave(&sigmask) - sigblock(false) - - stopTheWorldGC("doAllThreadsSyscall") - if atomic.Load(&newmHandoff.haveTemplateThread) != 0 { - // Ensure that there are no in-flight thread - // creations: don't want to race with allm. - lock(&newmHandoff.lock) - for !newmHandoff.waiting { - unlock(&newmHandoff.lock) - osyield() - lock(&newmHandoff.lock) - } - unlock(&newmHandoff.lock) - } - if netpollinited() { - netpollBreak() - } - sigRecvPrepareForFixup() - _g_ := getg() - if raceenabled { - // For m's running without racectx, we loan out the - // racectx of this call. - lock(&mFixupRace.lock) - mFixupRace.ctx = _g_.racectx - unlock(&mFixupRace.lock) - } - if ok := fn(true); ok { - tid := _g_.m.procid - for mp := allm; mp != nil; mp = mp.alllink { - if mp.procid == tid { - // This m has already completed fn() - // call. - continue - } - // Be wary of mp's without procid values if - // they are known not to park. If they are - // marked as parking with a zero procid, then - // they will be racing with this code to be - // allocated a procid and we will annotate - // them with the need to execute the fn when - // they acquire a procid to run it. - if mp.procid == 0 && !mp.doesPark { - // Reaching here, we are either - // running Windows, or cgo linked - // code. Neither of which are - // currently supported by this API. - throw("unsupported runtime environment") - } - // stopTheWorldGC() doesn't guarantee stopping - // all the threads, so we lock here to avoid - // the possibility of racing with mp. - lock(&mp.mFixup.lock) - mp.mFixup.fn = fn - atomic.Store(&mp.mFixup.used, 1) - if mp.doesPark { - // For non-service threads this will - // cause the wakeup to be short lived - // (once the mutex is unlocked). The - // next real wakeup will occur after - // startTheWorldGC() is called. - notewakeup(&mp.park) - } - unlock(&mp.mFixup.lock) - } - for { - done := true - for mp := allm; done && mp != nil; mp = mp.alllink { - if mp.procid == tid { - continue - } - done = atomic.Load(&mp.mFixup.used) == 0 - } - if done { - break - } - // if needed force sysmon and/or newmHandoff to wakeup. - lock(&sched.lock) - if atomic.Load(&sched.sysmonwait) != 0 { - atomic.Store(&sched.sysmonwait, 0) - notewakeup(&sched.sysmonnote) - } - unlock(&sched.lock) - lock(&newmHandoff.lock) - if newmHandoff.waiting { - newmHandoff.waiting = false - notewakeup(&newmHandoff.wake) - } - unlock(&newmHandoff.lock) - osyield() - } - } - if raceenabled { - lock(&mFixupRace.lock) - mFixupRace.ctx = 0 - unlock(&mFixupRace.lock) - } - startTheWorldGC() - msigrestore(sigmask) - unlockOSThread() -} - // runSafePointFn runs the safe point function, if any, for this P. // This should be called like // @@ -1847,8 +1703,14 @@ type cgothreadstart struct { // //go:yeswritebarrierrec func allocm(_p_ *p, fn func(), id int64) *m { + allocmLock.rlock() + + // The caller owns _p_, but we may borrow (i.e., acquirep) it. We must + // disable preemption to ensure it is not stolen, which would make the + // caller lose ownership. + acquirem() + _g_ := getg() - acquirem() // disable GC because it can be called from sysmon if _g_.m.p == 0 { acquirep(_p_) // temporarily borrow p for mallocs in this function } @@ -1894,8 +1756,9 @@ func allocm(_p_ *p, fn func(), id int64) *m { if _p_ == _g_.m.p.ptr() { releasep() } - releasem(_g_.m) + releasem(_g_.m) + allocmLock.runlock() return mp } @@ -2172,9 +2035,17 @@ func unlockextra(mp *m) { atomic.Storeuintptr(&extram, uintptr(unsafe.Pointer(mp))) } -// execLock serializes exec and clone to avoid bugs or unspecified behaviour -// around exec'ing while creating/destroying threads. See issue #19546. -var execLock rwmutex +var ( + // allocmLock is locked for read when creating new Ms in allocm and their + // addition to allm. Thus acquiring this lock for write blocks the + // creation of new Ms. + allocmLock rwmutex + + // execLock serializes exec and clone to avoid bugs or unspecified + // behaviour around exec'ing while creating/destroying threads. See + // issue #19546. + execLock rwmutex +) // newmHandoff contains a list of m structures that need new OS threads. // This is used by newm in situations where newm itself can't safely @@ -2204,8 +2075,19 @@ var newmHandoff struct { // id is optional pre-allocated m ID. Omit by passing -1. //go:nowritebarrierrec func newm(fn func(), _p_ *p, id int64) { + // allocm adds a new M to allm, but they do not start until created by + // the OS in newm1 or the template thread. + // + // doAllThreadsSyscall requires that every M in allm will eventually + // start and be signal-able, even with a STW. + // + // Disable preemption here until we start the thread to ensure that + // newm is not preempted between allocm and starting the new thread, + // ensuring that anything added to allm is guaranteed to eventually + // start. + acquirem() + mp := allocm(_p_, fn, id) - mp.doesPark = (_p_ != nil) mp.nextp.set(_p_) mp.sigmask = initSigmask if gp := getg(); gp != nil && gp.m != nil && (gp.m.lockedExt != 0 || gp.m.incgo) && GOOS != "plan9" { @@ -2231,9 +2113,14 @@ func newm(fn func(), _p_ *p, id int64) { notewakeup(&newmHandoff.wake) } unlock(&newmHandoff.lock) + // The M has not started yet, but the template thread does not + // participate in STW, so it will always process queued Ms and + // it is safe to releasem. + releasem(getg().m) return } newm1(mp) + releasem(getg().m) } func newm1(mp *m) { @@ -2281,81 +2168,6 @@ func startTemplateThread() { releasem(mp) } -// mFixupRace is used to temporarily borrow the race context from the -// coordinating m during a syscall_runtime_doAllThreadsSyscall and -// loan it out to each of the m's of the runtime so they can execute a -// mFixup.fn in that context. -var mFixupRace struct { - lock mutex - ctx uintptr -} - -// mDoFixup runs any outstanding fixup function for the running m. -// Returns true if a fixup was outstanding and actually executed. -// -// Note: to avoid deadlocks, and the need for the fixup function -// itself to be async safe, signals are blocked for the working m -// while it holds the mFixup lock. (See golang.org/issue/44193) -// -//go:nosplit -func mDoFixup() bool { - _g_ := getg() - if used := atomic.Load(&_g_.m.mFixup.used); used == 0 { - return false - } - - // slow path - if fixup fn is used, block signals and lock. - var sigmask sigset - sigsave(&sigmask) - sigblock(false) - lock(&_g_.m.mFixup.lock) - fn := _g_.m.mFixup.fn - if fn != nil { - if gcphase != _GCoff { - // We can't have a write barrier in this - // context since we may not have a P, but we - // clear fn to signal that we've executed the - // fixup. As long as fn is kept alive - // elsewhere, technically we should have no - // issues with the GC, but fn is likely - // generated in a different package altogether - // that may change independently. Just assert - // the GC is off so this lack of write barrier - // is more obviously safe. - throw("GC must be disabled to protect validity of fn value") - } - if _g_.racectx != 0 || !raceenabled { - fn(false) - } else { - // temporarily acquire the context of the - // originator of the - // syscall_runtime_doAllThreadsSyscall and - // block others from using it for the duration - // of the fixup call. - lock(&mFixupRace.lock) - _g_.racectx = mFixupRace.ctx - fn(false) - _g_.racectx = 0 - unlock(&mFixupRace.lock) - } - *(*uintptr)(unsafe.Pointer(&_g_.m.mFixup.fn)) = 0 - atomic.Store(&_g_.m.mFixup.used, 0) - } - unlock(&_g_.m.mFixup.lock) - msigrestore(sigmask) - return fn != nil -} - -// mDoFixupAndOSYield is called when an m is unable to send a signal -// because the allThreadsSyscall mechanism is in progress. That is, an -// mPark() has been interrupted with this signal handler so we need to -// ensure the fixup is executed from this context. -//go:nosplit -func mDoFixupAndOSYield() { - mDoFixup() - osyield() -} - // templateThread is a thread in a known-good state that exists solely // to start new threads in known-good states when the calling thread // may not be in a good state. @@ -2392,7 +2204,6 @@ func templateThread() { noteclear(&newmHandoff.wake) unlock(&newmHandoff.lock) notesleep(&newmHandoff.wake) - mDoFixup() } } @@ -5239,10 +5050,6 @@ func sysmon() { checkdead() unlock(&sched.lock) - // For syscall_runtime_doAllThreadsSyscall, sysmon is - // sufficiently up to participate in fixups. - atomic.Store(&sched.sysmonStarting, 0) - lasttrace := int64(0) idle := 0 // how many cycles in succession we had not wokeup somebody delay := uint32(0) @@ -5257,7 +5064,6 @@ func sysmon() { delay = 10 * 1000 } usleep(delay) - mDoFixup() // sysmon should not enter deep sleep if schedtrace is enabled so that // it can print that information at the right time. @@ -5294,7 +5100,6 @@ func sysmon() { osRelax(true) } syscallWake = notetsleep(&sched.sysmonnote, sleep) - mDoFixup() if shouldRelax { osRelax(false) } @@ -5337,7 +5142,6 @@ func sysmon() { incidlelocked(1) } } - mDoFixup() if GOOS == "netbsd" && needSysmonWorkaround { // netpoll is responsible for waiting for timer // expiration, so we typically don't have to worry diff --git a/src/runtime/race_arm64.s b/src/runtime/race_arm64.s index 798e23294a..95fec0b9c6 100644 --- a/src/runtime/race_arm64.s +++ b/src/runtime/race_arm64.s @@ -188,8 +188,12 @@ ret: // func runtime·racefuncenter(pc uintptr) // Called from instrumented code. -TEXT runtime·racefuncenter(SB), NOSPLIT, $0-8 +TEXT runtime·racefuncenter(SB), NOSPLIT, $0-8 +#ifdef GOEXPERIMENT_regabiargs + MOVD R0, R9 // callpc +#else MOVD callpc+0(FP), R9 +#endif JMP racefuncenter<>(SB) // Common code for racefuncenter @@ -205,7 +209,7 @@ TEXT racefuncenter<>(SB), NOSPLIT, $0-0 // func runtime·racefuncexit() // Called from instrumented code. -TEXT runtime·racefuncexit(SB), NOSPLIT, $0-0 +TEXT runtime·racefuncexit(SB), NOSPLIT, $0-0 load_g MOVD g_racectx(g), R0 // race context // void __tsan_func_exit(ThreadState *thr); @@ -392,12 +396,12 @@ racecallatomic_ignore: // Addr is outside the good range. // Call __tsan_go_ignore_sync_begin to ignore synchronization during the atomic op. // An attempt to synchronize on the address would cause crash. - MOVD R9, R20 // remember the original function + MOVD R9, R21 // remember the original function MOVD $__tsan_go_ignore_sync_begin(SB), R9 load_g MOVD g_racectx(g), R0 // goroutine context BL racecall<>(SB) - MOVD R20, R9 // restore the original function + MOVD R21, R9 // restore the original function // Call the atomic function. // racecall will call LLVM race code which might clobber R28 (g) load_g @@ -424,10 +428,12 @@ TEXT runtime·racecall(SB), NOSPLIT, $0-0 JMP racecall<>(SB) // Switches SP to g0 stack and calls (R9). Arguments already set. -TEXT racecall<>(SB), NOSPLIT, $0-0 +// Clobbers R19, R20. +TEXT racecall<>(SB), NOSPLIT|NOFRAME, $0-0 MOVD g_m(g), R10 // Switch to g0 stack. MOVD RSP, R19 // callee-saved, preserved across the CALL + MOVD R30, R20 // callee-saved, preserved across the CALL MOVD m_g0(R10), R11 CMP R11, g BEQ call // already on g0 @@ -436,7 +442,7 @@ TEXT racecall<>(SB), NOSPLIT, $0-0 call: BL R9 MOVD R19, RSP - RET + JMP (R20) // C->Go callback thunk that allows to call runtime·racesymbolize from C code. // Direct Go->C race call has only switched SP, finish g->g0 switch by setting correct g. diff --git a/src/runtime/runtime-gdb_test.go b/src/runtime/runtime-gdb_test.go index 7e8723e15f..ee8c6c210f 100644 --- a/src/runtime/runtime-gdb_test.go +++ b/src/runtime/runtime-gdb_test.go @@ -427,6 +427,14 @@ func TestGdbBacktrace(t *testing.T) { got, err := testenv.RunWithTimeout(t, exec.Command("gdb", args...)) t.Logf("gdb output:\n%s", got) if err != nil { + if bytes.Contains(got, []byte("internal-error: wait returned unexpected status 0x0")) { + // GDB bug: https://sourceware.org/bugzilla/show_bug.cgi?id=28551 + testenv.SkipFlaky(t, 43068) + } + if bytes.Contains(got, []byte("Couldn't get registers: No such process.")) { + // GDB bug: https://sourceware.org/bugzilla/show_bug.cgi?id=9086 + testenv.SkipFlaky(t, 50838) + } t.Fatalf("gdb exited with error: %v", err) } diff --git a/src/runtime/runtime2.go b/src/runtime/runtime2.go index 3eada37840..3d01ac5171 100644 --- a/src/runtime/runtime2.go +++ b/src/runtime/runtime2.go @@ -547,7 +547,6 @@ type m struct { ncgo int32 // number of cgo calls currently in progress cgoCallersUse uint32 // if non-zero, cgoCallers in use temporarily cgoCallers *cgoCallers // cgo traceback if crashing in cgo call - doesPark bool // non-P running threads: sysmon and newmHandoff never use .park park note alllink *m // on allm schedlink muintptr @@ -564,16 +563,6 @@ type m struct { syscalltick uint32 freelink *m // on sched.freem - // mFixup is used to synchronize OS related m state - // (credentials etc) use mutex to access. To avoid deadlocks - // an atomic.Load() of used being zero in mDoFixupFn() - // guarantees fn is nil. - mFixup struct { - lock mutex - used uint32 - fn func(bool) bool - } - // these are here because they are too large to be on the stack // of low-level NOSPLIT functions. libcall libcall @@ -817,10 +806,6 @@ type schedt struct { sysmonwait uint32 sysmonnote note - // While true, sysmon not ready for mFixup calls. - // Accessed atomically. - sysmonStarting uint32 - // safepointFn should be called on each P at the next GC // safepoint if p.runSafePointFn is set. safePointFn func(*p) @@ -838,8 +823,6 @@ type schedt struct { // with the rest of the runtime. sysmonlock mutex - _ uint32 // ensure timeToRun has 8-byte alignment - // timeToRun is a distribution of scheduling latencies, defined // as the sum of time a G spends in the _Grunnable state before // it transitions to _Grunning. @@ -856,7 +839,7 @@ const ( _SigPanic // if the signal is from the kernel, panic _SigDefault // if the signal isn't explicitly requested, don't monitor it _SigGoExit // cause all runtime procs to exit (only used on Plan 9). - _SigSetStack // add SA_ONSTACK to libc handler + _SigSetStack // Don't explicitly install handler, but add SA_ONSTACK to existing libc handler _SigUnblock // always unblock; see blockableSig _SigIgn // _SIG_DFL action is to ignore the signal ) diff --git a/src/runtime/signal_unix.go b/src/runtime/signal_unix.go index 08f266cc67..2dd4cc51a3 100644 --- a/src/runtime/signal_unix.go +++ b/src/runtime/signal_unix.go @@ -161,6 +161,13 @@ func sigInstallGoHandler(sig uint32) bool { } } + if (GOOS == "linux" || GOOS == "android") && !iscgo && sig == sigPerThreadSyscall { + // sigPerThreadSyscall is the same signal used by glibc for + // per-thread syscalls on Linux. We use it for the same purpose + // in non-cgo binaries. + return true + } + t := &sigtable[sig] if t.flags&_SigSetStack != 0 { return false @@ -616,6 +623,15 @@ func sighandler(sig uint32, info *siginfo, ctxt unsafe.Pointer, gp *g) { return } + if (GOOS == "linux" || GOOS == "android") && sig == sigPerThreadSyscall { + // sigPerThreadSyscall is the same signal used by glibc for + // per-thread syscalls on Linux. We use it for the same purpose + // in non-cgo binaries. Since this signal is not _SigNotify, + // there is nothing more to do once we run the syscall. + runPerThreadSyscall() + return + } + if sig == sigPreempt && debug.asyncpreemptoff == 0 { // Might be a preemption signal. doSigPreempt(gp, c) diff --git a/src/runtime/sigqueue.go b/src/runtime/sigqueue.go index 7b84a0ef65..fdf99d94a2 100644 --- a/src/runtime/sigqueue.go +++ b/src/runtime/sigqueue.go @@ -11,18 +11,18 @@ // // sigsend is called by the signal handler to queue a new signal. // signal_recv is called by the Go program to receive a newly queued signal. +// // Synchronization between sigsend and signal_recv is based on the sig.state -// variable. It can be in 4 states: sigIdle, sigReceiving, sigSending and sigFixup. -// sigReceiving means that signal_recv is blocked on sig.Note and there are no -// new pending signals. -// sigSending means that sig.mask *may* contain new pending signals, -// signal_recv can't be blocked in this state. -// sigIdle means that there are no new pending signals and signal_recv is not blocked. -// sigFixup is a transient state that can only exist as a short -// transition from sigReceiving and then on to sigIdle: it is -// used to ensure the AllThreadsSyscall()'s mDoFixup() operation -// occurs on the sleeping m, waiting to receive a signal. +// variable. It can be in three states: +// * sigReceiving means that signal_recv is blocked on sig.Note and there are +// no new pending signals. +// * sigSending means that sig.mask *may* contain new pending signals, +// signal_recv can't be blocked in this state. +// * sigIdle means that there are no new pending signals and signal_recv is not +// blocked. +// // Transitions between states are done atomically with CAS. +// // When signal_recv is unblocked, it resets sig.Note and rechecks sig.mask. // If several sigsends and signal_recv execute concurrently, it can lead to // unnecessary rechecks of sig.mask, but it cannot lead to missed signals @@ -63,7 +63,6 @@ const ( sigIdle = iota sigReceiving sigSending - sigFixup ) // sigsend delivers a signal from sighandler to the internal signal delivery queue. @@ -117,9 +116,6 @@ Send: notewakeup(&sig.note) break Send } - case sigFixup: - // nothing to do - we need to wait for sigIdle. - mDoFixupAndOSYield() } } @@ -127,19 +123,6 @@ Send: return true } -// sigRecvPrepareForFixup is used to temporarily wake up the -// signal_recv() running thread while it is blocked waiting for the -// arrival of a signal. If it causes the thread to wake up, the -// sig.state travels through this sequence: sigReceiving -> sigFixup -// -> sigIdle -> sigReceiving and resumes. (This is only called while -// GC is disabled.) -//go:nosplit -func sigRecvPrepareForFixup() { - if atomic.Cas(&sig.state, sigReceiving, sigFixup) { - notewakeup(&sig.note) - } -} - // Called to receive the next queued signal. // Must only be called from a single goroutine at a time. //go:linkname signal_recv os/signal.signal_recv @@ -167,16 +150,7 @@ func signal_recv() uint32 { } notetsleepg(&sig.note, -1) noteclear(&sig.note) - if !atomic.Cas(&sig.state, sigFixup, sigIdle) { - break Receive - } - // Getting here, the code will - // loop around again to sleep - // in state sigReceiving. This - // path is taken when - // sigRecvPrepareForFixup() - // has been called by another - // thread. + break Receive } case sigSending: if atomic.Cas(&sig.state, sigSending, sigIdle) { diff --git a/src/runtime/sigqueue_plan9.go b/src/runtime/sigqueue_plan9.go index aebd2060e7..d5fe8f8b35 100644 --- a/src/runtime/sigqueue_plan9.go +++ b/src/runtime/sigqueue_plan9.go @@ -92,13 +92,6 @@ func sendNote(s *byte) bool { return true } -// sigRecvPrepareForFixup is a no-op on plan9. (This would only be -// called while GC is disabled.) -// -//go:nosplit -func sigRecvPrepareForFixup() { -} - // Called to receive the next queued signal. // Must only be called from a single goroutine at a time. //go:linkname signal_recv os/signal.signal_recv diff --git a/src/runtime/symtab.go b/src/runtime/symtab.go index 017b0a0749..ee4db47314 100644 --- a/src/runtime/symtab.go +++ b/src/runtime/symtab.go @@ -310,6 +310,7 @@ const ( _FUNCDATA_OpenCodedDeferInfo = 4 _FUNCDATA_ArgInfo = 5 _FUNCDATA_ArgLiveInfo = 6 + _FUNCDATA_WrapInfo = 7 _ArgsSizeUnknown = -0x80000000 ) diff --git a/src/runtime/sys_darwin.go b/src/runtime/sys_darwin.go index 80dd1a0378..58b3a9171c 100644 --- a/src/runtime/sys_darwin.go +++ b/src/runtime/sys_darwin.go @@ -17,87 +17,91 @@ import ( //go:linkname syscall_syscall syscall.syscall //go:nosplit -//go:cgo_unsafe_args func syscall_syscall(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) { + args := struct{ fn, a1, a2, a3, r1, r2, err uintptr }{fn, a1, a2, a3, r1, r2, err} entersyscall() - libcCall(unsafe.Pointer(abi.FuncPCABI0(syscall)), unsafe.Pointer(&fn)) + libcCall(unsafe.Pointer(abi.FuncPCABI0(syscall)), unsafe.Pointer(&args)) exitsyscall() - return + return args.r1, args.r2, args.err } func syscall() //go:linkname syscall_syscallX syscall.syscallX //go:nosplit -//go:cgo_unsafe_args func syscall_syscallX(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) { + args := struct{ fn, a1, a2, a3, r1, r2, err uintptr }{fn, a1, a2, a3, r1, r2, err} entersyscall() - libcCall(unsafe.Pointer(abi.FuncPCABI0(syscallX)), unsafe.Pointer(&fn)) + libcCall(unsafe.Pointer(abi.FuncPCABI0(syscallX)), unsafe.Pointer(&args)) exitsyscall() - return + return args.r1, args.r2, args.err } func syscallX() //go:linkname syscall_syscall6 syscall.syscall6 //go:nosplit -//go:cgo_unsafe_args func syscall_syscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr) { + args := struct{ fn, a1, a2, a3, a4, a5, a6, r1, r2, err uintptr }{fn, a1, a2, a3, a4, a5, a6, r1, r2, err} entersyscall() - libcCall(unsafe.Pointer(abi.FuncPCABI0(syscall6)), unsafe.Pointer(&fn)) + libcCall(unsafe.Pointer(abi.FuncPCABI0(syscall6)), unsafe.Pointer(&args)) exitsyscall() - return + return args.r1, args.r2, args.err } func syscall6() //go:linkname syscall_syscall6X syscall.syscall6X //go:nosplit -//go:cgo_unsafe_args func syscall_syscall6X(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr) { + args := struct{ fn, a1, a2, a3, a4, a5, a6, r1, r2, err uintptr }{fn, a1, a2, a3, a4, a5, a6, r1, r2, err} entersyscall() - libcCall(unsafe.Pointer(abi.FuncPCABI0(syscall6X)), unsafe.Pointer(&fn)) + libcCall(unsafe.Pointer(abi.FuncPCABI0(syscall6X)), unsafe.Pointer(&args)) exitsyscall() - return + return args.r1, args.r2, args.err } func syscall6X() //go:linkname syscall_syscallPtr syscall.syscallPtr //go:nosplit -//go:cgo_unsafe_args func syscall_syscallPtr(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) { + args := struct{ fn, a1, a2, a3, r1, r2, err uintptr }{fn, a1, a2, a3, r1, r2, err} entersyscall() - libcCall(unsafe.Pointer(abi.FuncPCABI0(syscallPtr)), unsafe.Pointer(&fn)) + libcCall(unsafe.Pointer(abi.FuncPCABI0(syscallPtr)), unsafe.Pointer(&args)) exitsyscall() - return + return args.r1, args.r2, args.err } func syscallPtr() //go:linkname syscall_rawSyscall syscall.rawSyscall //go:nosplit -//go:cgo_unsafe_args func syscall_rawSyscall(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) { - libcCall(unsafe.Pointer(abi.FuncPCABI0(syscall)), unsafe.Pointer(&fn)) - return + args := struct{ fn, a1, a2, a3, r1, r2, err uintptr }{fn, a1, a2, a3, r1, r2, err} + libcCall(unsafe.Pointer(abi.FuncPCABI0(syscall)), unsafe.Pointer(&args)) + return args.r1, args.r2, args.err } //go:linkname syscall_rawSyscall6 syscall.rawSyscall6 //go:nosplit -//go:cgo_unsafe_args func syscall_rawSyscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr) { - libcCall(unsafe.Pointer(abi.FuncPCABI0(syscall6)), unsafe.Pointer(&fn)) - return + args := struct{ fn, a1, a2, a3, a4, a5, a6, r1, r2, err uintptr }{fn, a1, a2, a3, a4, a5, a6, r1, r2, err} + libcCall(unsafe.Pointer(abi.FuncPCABI0(syscall6)), unsafe.Pointer(&args)) + return args.r1, args.r2, args.err } // syscallNoErr is used in crypto/x509 to call into Security.framework and CF. //go:linkname crypto_x509_syscall crypto/x509/internal/macos.syscall //go:nosplit -//go:cgo_unsafe_args -func crypto_x509_syscall(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1 uintptr) { +func crypto_x509_syscall(fn, a1, a2, a3, a4, a5 uintptr, f1 float64) (r1 uintptr) { + args := struct { + fn, a1, a2, a3, a4, a5 uintptr + f1 float64 + r1 uintptr + }{fn, a1, a2, a3, a4, a5, f1, r1} entersyscall() - libcCall(unsafe.Pointer(abi.FuncPCABI0(syscallNoErr)), unsafe.Pointer(&fn)) + libcCall(unsafe.Pointer(abi.FuncPCABI0(syscall_x509)), unsafe.Pointer(&args)) exitsyscall() - return + return args.r1 } -func syscallNoErr() +func syscall_x509() // The *_trampoline functions convert from the Go calling convention to the C calling convention // and then call the underlying libc function. They are defined in sys_darwin_$ARCH.s. diff --git a/src/runtime/sys_darwin_amd64.s b/src/runtime/sys_darwin_amd64.s index 5d89cda8e6..db4715d2b7 100644 --- a/src/runtime/sys_darwin_amd64.s +++ b/src/runtime/sys_darwin_amd64.s @@ -831,9 +831,10 @@ ok: POPQ BP RET -// syscallNoErr is like syscall6 but does not check for errors, and -// only returns one value, for use with standard C ABI library functions. -TEXT runtime·syscallNoErr(SB),NOSPLIT,$0 +// syscall_x509 is for crypto/x509. It is like syscall6 but does not check for errors, +// takes 5 uintptrs and 1 float64, and only returns one value, +// for use with standard C ABI functions. +TEXT runtime·syscall_x509(SB),NOSPLIT,$0 PUSHQ BP MOVQ SP, BP SUBQ $16, SP @@ -842,7 +843,7 @@ TEXT runtime·syscallNoErr(SB),NOSPLIT,$0 MOVQ (3*8)(DI), DX // a3 MOVQ (4*8)(DI), CX // a4 MOVQ (5*8)(DI), R8 // a5 - MOVQ (6*8)(DI), R9 // a6 + MOVQ (6*8)(DI), X0 // f1 MOVQ DI, (SP) MOVQ (1*8)(DI), DI // a1 XORL AX, AX // vararg: say "no float args" diff --git a/src/runtime/sys_darwin_arm64.s b/src/runtime/sys_darwin_arm64.s index 96d2ed1076..e57ac53e10 100644 --- a/src/runtime/sys_darwin_arm64.s +++ b/src/runtime/sys_darwin_arm64.s @@ -736,9 +736,10 @@ TEXT runtime·syscall6X(SB),NOSPLIT,$0 ok: RET -// syscallNoErr is like syscall6 but does not check for errors, and -// only returns one value, for use with standard C ABI library functions. -TEXT runtime·syscallNoErr(SB),NOSPLIT,$0 +// syscall_x509 is for crypto/x509. It is like syscall6 but does not check for errors, +// takes 5 uintptrs and 1 float64, and only returns one value, +// for use with standard C ABI functions. +TEXT runtime·syscall_x509(SB),NOSPLIT,$0 SUB $16, RSP // push structure pointer MOVD R0, (RSP) @@ -747,7 +748,7 @@ TEXT runtime·syscallNoErr(SB),NOSPLIT,$0 MOVD 24(R0), R2 // a3 MOVD 32(R0), R3 // a4 MOVD 40(R0), R4 // a5 - MOVD 48(R0), R5 // a6 + FMOVD 48(R0), F0 // f1 MOVD 8(R0), R0 // a1 BL (R12) diff --git a/src/runtime/testdata/testprogcgo/aprof.go b/src/runtime/testdata/testprogcgo/aprof.go index c70d6333bb..16870144dd 100644 --- a/src/runtime/testdata/testprogcgo/aprof.go +++ b/src/runtime/testdata/testprogcgo/aprof.go @@ -10,7 +10,7 @@ package main // This is a regression test for issue 14599, where profiling fails when the // function is the first C function. Exported functions are the first C // functions, so we use an exported function. Exported functions are created in -// lexigraphical order of source files, so this file is named aprof.go to +// lexicographical order of source files, so this file is named aprof.go to // ensure its function is first. // extern void CallGoNop(); diff --git a/src/runtime/trace.go b/src/runtime/trace.go index 71a29d4316..8f60de2b05 100644 --- a/src/runtime/trace.go +++ b/src/runtime/trace.go @@ -229,7 +229,7 @@ func StartTrace() error { gp.traceseq = 0 gp.tracelastp = getg().m.p // +PCQuantum because traceFrameForPC expects return PCs and subtracts PCQuantum. - id := trace.stackTab.put([]uintptr{gp.startpc + sys.PCQuantum}) + id := trace.stackTab.put([]uintptr{startPCforTrace(gp.startpc) + sys.PCQuantum}) traceEvent(traceEvGoCreate, -1, uint64(gp.goid), uint64(id), stackID) } if status == _Gwaiting { @@ -1071,7 +1071,7 @@ func traceGoCreate(newg *g, pc uintptr) { newg.traceseq = 0 newg.tracelastp = getg().m.p // +PCQuantum because traceFrameForPC expects return PCs and subtracts PCQuantum. - id := trace.stackTab.put([]uintptr{pc + sys.PCQuantum}) + id := trace.stackTab.put([]uintptr{startPCforTrace(pc) + sys.PCQuantum}) traceEvent(traceEvGoCreate, 2, uint64(newg.goid), uint64(id)) } @@ -1244,3 +1244,17 @@ func trace_userLog(id uint64, category, message string) { traceReleaseBuffer(pid) } + +// the start PC of a goroutine for tracing purposes. If pc is a wrapper, +// it returns the PC of the wrapped function. Otherwise it returns pc. +func startPCforTrace(pc uintptr) uintptr { + f := findfunc(pc) + if !f.valid() { + return pc // should not happen, but don't care + } + w := funcdata(f, _FUNCDATA_WrapInfo) + if w == nil { + return pc // not a wrapper + } + return f.datap.textAddr(*(*uint32)(w)) +} diff --git a/src/strings/builder.go b/src/strings/builder.go index 547e52e84d..ba4df618bf 100644 --- a/src/strings/builder.go +++ b/src/strings/builder.go @@ -17,10 +17,9 @@ type Builder struct { buf []byte } -// noescape hides a pointer from escape analysis. noescape is -// the identity function but escape analysis doesn't think the -// output depends on the input. noescape is inlined and currently -// compiles down to zero instructions. +// noescape hides a pointer from escape analysis. It is the identity function +// but escape analysis doesn't think the output depends on the input. +// noescape is inlined and currently compiles down to zero instructions. // USE CAREFULLY! // This was copied from the runtime; see issues 23382 and 7921. //go:nosplit diff --git a/src/strings/strings.go b/src/strings/strings.go index c5a29e95f6..5793d9e26f 100644 --- a/src/strings/strings.go +++ b/src/strings/strings.go @@ -270,6 +270,8 @@ func genSplit(s, sep string, sepSave, n int) []string { // // Edge cases for s and sep (for example, empty strings) are handled // as described in the documentation for Split. +// +// To split around the first instance of a separator, see Cut. func SplitN(s, sep string, n int) []string { return genSplit(s, sep, 0, n) } // SplitAfterN slices s into substrings after each instance of sep and @@ -296,6 +298,8 @@ func SplitAfterN(s, sep string, n int) []string { // and sep are empty, Split returns an empty slice. // // It is equivalent to SplitN with a count of -1. +// +// To split around the first instance of a separator, see Cut. func Split(s, sep string) []string { return genSplit(s, sep, 0, -1) } // SplitAfter slices s into all substrings after each instance of sep and diff --git a/src/syscall/syscall_linux.go b/src/syscall/syscall_linux.go index abcf1d5dfe..e3891b0855 100644 --- a/src/syscall/syscall_linux.go +++ b/src/syscall/syscall_linux.go @@ -958,62 +958,11 @@ func Getpgrp() (pid int) { //sysnb Setsid() (pid int, err error) //sysnb Settimeofday(tv *Timeval) (err error) -// allThreadsCaller holds the input and output state for performing a -// allThreadsSyscall that needs to synchronize all OS thread state. Linux -// generally does not always support this natively, so we have to -// manipulate the runtime to fix things up. -type allThreadsCaller struct { - // arguments - trap, a1, a2, a3, a4, a5, a6 uintptr - - // return values (only set by 0th invocation) - r1, r2 uintptr - - // err is the error code - err Errno -} - -// doSyscall is a callback for executing a syscall on the current m -// (OS thread). -//go:nosplit -//go:norace -func (pc *allThreadsCaller) doSyscall(initial bool) bool { - r1, r2, err := RawSyscall(pc.trap, pc.a1, pc.a2, pc.a3) - if initial { - pc.r1 = r1 - pc.r2 = r2 - pc.err = err - } else if pc.r1 != r1 || (archHonorsR2 && pc.r2 != r2) || pc.err != err { - print("trap:", pc.trap, ", a123=[", pc.a1, ",", pc.a2, ",", pc.a3, "]\n") - print("results: got {r1=", r1, ",r2=", r2, ",err=", err, "}, want {r1=", pc.r1, ",r2=", pc.r2, ",r3=", pc.err, "}\n") - panic("AllThreadsSyscall results differ between threads; runtime corrupted") - } - return err == 0 -} - -// doSyscall6 is a callback for executing a syscall6 on the current m -// (OS thread). -//go:nosplit -//go:norace -func (pc *allThreadsCaller) doSyscall6(initial bool) bool { - r1, r2, err := RawSyscall6(pc.trap, pc.a1, pc.a2, pc.a3, pc.a4, pc.a5, pc.a6) - if initial { - pc.r1 = r1 - pc.r2 = r2 - pc.err = err - } else if pc.r1 != r1 || (archHonorsR2 && pc.r2 != r2) || pc.err != err { - print("trap:", pc.trap, ", a123456=[", pc.a1, ",", pc.a2, ",", pc.a3, ",", pc.a4, ",", pc.a5, ",", pc.a6, "]\n") - print("results: got {r1=", r1, ",r2=", r2, ",err=", err, "}, want {r1=", pc.r1, ",r2=", pc.r2, ",r3=", pc.err, "}\n") - panic("AllThreadsSyscall6 results differ between threads; runtime corrupted") - } - return err == 0 -} - -// Provided by runtime.syscall_runtime_doAllThreadsSyscall which -// serializes the world and invokes the fn on each OS thread (what the -// runtime refers to as m's). Once this function returns, all threads -// are in sync. -func runtime_doAllThreadsSyscall(fn func(bool) bool) +// Provided by runtime.syscall_runtime_doAllThreadsSyscall which stops the +// world and invokes the syscall on each OS thread. Once this function returns, +// all threads are in sync. +//go:uintptrescapes +func runtime_doAllThreadsSyscall(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr) // AllThreadsSyscall performs a syscall on each OS thread of the Go // runtime. It first invokes the syscall on one thread. Should that @@ -1035,17 +984,8 @@ func AllThreadsSyscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno) { if cgo_libc_setegid != nil { return minus1, minus1, ENOTSUP } - pc := &allThreadsCaller{ - trap: trap, - a1: a1, - a2: a2, - a3: a3, - } - runtime_doAllThreadsSyscall(pc.doSyscall) - r1 = pc.r1 - r2 = pc.r2 - err = pc.err - return + r1, r2, errno := runtime_doAllThreadsSyscall(trap, a1, a2, a3, 0, 0, 0) + return r1, r2, Errno(errno) } // AllThreadsSyscall6 is like AllThreadsSyscall, but extended to six @@ -1055,20 +995,8 @@ func AllThreadsSyscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, e if cgo_libc_setegid != nil { return minus1, minus1, ENOTSUP } - pc := &allThreadsCaller{ - trap: trap, - a1: a1, - a2: a2, - a3: a3, - a4: a4, - a5: a5, - a6: a6, - } - runtime_doAllThreadsSyscall(pc.doSyscall6) - r1 = pc.r1 - r2 = pc.r2 - err = pc.err - return + r1, r2, errno := runtime_doAllThreadsSyscall(trap, a1, a2, a3, a4, a5, a6) + return r1, r2, Errno(errno) } // linked by runtime.cgocall.go diff --git a/src/syscall/syscall_linux_386.go b/src/syscall/syscall_linux_386.go index 98442055d8..a3a5870a17 100644 --- a/src/syscall/syscall_linux_386.go +++ b/src/syscall/syscall_linux_386.go @@ -6,12 +6,6 @@ package syscall import "unsafe" -// archHonorsR2 captures the fact that r2 is honored by the -// runtime.GOARCH. Syscall conventions are generally r1, r2, err := -// syscall(trap, ...). Not all architectures define r2 in their -// ABI. See "man syscall". -const archHonorsR2 = true - const _SYS_setgroups = SYS_SETGROUPS32 func setTimespec(sec, nsec int64) Timespec { diff --git a/src/syscall/syscall_linux_amd64.go b/src/syscall/syscall_linux_amd64.go index 04acd063fa..26b40ffe9b 100644 --- a/src/syscall/syscall_linux_amd64.go +++ b/src/syscall/syscall_linux_amd64.go @@ -4,12 +4,6 @@ package syscall -// archHonorsR2 captures the fact that r2 is honored by the -// runtime.GOARCH. Syscall conventions are generally r1, r2, err := -// syscall(trap, ...). Not all architectures define r2 in their -// ABI. See "man syscall". -const archHonorsR2 = true - const _SYS_setgroups = SYS_SETGROUPS //sys Dup2(oldfd int, newfd int) (err error) diff --git a/src/syscall/syscall_linux_arm.go b/src/syscall/syscall_linux_arm.go index f2f342e7ed..58f376f350 100644 --- a/src/syscall/syscall_linux_arm.go +++ b/src/syscall/syscall_linux_arm.go @@ -6,12 +6,6 @@ package syscall import "unsafe" -// archHonorsR2 captures the fact that r2 is honored by the -// runtime.GOARCH. Syscall conventions are generally r1, r2, err := -// syscall(trap, ...). Not all architectures define r2 in their -// ABI. See "man syscall". [EABI assumed.] -const archHonorsR2 = true - const _SYS_setgroups = SYS_SETGROUPS32 func setTimespec(sec, nsec int64) Timespec { diff --git a/src/syscall/syscall_linux_arm64.go b/src/syscall/syscall_linux_arm64.go index 990e732f35..f3c6c48d06 100644 --- a/src/syscall/syscall_linux_arm64.go +++ b/src/syscall/syscall_linux_arm64.go @@ -6,12 +6,6 @@ package syscall import "unsafe" -// archHonorsR2 captures the fact that r2 is honored by the -// runtime.GOARCH. Syscall conventions are generally r1, r2, err := -// syscall(trap, ...). Not all architectures define r2 in their -// ABI. See "man syscall". -const archHonorsR2 = true - const _SYS_setgroups = SYS_SETGROUPS func EpollCreate(size int) (fd int, err error) { diff --git a/src/syscall/syscall_linux_mips64x.go b/src/syscall/syscall_linux_mips64x.go index 7c9dd80614..7be1664637 100644 --- a/src/syscall/syscall_linux_mips64x.go +++ b/src/syscall/syscall_linux_mips64x.go @@ -6,12 +6,6 @@ package syscall -// archHonorsR2 captures the fact that r2 is honored by the -// runtime.GOARCH. Syscall conventions are generally r1, r2, err := -// syscall(trap, ...). Not all architectures define r2 in their -// ABI. See "man syscall". -const archHonorsR2 = true - const _SYS_setgroups = SYS_SETGROUPS //sys Dup2(oldfd int, newfd int) (err error) diff --git a/src/syscall/syscall_linux_mipsx.go b/src/syscall/syscall_linux_mipsx.go index 741eeb14bb..97188d3895 100644 --- a/src/syscall/syscall_linux_mipsx.go +++ b/src/syscall/syscall_linux_mipsx.go @@ -8,12 +8,6 @@ package syscall import "unsafe" -// archHonorsR2 captures the fact that r2 is honored by the -// runtime.GOARCH. Syscall conventions are generally r1, r2, err := -// syscall(trap, ...). Not all architectures define r2 in their -// ABI. See "man syscall". -const archHonorsR2 = true - const _SYS_setgroups = SYS_SETGROUPS func Syscall9(trap, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr, err Errno) diff --git a/src/syscall/syscall_linux_ppc64x.go b/src/syscall/syscall_linux_ppc64x.go index cc1b72e0e7..ac42b20598 100644 --- a/src/syscall/syscall_linux_ppc64x.go +++ b/src/syscall/syscall_linux_ppc64x.go @@ -6,12 +6,6 @@ package syscall -// archHonorsR2 captures the fact that r2 is honored by the -// runtime.GOARCH. Syscall conventions are generally r1, r2, err := -// syscall(trap, ...). Not all architectures define r2 in their -// ABI. See "man syscall". -const archHonorsR2 = false - const _SYS_setgroups = SYS_SETGROUPS //sys Dup2(oldfd int, newfd int) (err error) diff --git a/src/syscall/syscall_linux_riscv64.go b/src/syscall/syscall_linux_riscv64.go index bcb89c6e9a..4331a19e8d 100644 --- a/src/syscall/syscall_linux_riscv64.go +++ b/src/syscall/syscall_linux_riscv64.go @@ -6,12 +6,6 @@ package syscall import "unsafe" -// archHonorsR2 captures the fact that r2 is honored by the -// runtime.GOARCH. Syscall conventions are generally r1, r2, err := -// syscall(trap, ...). Not all architectures define r2 in their -// ABI. See "man syscall". -const archHonorsR2 = true - const _SYS_setgroups = SYS_SETGROUPS func EpollCreate(size int) (fd int, err error) { diff --git a/src/syscall/syscall_linux_s390x.go b/src/syscall/syscall_linux_s390x.go index 123664f5b2..ff99024788 100644 --- a/src/syscall/syscall_linux_s390x.go +++ b/src/syscall/syscall_linux_s390x.go @@ -6,12 +6,6 @@ package syscall import "unsafe" -// archHonorsR2 captures the fact that r2 is honored by the -// runtime.GOARCH. Syscall conventions are generally r1, r2, err := -// syscall(trap, ...). Not all architectures define r2 in their -// ABI. See "man syscall". -const archHonorsR2 = true - const _SYS_setgroups = SYS_SETGROUPS //sys Dup2(oldfd int, newfd int) (err error) diff --git a/src/syscall/syscall_linux_test.go b/src/syscall/syscall_linux_test.go index 8d828be015..0444b64266 100644 --- a/src/syscall/syscall_linux_test.go +++ b/src/syscall/syscall_linux_test.go @@ -15,6 +15,7 @@ import ( "sort" "strconv" "strings" + "sync" "syscall" "testing" "unsafe" @@ -565,3 +566,73 @@ func TestSetuidEtc(t *testing.T) { } } } + +// TestAllThreadsSyscallError verifies that errors are properly returned when +// the syscall fails on the original thread. +func TestAllThreadsSyscallError(t *testing.T) { + // SYS_CAPGET takes pointers as the first two arguments. Since we pass + // 0, we expect to get EFAULT back. + r1, r2, err := syscall.AllThreadsSyscall(syscall.SYS_CAPGET, 0, 0, 0) + if err == syscall.ENOTSUP { + t.Skip("AllThreadsSyscall disabled with cgo") + } + if err != syscall.EFAULT { + t.Errorf("AllThreadSyscall(SYS_CAPGET) got %d, %d, %v, want err %v", r1, r2, err, syscall.EFAULT) + } +} + +// TestAllThreadsSyscallBlockedSyscall confirms that AllThreadsSyscall +// can interrupt threads in long-running system calls. This test will +// deadlock if this doesn't work correctly. +func TestAllThreadsSyscallBlockedSyscall(t *testing.T) { + if _, _, err := syscall.AllThreadsSyscall(syscall.SYS_PRCTL, PR_SET_KEEPCAPS, 0, 0); err == syscall.ENOTSUP { + t.Skip("AllThreadsSyscall disabled with cgo") + } + + rd, wr, err := os.Pipe() + if err != nil { + t.Fatalf("unable to obtain a pipe: %v", err) + } + + // Perform a blocking read on the pipe. + var wg sync.WaitGroup + ready := make(chan bool) + wg.Add(1) + go func() { + data := make([]byte, 1) + + // To narrow the window we have to wait for this + // goroutine to block in read, synchronize just before + // calling read. + ready <- true + + // We use syscall.Read directly to avoid the poller. + // This will return when the write side is closed. + n, err := syscall.Read(int(rd.Fd()), data) + if !(n == 0 && err == nil) { + t.Errorf("expected read to return 0, got %d, %s", n, err) + } + + // Clean up rd and also ensure rd stays reachable so + // it doesn't get closed by GC. + rd.Close() + wg.Done() + }() + <-ready + + // Loop here to give the goroutine more time to block in read. + // Generally this will trigger on the first iteration anyway. + pid := syscall.Getpid() + for i := 0; i < 100; i++ { + if id, _, e := syscall.AllThreadsSyscall(syscall.SYS_GETPID, 0, 0, 0); e != 0 { + t.Errorf("[%d] getpid failed: %v", i, e) + } else if int(id) != pid { + t.Errorf("[%d] getpid got=%d, want=%d", i, id, pid) + } + // Provide an explicit opportunity for this goroutine + // to change Ms. + runtime.Gosched() + } + wr.Close() + wg.Wait() +} diff --git a/src/syscall/syscall_unix_test.go b/src/syscall/syscall_unix_test.go index e4af0ba4a5..317c0c1f34 100644 --- a/src/syscall/syscall_unix_test.go +++ b/src/syscall/syscall_unix_test.go @@ -326,46 +326,6 @@ func TestUnixRightsRoundtrip(t *testing.T) { } } -func TestRlimit(t *testing.T) { - var rlimit, zero syscall.Rlimit - err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit) - if err != nil { - t.Fatalf("Getrlimit: save failed: %v", err) - } - if zero == rlimit { - t.Fatalf("Getrlimit: save failed: got zero value %#v", rlimit) - } - set := rlimit - set.Cur = set.Max - 1 - if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && set.Cur > 4096 { - // rlim_min for RLIMIT_NOFILE should be equal to - // or lower than kern.maxfilesperproc, which on - // some machines are 4096. See #40564. - set.Cur = 4096 - } - err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &set) - if err != nil { - t.Fatalf("Setrlimit: set failed: %#v %v", set, err) - } - var get syscall.Rlimit - err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &get) - if err != nil { - t.Fatalf("Getrlimit: get failed: %v", err) - } - set = rlimit - set.Cur = set.Max - 1 - if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && set.Cur > 4096 { - set.Cur = 4096 - } - if set != get { - t.Fatalf("Rlimit: change failed: wanted %#v got %#v", set, get) - } - err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlimit) - if err != nil { - t.Fatalf("Setrlimit: restore failed: %#v %v", rlimit, err) - } -} - func TestSeekFailure(t *testing.T) { _, err := syscall.Seek(-1, 0, io.SeekStart) if err == nil { diff --git a/src/testing/fuzz.go b/src/testing/fuzz.go index e1d7544f7a..b5e1339deb 100644 --- a/src/testing/fuzz.go +++ b/src/testing/fuzz.go @@ -227,6 +227,9 @@ func (f *F) Fuzz(ff any) { if fnType.NumIn() < 2 || fnType.In(0) != reflect.TypeOf((*T)(nil)) { panic("testing: fuzz target must receive at least two arguments, where the first argument is a *T") } + if fnType.NumOut() != 0 { + panic("testing: fuzz target must not return a value") + } // Save the types of the function to compare against the corpus. var types []reflect.Type diff --git a/src/time/format.go b/src/time/format.go index 5fb9cdc969..33e6543289 100644 --- a/src/time/format.go +++ b/src/time/format.go @@ -914,6 +914,7 @@ func skip(value, prefix string) (string, error) { // field immediately after the seconds field, even if the layout does not // signify its presence. In that case either a comma or a decimal point // followed by a maximal series of digits is parsed as a fractional second. +// Fractional seconds are truncated to nanosecond precision. // // Elements omitted from the layout are assumed to be zero or, when // zero is impossible, one, so parsing "3:04pm" returns the time diff --git a/test/fixedbugs/bug255.go b/test/fixedbugs/bug255.go index 38df7813c9..184ff2d378 100644 --- a/test/fixedbugs/bug255.go +++ b/test/fixedbugs/bug255.go @@ -6,12 +6,13 @@ package main -var a [10]int // ok -var b [1e1]int // ok -var c [1.5]int // ERROR "truncated|must be integer" -var d ["abc"]int // ERROR "invalid array bound|not numeric|must be integer" -var e [nil]int // ERROR "use of untyped nil|invalid array bound|not numeric|must be constant" -var f [e]int // ok: error already reported for e +var a [10]int // ok +var b [1e1]int // ok +var c [1.5]int // ERROR "truncated|must be integer" +var d ["abc"]int // ERROR "invalid array bound|not numeric|must be integer" +var e [nil]int // ERROR "use of untyped nil|invalid array (bound|length)|not numeric|must be constant" +// var f [e]int // ok with Go 1.17 because an error was reported for e; leads to an error for Go 1.18 +var f [ee]int // ERROR "undefined|undeclared" var g [1 << 65]int // ERROR "array bound is too large|overflows|must be integer" var h [len(a)]int // ok diff --git a/test/fixedbugs/bug515.go b/test/fixedbugs/bug515.go new file mode 100644 index 0000000000..186f46609a --- /dev/null +++ b/test/fixedbugs/bug515.go @@ -0,0 +1,21 @@ +// compile + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Caused a gofrontend crash. + +package p + +//go:notinheap +type S1 struct{} + +type S2 struct { + r interface { Read([]byte) (int, error) } + s1, s2 []byte + p *S1 + n uintptr +} + +var V any = S2{} diff --git a/test/fixedbugs/issue51101.go b/test/fixedbugs/issue51101.go new file mode 100644 index 0000000000..a390e50da4 --- /dev/null +++ b/test/fixedbugs/issue51101.go @@ -0,0 +1,36 @@ +// run + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Issue 51101: on RISCV64, difference of two pointers +// was marked as pointer and crashes GC. + +package main + +var a, b int + +func main() { + F(&b, &a) +} + +//go:noinline +func F(a, b *int) bool { + x := a == b + G(x) + y := a != b + return y +} + +//go:noinline +func G(bool) { + grow([1000]int{20}) +} + +func grow(x [1000]int) { + if x[0] != 0 { + x[0]-- + grow(x) + } +} diff --git a/test/fixedbugs/issue51401.go b/test/fixedbugs/issue51401.go new file mode 100644 index 0000000000..1e8e0d0b6e --- /dev/null +++ b/test/fixedbugs/issue51401.go @@ -0,0 +1,44 @@ +// run + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Issue 51401: bad inline info in generated interface method wrapper +// causes infinite loop in stack unwinding. + +package main + +import "runtime" + +type Outer interface{ Inner } + +type impl struct{} + +func New() Outer { return &impl{} } + +type Inner interface { + DoStuff() error +} + +func (a *impl) DoStuff() error { + return newError() +} + +func newError() error { + stack := make([]uintptr, 50) + runtime.Callers(2, stack[:]) + + return nil +} + +func main() { + funcs := listFuncs(New()) + for _, f := range funcs { + f() + } +} + +func listFuncs(outer Outer) []func() error { + return []func() error{outer.DoStuff} +} diff --git a/test/fixedbugs/issue51437.go b/test/fixedbugs/issue51437.go new file mode 100644 index 0000000000..3d1b9ee32c --- /dev/null +++ b/test/fixedbugs/issue51437.go @@ -0,0 +1,19 @@ +// compile + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type T struct{} + +func (T) m() []T { return nil } + +func f(x T) { + for _, x := range func() []T { + return x.m() + }() { + _ = x + } +} diff --git a/test/fixedbugs/issue51531.go b/test/fixedbugs/issue51531.go new file mode 100644 index 0000000000..9a6a4388dc --- /dev/null +++ b/test/fixedbugs/issue51531.go @@ -0,0 +1,13 @@ +// errorcheck -G=3 -lang=go1.17 + +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type empty interface{} + +type Foo[T empty] int // ERROR "type parameter requires go1\.18 or later \(-lang was set to go1\.17; check go.mod\)" + +func Bar[T empty]() {} // ERROR "type parameter requires go1\.18 or later \(-lang was set to go1\.17; check go.mod\)" diff --git a/test/typeparam/absdiff2.go b/test/typeparam/absdiff2.go index 36de8ff2c9..443388e610 100644 --- a/test/typeparam/absdiff2.go +++ b/test/typeparam/absdiff2.go @@ -23,15 +23,16 @@ type Numeric interface { // numericAbs matches a struct containing a numeric type that has an Abs method. type numericAbs[T Numeric] interface { - ~struct{ Value T } + ~struct{ Value_ T } Abs() T + Value() T } // absDifference computes the absolute value of the difference of // a and b, where the absolute value is determined by the Abs method. func absDifference[T Numeric, U numericAbs[T]](a, b U) T { - d := a.Value - b.Value - dt := U{Value: d} + d := a.Value() - b.Value() + dt := U{Value_: d} return dt.Abs() } @@ -50,20 +51,29 @@ type Complex interface { // orderedAbs is a helper type that defines an Abs method for // a struct containing an ordered numeric type. type orderedAbs[T orderedNumeric] struct { - Value T + Value_ T } func (a orderedAbs[T]) Abs() T { - if a.Value < 0 { - return -a.Value + if a.Value_ < 0 { + return -a.Value_ } - return a.Value + return a.Value_ +} + +// Field accesses through type parameters are disabled +// until we have a more thorough understanding of the +// implications on the spec. See issue #51576. +// Use accessor method instead. + +func (a orderedAbs[T]) Value() T { + return a.Value_ } // complexAbs is a helper type that defines an Abs method for // a struct containing a complex type. type complexAbs[T Complex] struct { - Value T + Value_ T } func realimag(x any) (re, im float64) { @@ -82,13 +92,17 @@ func realimag(x any) (re, im float64) { func (a complexAbs[T]) Abs() T { // TODO use direct conversion instead of realimag once #50937 is fixed - r, i := realimag(a.Value) + r, i := realimag(a.Value_) // r := float64(real(a.Value)) // i := float64(imag(a.Value)) d := math.Sqrt(r*r + i*i) return T(complex(d, 0)) } +func (a complexAbs[T]) Value() T { + return a.Value_ +} + // OrderedAbsDifference returns the absolute value of the difference // between a and b, where a and b are of an ordered type. func OrderedAbsDifference[T orderedNumeric](a, b T) T { diff --git a/test/typeparam/absdiffimp2.dir/a.go b/test/typeparam/absdiffimp2.dir/a.go index 43493e1430..dc64f2dcbe 100644 --- a/test/typeparam/absdiffimp2.dir/a.go +++ b/test/typeparam/absdiffimp2.dir/a.go @@ -17,15 +17,16 @@ type Numeric interface { // numericAbs matches a struct containing a numeric type that has an Abs method. type numericAbs[T Numeric] interface { - ~struct{ Value T } + ~struct{ Value_ T } Abs() T + Value() T } // absDifference computes the absolute value of the difference of // a and b, where the absolute value is determined by the Abs method. func absDifference[T Numeric, U numericAbs[T]](a, b U) T { - d := a.Value - b.Value - dt := U{Value: d} + d := a.Value() - b.Value() + dt := U{Value_: d} return dt.Abs() } @@ -44,20 +45,29 @@ type Complex interface { // orderedAbs is a helper type that defines an Abs method for // a struct containing an ordered numeric type. type orderedAbs[T orderedNumeric] struct { - Value T + Value_ T } func (a orderedAbs[T]) Abs() T { - if a.Value < 0 { - return -a.Value + if a.Value_ < 0 { + return -a.Value_ } - return a.Value + return a.Value_ +} + +// Field accesses through type parameters are disabled +// until we have a more thorough understanding of the +// implications on the spec. See issue #51576. +// Use accessor method instead. + +func (a orderedAbs[T]) Value() T { + return a.Value_ } // complexAbs is a helper type that defines an Abs method for // a struct containing a complex type. type complexAbs[T Complex] struct { - Value T + Value_ T } func realimag(x any) (re, im float64) { @@ -76,13 +86,17 @@ func realimag(x any) (re, im float64) { func (a complexAbs[T]) Abs() T { // TODO use direct conversion instead of realimag once #50937 is fixed - r, i := realimag(a.Value) + r, i := realimag(a.Value_) // r := float64(real(a.Value)) // i := float64(imag(a.Value)) d := math.Sqrt(r*r + i*i) return T(complex(d, 0)) } +func (a complexAbs[T]) Value() T { + return a.Value_ +} + // OrderedAbsDifference returns the absolute value of the difference // between a and b, where a and b are of an ordered type. func OrderedAbsDifference[T orderedNumeric](a, b T) T { diff --git a/test/typeparam/issue48424.go b/test/typeparam/issue48424.go index 3253e6457b..8d80911568 100644 --- a/test/typeparam/issue48424.go +++ b/test/typeparam/issue48424.go @@ -13,14 +13,14 @@ func identity[T int](x T) T { return x } -func min[T int|string](x, y T) T { +func min[T int | string](x, y T) T { if x < y { return x } return y } -func max[T ~float64](x, y T) T { +func max[T ~int | ~float64](x, y T) T { if x > y { return x } @@ -48,7 +48,7 @@ func main() { // Some random type parameter lists with elided interfaces. type ( - _ [T struct{}] struct{} - _ [M map[K]V, K comparable, V any] struct{} - _ [_ interface{}|int] struct{} + _[T struct{}] struct{} + _[M map[K]V, K comparable, V any] struct{} + _[_ interface{} | int] struct{} ) diff --git a/test/typeparam/issue50417.go b/test/typeparam/issue50417.go index cd46f3feab..e93583fc35 100644 --- a/test/typeparam/issue50417.go +++ b/test/typeparam/issue50417.go @@ -8,6 +8,11 @@ package main func main() {} +// Field accesses through type parameters are disabled +// until we have a more thorough understanding of the +// implications on the spec. See issue #51576. + +/* type Sf struct { f int } @@ -57,7 +62,7 @@ func f2[P interface { var _ = f2[Sfm] -// special case: structural type is a named pointer type +// special case: core type is a named pointer type type PSfm *Sfm @@ -68,7 +73,7 @@ func f3[P interface{ PSfm }](p P) { var _ = f3[PSfm] -// special case: structural type is an unnamed pointer type +// special case: core type is an unnamed pointer type func f4[P interface{ *Sfm }](p P) { _ = p.f @@ -138,3 +143,4 @@ func f8[P Int4](p P) { } var _ = f8[*Sf] +*/ diff --git a/test/typeparam/issue50417b.go b/test/typeparam/issue50417b.go index e6b205cb37..86e1f8aa14 100644 --- a/test/typeparam/issue50417b.go +++ b/test/typeparam/issue50417b.go @@ -6,6 +6,13 @@ package main +func main() {} + +// Field accesses through type parameters are disabled +// until we have a more thorough understanding of the +// implications on the spec. See issue #51576. + +/* import "fmt" type MyStruct struct { @@ -48,3 +55,4 @@ func main() { panic(fmt.Sprintf("got %d, want %d", got, want)) } } +*/ diff --git a/test/typeparam/issue50690a.go b/test/typeparam/issue50690a.go index 5af3c9ead8..0f5f5e9bd1 100644 --- a/test/typeparam/issue50690a.go +++ b/test/typeparam/issue50690a.go @@ -29,34 +29,47 @@ func Sum[T Numeric](args ...T) T { // Ledger is an identifiable, financial record. type Ledger[T ~string, K Numeric] struct { - // ID identifies the ledger. - ID T + ID_ T // Amounts is a list of monies associated with this ledger. - Amounts []K + Amounts_ []K // SumFn is a function that can be used to sum the amounts // in this ledger. - SumFn func(...K) K + SumFn_ func(...K) K } +// Field accesses through type parameters are disabled +// until we have a more thorough understanding of the +// implications on the spec. See issue #51576. +// Use accessor methods instead. + +func (l Ledger[T, _]) ID() T { return l.ID_ } +func (l Ledger[_, K]) Amounts() []K { return l.Amounts_ } +func (l Ledger[_, K]) SumFn() func(...K) K { return l.SumFn_ } + func PrintLedger[ T ~string, K Numeric, - L ~struct { - ID T - Amounts []K - SumFn func(...K) K + L interface { + ~struct { + ID_ T + Amounts_ []K + SumFn_ func(...K) K + } + ID() T + Amounts() []K + SumFn() func(...K) K }, ](l L) { - fmt.Printf("%s has a sum of %v\n", l.ID, l.SumFn(l.Amounts...)) + fmt.Printf("%s has a sum of %v\n", l.ID(), l.SumFn()(l.Amounts()...)) } func main() { PrintLedger(Ledger[string, int]{ - ID: "fake", - Amounts: []int{1, 2, 3}, - SumFn: Sum[int], + ID_: "fake", + Amounts_: []int{1, 2, 3}, + SumFn_: Sum[int], }) } diff --git a/test/typeparam/issue50690b.go b/test/typeparam/issue50690b.go index 498b9d37e1..572d8eb0ff 100644 --- a/test/typeparam/issue50690b.go +++ b/test/typeparam/issue50690b.go @@ -18,24 +18,34 @@ func Print[T ~string](s T) { fmt.Println(s) } -func PrintWithPrinter[T ~string, S ~struct { - ID T - PrintFn func(T) +func PrintWithPrinter[T ~string, S interface { + ~struct { + ID T + PrintFn_ func(T) + } + PrintFn() func(T) }](message T, obj S) { - obj.PrintFn(message) + obj.PrintFn()(message) } type PrintShop[T ~string] struct { - ID T - PrintFn func(T) + ID T + PrintFn_ func(T) } +// Field accesses through type parameters are disabled +// until we have a more thorough understanding of the +// implications on the spec. See issue #51576. +// Use accessor method instead. + +func (s PrintShop[T]) PrintFn() func(T) { return s.PrintFn_ } + func main() { PrintWithPrinter( "Hello, world.", PrintShop[string]{ - ID: "fake", - PrintFn: Print[string], + ID: "fake", + PrintFn_: Print[string], }, ) } diff --git a/test/typeparam/issue50690c.go b/test/typeparam/issue50690c.go index aa9258f932..8b87c2f06d 100644 --- a/test/typeparam/issue50690c.go +++ b/test/typeparam/issue50690c.go @@ -18,19 +18,33 @@ func Print[T ~string](s T) { fmt.Println(s) } -func PrintWithPrinter[T ~string, S struct { - ID T - PrintFn func(T) +func PrintWithPrinter[T ~string, S interface { + ~struct { + ID T + PrintFn_ func(T) + } + PrintFn() func(T) }](message T, obj S) { - obj.PrintFn(message) + obj.PrintFn()(message) } func main() { PrintWithPrinter( "Hello, world.", - struct { - ID string - PrintFn func(string) - }{ID: "fake", PrintFn: Print[string]}, + StructWithPrinter{ID: "fake", PrintFn_: Print[string]}, ) } + +type StructWithPrinter struct { + ID string + PrintFn_ func(string) +} + +// Field accesses through type parameters are disabled +// until we have a more thorough understanding of the +// implications on the spec. See issue #51576. +// Use accessor method instead. + +func (s StructWithPrinter) PrintFn() func(string) { + return s.PrintFn_ +} diff --git a/test/typeparam/issue51219.dir/a.go b/test/typeparam/issue51219.dir/a.go new file mode 100644 index 0000000000..29670df0d3 --- /dev/null +++ b/test/typeparam/issue51219.dir/a.go @@ -0,0 +1,20 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package a + +// Type I is the first basic test for the issue, which relates to a type that is recursive +// via a type constraint. (In this test, I -> IConstraint -> MyStruct -> I.) +type JsonRaw []byte + +type MyStruct struct { + x *I[JsonRaw] +} + +type IConstraint interface { + JsonRaw | MyStruct +} + +type I[T IConstraint] struct { +} diff --git a/test/typeparam/issue51219.dir/main.go b/test/typeparam/issue51219.dir/main.go new file mode 100644 index 0000000000..999b4a96a1 --- /dev/null +++ b/test/typeparam/issue51219.dir/main.go @@ -0,0 +1,16 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "a" + "fmt" +) + +func main() { + var x a.I[a.JsonRaw] + + fmt.Printf("%v\n", x) +} diff --git a/test/typeparam/issue51219.go b/test/typeparam/issue51219.go new file mode 100644 index 0000000000..642f4bf49f --- /dev/null +++ b/test/typeparam/issue51219.go @@ -0,0 +1,7 @@ +// rundir -G=3 + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ignored diff --git a/test/typeparam/issue51219.out b/test/typeparam/issue51219.out new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/test/typeparam/issue51219.out @@ -0,0 +1 @@ +{} diff --git a/test/typeparam/issue51219b.dir/a.go b/test/typeparam/issue51219b.dir/a.go new file mode 100644 index 0000000000..19049406a6 --- /dev/null +++ b/test/typeparam/issue51219b.dir/a.go @@ -0,0 +1,37 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package a + +type Interaction[DataT InteractionDataConstraint] struct { +} + +type InteractionDataConstraint interface { + []byte | + UserCommandInteractionData +} + +type UserCommandInteractionData struct { + resolvedInteractionWithOptions +} + +type resolvedInteractionWithOptions struct { + Resolved Resolved `json:"resolved,omitempty"` +} + +type Resolved struct { + Users ResolvedData[User] `json:"users,omitempty"` +} + +type ResolvedData[T ResolvedDataConstraint] map[uint64]T + +type ResolvedDataConstraint interface { + User | Message +} + +type User struct{} + +type Message struct { + Interaction *Interaction[[]byte] `json:"interaction,omitempty"` +} diff --git a/test/typeparam/issue51219b.dir/b.go b/test/typeparam/issue51219b.dir/b.go new file mode 100644 index 0000000000..8413d666b7 --- /dev/null +++ b/test/typeparam/issue51219b.dir/b.go @@ -0,0 +1,14 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package b + +import ( + "./a" +) + +// InteractionRequest is an incoming request Interaction +type InteractionRequest[T a.InteractionDataConstraint] struct { + a.Interaction[T] +} diff --git a/test/typeparam/issue51219b.dir/p.go b/test/typeparam/issue51219b.dir/p.go new file mode 100644 index 0000000000..9f8b840d48 --- /dev/null +++ b/test/typeparam/issue51219b.dir/p.go @@ -0,0 +1,14 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +import ( + "./b" +) + +// ResponseWriterMock mocks corde's ResponseWriter interface +type ResponseWriterMock struct { + x b.InteractionRequest[[]byte] +} diff --git a/test/typeparam/issue51219b.go b/test/typeparam/issue51219b.go new file mode 100644 index 0000000000..060a1214cc --- /dev/null +++ b/test/typeparam/issue51219b.go @@ -0,0 +1,7 @@ +// compiledir -G=3 + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ignored diff --git a/test/typeparam/issue51232.go b/test/typeparam/issue51232.go new file mode 100644 index 0000000000..4e9d68cb69 --- /dev/null +++ b/test/typeparam/issue51232.go @@ -0,0 +1,31 @@ +// errorcheck -G=3 + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +type RC[RG any] interface { + ~[]RG +} + +type Fn[RCT RC[RG], RG any] func(RCT) + +type F[RCT RC[RG], RG any] interface { + Fn() Fn[RCT] // ERROR "got 1 arguments" +} + +type concreteF[RCT RC[RG], RG any] struct { + makeFn func() Fn[RCT] // ERROR "got 1 arguments" +} + +func (c *concreteF[RCT, RG]) Fn() Fn[RCT] { // ERROR "got 1 arguments" + return c.makeFn() +} + +func NewConcrete[RCT RC[RG], RG any](Rc RCT) F[RCT] { // ERROR "got 1 arguments" + return &concreteF[RCT]{ // ERROR "cannot use" "got 1 arguments" + makeFn: nil, + } +} diff --git a/test/typeparam/issue51233.go b/test/typeparam/issue51233.go new file mode 100644 index 0000000000..e6aabf32fd --- /dev/null +++ b/test/typeparam/issue51233.go @@ -0,0 +1,28 @@ +// errorcheck -G=3 + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +package p + +// As of issue #51527, type-type inference has been disabled. + +type RC[RG any] interface { + ~[]RG +} + +type Fn[RCT RC[RG], RG any] func(RCT) + +type FFn[RCT RC[RG], RG any] func() Fn[RCT] // ERROR "got 1 arguments" + +type F[RCT RC[RG], RG any] interface { + Fn() Fn[RCT] // ERROR "got 1 arguments" +} + +type concreteF[RCT RC[RG], RG any] struct { + makeFn FFn[RCT] // ERROR "got 1 arguments" +} + +func (c *concreteF[RCT, RG]) Fn() Fn[RCT] { // ERROR "got 1 arguments" + return c.makeFn() +} diff --git a/test/typeparam/issue51236.go b/test/typeparam/issue51236.go new file mode 100644 index 0000000000..779c74ee6c --- /dev/null +++ b/test/typeparam/issue51236.go @@ -0,0 +1,22 @@ +// run -gcflags=-G=3 + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +type I interface { + []byte +} + +func F[T I]() { + var t T + explodes(t) +} + +func explodes(b []byte) {} + +func main() { + +} diff --git a/test/typeparam/issue51245.go b/test/typeparam/issue51245.go new file mode 100644 index 0000000000..bd4f7c5dc9 --- /dev/null +++ b/test/typeparam/issue51245.go @@ -0,0 +1,16 @@ +// build -gcflags=-G=3 + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +type T[P any] int +const C T[int] = 3 + +type T2 int +const C2 T2 = 9 + +func main() { +} diff --git a/test/typeparam/issue51250a.dir/a.go b/test/typeparam/issue51250a.dir/a.go new file mode 100644 index 0000000000..12dd60a3d1 --- /dev/null +++ b/test/typeparam/issue51250a.dir/a.go @@ -0,0 +1,9 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package a + +type G[T any] struct { + x T +} diff --git a/test/typeparam/issue51250a.dir/b.go b/test/typeparam/issue51250a.dir/b.go new file mode 100644 index 0000000000..114c9f80f7 --- /dev/null +++ b/test/typeparam/issue51250a.dir/b.go @@ -0,0 +1,24 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package b + +import "./a" + +type T struct { a int } + +var I interface{} = a.G[T]{} + +//go:noinline +func F(x interface{}) { + switch x.(type) { + case a.G[T]: + case int: + panic("bad") + case float64: + panic("bad") + default: + panic("bad") + } +} diff --git a/test/typeparam/issue51250a.dir/main.go b/test/typeparam/issue51250a.dir/main.go new file mode 100644 index 0000000000..45288be482 --- /dev/null +++ b/test/typeparam/issue51250a.dir/main.go @@ -0,0 +1,24 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "./a" + "./b" +) + +func main() { + switch b.I.(type) { + case a.G[b.T]: + case int: + panic("bad") + case float64: + panic("bad") + default: + panic("bad") + } + + b.F(a.G[b.T]{}) +} diff --git a/test/typeparam/issue51250a.go b/test/typeparam/issue51250a.go new file mode 100644 index 0000000000..aefbe67310 --- /dev/null +++ b/test/typeparam/issue51250a.go @@ -0,0 +1,7 @@ +// rundir + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ignored diff --git a/test/typeparam/issue51303.go b/test/typeparam/issue51303.go new file mode 100644 index 0000000000..5f4bdc0634 --- /dev/null +++ b/test/typeparam/issue51303.go @@ -0,0 +1,65 @@ +// run -gcflags=-G=3 + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" +) + +func main() { + x := [][]int{{1}} + y := [][]int{{2, 3}} + IntersectSS(x, y) +} + +type list[E any] interface { + ~[]E + Equal(x, y E) bool +} + +// ss is a set of sets +type ss[E comparable, T []E] []T + +func (ss[E, T]) Equal(a, b T) bool { + return SetEq(a, b) +} + +func IntersectSS[E comparable](x, y [][]E) [][]E { + return IntersectT[[]E, ss[E, []E]](ss[E, []E](x), ss[E, []E](y)) +} + +func IntersectT[E any, L list[E]](x, y L) L { + var z L +outer: + for _, xe := range x { + fmt.Println("xe", xe) + for _, ye := range y { + fmt.Println("ye", ye) + fmt.Println("x", x) + if x.Equal(xe, ye) { + fmt.Println("appending") + z = append(z, xe) + continue outer + } + } + } + return z +} + +func SetEq[S []E, E comparable](x, y S) bool { + fmt.Println("SetEq", x, y) +outer: + for _, xe := range x { + for _, ye := range y { + if xe == ye { + continue outer + } + } + return false // xs wasn't found in y + } + return true +} diff --git a/test/typeparam/issue51303.out b/test/typeparam/issue51303.out new file mode 100644 index 0000000000..34b3be32dd --- /dev/null +++ b/test/typeparam/issue51303.out @@ -0,0 +1,4 @@ +xe [1] +ye [2 3] +x [[1]] +SetEq [1] [2 3] diff --git a/test/typeparam/issue51355.go b/test/typeparam/issue51355.go new file mode 100644 index 0000000000..15ffa4ba21 --- /dev/null +++ b/test/typeparam/issue51355.go @@ -0,0 +1,31 @@ +// compile -G=3 + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +type Cache[E comparable] struct { + adder func(...E) +} + +func New[E comparable]() *Cache[E] { + c := &Cache[E]{} + + c.adder = func(elements ...E) { + for _, value := range elements { + value := value + go func() { + println(value) + }() + } + } + + return c +} + +func main() { + c := New[string]() + c.adder("test") +} diff --git a/test/typeparam/issue51367.dir/a.go b/test/typeparam/issue51367.dir/a.go new file mode 100644 index 0000000000..be0c3b0688 --- /dev/null +++ b/test/typeparam/issue51367.dir/a.go @@ -0,0 +1,14 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package a + +type A[T any] struct{} + +func (_ A[T]) Method() {} + +func DoSomething[P any]() { + a := A[*byte]{} + a.Method() +} diff --git a/test/typeparam/issue51367.dir/main.go b/test/typeparam/issue51367.dir/main.go new file mode 100644 index 0000000000..64273d313b --- /dev/null +++ b/test/typeparam/issue51367.dir/main.go @@ -0,0 +1,13 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "a" +) + +func main() { + a.DoSomething[byte]() +} diff --git a/test/typeparam/issue51367.go b/test/typeparam/issue51367.go new file mode 100644 index 0000000000..642f4bf49f --- /dev/null +++ b/test/typeparam/issue51367.go @@ -0,0 +1,7 @@ +// rundir -G=3 + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ignored diff --git a/test/typeparam/issue51423.dir/a.go b/test/typeparam/issue51423.dir/a.go new file mode 100644 index 0000000000..e824d0e165 --- /dev/null +++ b/test/typeparam/issue51423.dir/a.go @@ -0,0 +1,17 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package a + +type Comparator[T any] func(v1, v2 T) int + +func CompareInt[T ~int](a, b T) int { + if a < b { + return -1 + } + if a == b { + return 0 + } + return 1 +} diff --git a/test/typeparam/issue51423.dir/b.go b/test/typeparam/issue51423.dir/b.go new file mode 100644 index 0000000000..2bad19fbda --- /dev/null +++ b/test/typeparam/issue51423.dir/b.go @@ -0,0 +1,11 @@ +package b + +import "./a" + +func C() a.Comparator[int] { + return a.CompareInt[int] +} + +func main() { + _ = C()(1, 2) +} diff --git a/test/typeparam/issue51423.go b/test/typeparam/issue51423.go new file mode 100644 index 0000000000..060a1214cc --- /dev/null +++ b/test/typeparam/issue51423.go @@ -0,0 +1,7 @@ +// compiledir -G=3 + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ignored diff --git a/test/typeparam/issue51522a.go b/test/typeparam/issue51522a.go new file mode 100644 index 0000000000..31ce4bfc18 --- /dev/null +++ b/test/typeparam/issue51522a.go @@ -0,0 +1,42 @@ +// run -gcflags=-G=3 + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +package main + + +func f[T comparable](i any) { + var t T + + if i != t { + println("FAIL: if i != t") + } +} + +type myint int + +func (m myint) foo() { +} + +type fooer interface { + foo() +} + +type comparableFoo interface { + comparable + foo() +} + +func g[T comparableFoo](i fooer) { + var t T + + if i != t { + println("FAIL: if i != t") + } +} + +func main() { + f[int](int(0)) + g[myint](myint(0)) +} diff --git a/test/typeparam/issue51522b.go b/test/typeparam/issue51522b.go new file mode 100644 index 0000000000..47de578d63 --- /dev/null +++ b/test/typeparam/issue51522b.go @@ -0,0 +1,62 @@ +// run -gcflags=-G=3 + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +func f[T comparable](i any) { + var t T + + switch i { + case t: + // ok + default: + println("FAIL: switch i") + } + + switch t { + case i: + // ok + default: + println("FAIL: switch t") + } +} + +type myint int + +func (m myint) foo() { +} + +type fooer interface { + foo() +} + +type comparableFoo interface { + comparable + foo() +} + +func g[T comparableFoo](i fooer) { + var t T + + switch i { + case t: + // ok + default: + println("FAIL: switch i") + } + + switch t { + case i: + // ok + default: + println("FAIL: switch t") + } +} + +func main() { + f[int](0) + g[myint](myint(0)) +} diff --git a/test/typeparam/settable.go b/test/typeparam/settable.go index 412023b20a..6179ae016f 100644 --- a/test/typeparam/settable.go +++ b/test/typeparam/settable.go @@ -15,7 +15,7 @@ import ( type Setter[B any] interface { Set(string) - ~*B + *B } // Takes two type parameters where PT = *T diff --git a/test/typeparam/typelist.go b/test/typeparam/typelist.go index 34ea4b8aa9..c82cb5f19f 100644 --- a/test/typeparam/typelist.go +++ b/test/typeparam/typelist.go @@ -4,7 +4,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// This file tests type lists & structural constraints. +// This file tests type lists & constraints with core types. // Note: This test has been adjusted to use the new // type set notation rather than type lists. @@ -30,28 +30,28 @@ func _[T interface{ ~int }](x T) { var _ T = 42 var _ T = T(myint(42)) } + // TODO: put this type declaration back inside the above function when issue 47631 is fixed. type myint int -// Indexing a generic type which has a structural contraints to be an array. +// Indexing a generic type which has a an array as core type. func _[T interface{ ~[10]int }](x T) { _ = x[9] // ok } -// Dereference of a generic type which has a structural contraint to be a pointer. +// Dereference of a generic type which has a pointer as core type. func _[T interface{ ~*int }](p T) int { return *p } -// Channel send and receive on a generic type which has a structural constraint to -// be a channel. +// Channel send and receive on a generic type which has a channel as core type. func _[T interface{ ~chan int }](ch T) int { // This would deadlock if executed (but ok for a compile test) ch <- 0 return <-ch } -// Calling of a generic type which has a structural constraint to be a function. +// Calling of a generic type which has a function as core type. func _[T interface{ ~func() }](f T) { f() go f() @@ -62,7 +62,7 @@ func _[T interface{ ~func(string) int }](f T) int { return f("hello") } -// Map access of a generic type which has a structural constraint to be a map. +// Map access of a generic type which has a map as core type. func _[V any, T interface{ ~map[string]V }](p T) V { return p["test"] } @@ -89,7 +89,7 @@ func f1x() { } */ -func f2[A any, B interface{ ~[]A }](_ A, _ B) {} +func f2[A any, B interface{ []A }](_ A, _ B) {} func f2x() { f := f2[byte] f(byte(0), []byte{}) @@ -109,7 +109,7 @@ func f3x() { } */ -func f4[A any, B interface{ ~[]C }, C interface{ ~*A }](_ A, _ B, c C) {} +func f4[A any, B interface{ []C }, C interface{ *A }](_ A, _ B, c C) {} func f4x() { f := f4[int] var x int @@ -118,11 +118,13 @@ func f4x() { } func f5[A interface { - ~struct { + struct { b B c C } -}, B any, C interface{ ~*B }](x B) A { panic(0) } +}, B any, C interface{ *B }](x B) A { + panic(0) +} func f5x() { x := f5(1.2) var _ float64 = x.b