diff --git a/contribs/github-bot/internal/check/check.go b/contribs/github-bot/internal/check/check.go index 5ca2235e823..cb1848b757c 100644 --- a/contribs/github-bot/internal/check/check.go +++ b/contribs/github-bot/internal/check/check.go @@ -101,7 +101,8 @@ func processPRList(gh *client.GitHub, prs []*github.PullRequest) error { go func(pr *github.PullRequest) { defer wg.Done() commentContent := CommentContent{} - commentContent.allSatisfied = true + commentContent.AutoAllSatisfied = true + commentContent.ManualAllSatisfied = true // Iterate over all automatic rules in config. for _, autoRule := range autoRules { @@ -120,7 +121,7 @@ func processPRList(gh *client.GitHub, prs []*github.PullRequest) error { thenDetails.SetValue(fmt.Sprintf("%s Requirement satisfied", utils.Success)) c.Satisfied = true } else { - commentContent.allSatisfied = false + commentContent.AutoAllSatisfied = false } c.ConditionDetails = ifDetails.String() @@ -160,8 +161,14 @@ func processPRList(gh *client.GitHub, prs []*github.PullRequest) error { }, ) - if checkedBy == "" { - commentContent.allSatisfied = false + // If this check is the special one, store its state in the dedicated var. + if manualRule.Description == config.ForceSkipDescription { + if checkedBy != "" { + commentContent.ForceSkip = true + } + } else if checkedBy == "" { + // Or if its a normal check, just verify if it was checked by someone. + commentContent.ManualAllSatisfied = false } } @@ -224,9 +231,20 @@ func logResults(logger logger.Logger, prNum int, commentContent CommentContent) } logger.Infof("Conclusion:") - if commentContent.allSatisfied { - logger.Infof("%s All requirements are satisfied\n", utils.Success) + + if commentContent.AutoAllSatisfied { + logger.Infof("%s All automated checks are satisfied", utils.Success) + } else { + logger.Infof("%s Some automated checks are not satisfied", utils.Fail) + } + + if commentContent.ManualAllSatisfied { + logger.Infof("%s All manual checks are satisfied\n", utils.Success) } else { - logger.Infof("%s Not all requirements are satisfied\n", utils.Fail) + logger.Infof("%s Some manual checks are not satisfied\n", utils.Fail) + } + + if commentContent.ForceSkip { + logger.Infof("%s Bot checks are force skipped\n", utils.Success) } } diff --git a/contribs/github-bot/internal/check/comment.go b/contribs/github-bot/internal/check/comment.go index 297395ffe4b..d2b386cfa2e 100644 --- a/contribs/github-bot/internal/check/comment.go +++ b/contribs/github-bot/internal/check/comment.go @@ -24,9 +24,9 @@ var errTriggeredByBot = errors.New("event triggered by bot") // Compile regex only once. var ( // Regex for capturing the entire line of a manual check. - manualCheckLine = regexp.MustCompile(`(?m:^-\s\[([ xX])\]\s+(.+?)\s*(\(checked by @(\w+)\))?$)`) + manualCheckLine = regexp.MustCompile(`(?m:^- \[([ xX])\] (.+?)(?: \(checked by @([A-Za-z0-9-]+)\))?$)`) // Regex for capturing only the checkboxes. - checkboxes = regexp.MustCompile(`(?m:^- \[[ x]\])`) + checkboxes = regexp.MustCompile(`(?m:^- \[[ xX]\])`) // Regex used to capture markdown links. markdownLink = regexp.MustCompile(`\[(.*)\]\([^)]*\)`) ) @@ -46,9 +46,11 @@ type ManualContent struct { Teams []string } type CommentContent struct { - AutoRules []AutoContent - ManualRules []ManualContent - allSatisfied bool + AutoRules []AutoContent + ManualRules []ManualContent + AutoAllSatisfied bool + ManualAllSatisfied bool + ForceSkip bool } type manualCheckDetails struct { @@ -64,10 +66,10 @@ func getCommentManualChecks(commentBody string) map[string]manualCheckDetails { // For each line that matches the "Manual check" regex. for _, match := range manualCheckLine.FindAllStringSubmatch(commentBody, -1) { description := match[2] - status := match[1] + status := strings.ToLower(match[1]) // if X captured, convert it to x. checkedBy := "" - if len(match) > 4 { - checkedBy = strings.ToLower(match[4]) // if X captured, convert it to x. + if len(match) > 3 { + checkedBy = match[3] } checks[description] = manualCheckDetails{status: status, checkedBy: checkedBy} @@ -261,13 +263,15 @@ func updatePullRequest(gh *client.GitHub, pr *github.PullRequest, content Commen var ( context = "Merge Requirements" targetURL = comment.GetHTMLURL() - state = "failure" - description = "Some requirements are not satisfied yet. See bot comment." + state = "success" + description = "All requirements are satisfied." ) - if content.allSatisfied { - state = "success" - description = "All requirements are satisfied." + if content.ForceSkip { + description = "Bot checks are skipped for this PR." + } else if !content.AutoAllSatisfied || !content.ManualAllSatisfied { + state = "failure" + description = "Some requirements are not satisfied yet. See bot comment." } // Update or create commit status. diff --git a/contribs/github-bot/internal/check/comment.tmpl b/contribs/github-bot/internal/check/comment.tmpl index 4312019dd2e..d9b633a69d5 100644 --- a/contribs/github-bot/internal/check/comment.tmpl +++ b/contribs/github-bot/internal/check/comment.tmpl @@ -1,19 +1,34 @@ -I'm a bot that assists the Gno Core team in maintaining this repository. My role is to ensure that contributors understand and follow our guidelines, helping to streamline the development process. +#### 🛠 PR Checks Summary +{{ if and .AutoRules (not .AutoAllSatisfied) }}{{ range .AutoRules }}{{ if not .Satisfied }} 🔴 {{ .Description }} +{{end}}{{end}}{{ else }}All **Automated Checks** passed. ✅{{end}} -The following requirements must be fulfilled before a pull request can be merged. -Some requirement checks are automated and can be verified by the CI, while others need manual verification by a staff member. +##### Manual Checks (for Reviewers): +{{ if .ManualRules }}{{ range .ManualRules }}- [{{ if .CheckedBy }}x{{ else }} {{ end }}] {{ .Description }}{{ if .CheckedBy }} (checked by @{{ .CheckedBy }}){{ end }} +{{ end }}{{ else }}*No manual checks match this pull request.*{{ end }} -These requirements are defined in this [configuration file](https://github.com/gnolang/gno/tree/master/contribs/github-bot/internal/config/config.go). +
Read More -## Automated Checks +🤖 This bot helps streamline PR reviews by verifying automated checks and providing guidance for contributors and reviewers. +##### ✅ Automated Checks (for Contributors): {{ if .AutoRules }}{{ range .AutoRules }} {{ if .Satisfied }}🟢{{ else }}🔴{{ end }} {{ .Description }} {{ end }}{{ else }}*No automated checks match this pull request.*{{ end }} -## Manual Checks +##### ☑️ Contributor Actions: +1. Fix any issues flagged by automated checks. +2. Follow the Contributor Checklist to ensure your PR is ready for review. + - Add new tests, or document why they are unnecessary. + - Provide clear examples/screenshots, if necessary. + - Update documentation, if required. + - Ensure no breaking changes, or include `BREAKING CHANGE` notes. + - Link related issues/PRs, where applicable. -{{ if .ManualRules }}{{ range .ManualRules }}- [{{ if .CheckedBy }}x{{ else }} {{ end }}] {{ .Description }}{{ if .CheckedBy }} (checked by @{{ .CheckedBy }}){{ end }} -{{ end }}{{ else }}*No manual checks match this pull request.*{{ end }} +##### ☑️ Reviewer Actions: +1. Complete manual checks for the PR, including the guidelines and additional checks if applicable. + +##### 📚 Resources: +- [Report a bug with the bot](https://github.com/gnolang/gno/issues/3238). +- [View the bot’s configuration file](https://github.com/gnolang/gno/tree/master/contribs/github-bot/internal/config/config.go). {{ if or .AutoRules .ManualRules }}
Debug
{{ if .AutoRules }}
Automated Checks
@@ -52,3 +67,4 @@ These requirements are defined in this [configuration file](https://github.com/g {{ end }}
{{ end }} +
diff --git a/contribs/github-bot/internal/check/comment_test.go b/contribs/github-bot/internal/check/comment_test.go index 0334b76f95c..29886f80f43 100644 --- a/contribs/github-bot/internal/check/comment_test.go +++ b/contribs/github-bot/internal/check/comment_test.go @@ -31,31 +31,44 @@ func TestGeneratedComment(t *testing.T) { {Description: "Test automatic 5", Satisfied: false}, } manualRules := []ManualContent{ - {Description: "Test manual 1", CheckedBy: "user_1"}, + {Description: "Test manual 1", CheckedBy: "user-1"}, {Description: "Test manual 2", CheckedBy: ""}, {Description: "Test manual 3", CheckedBy: ""}, - {Description: "Test manual 4", CheckedBy: "user_4"}, - {Description: "Test manual 5", CheckedBy: "user_5"}, + {Description: "Test manual 4", CheckedBy: "user-4"}, + {Description: "Test manual 5", CheckedBy: "user-5"}, } commentText, err := generateComment(content) assert.Nil(t, err, fmt.Sprintf("error is not nil: %v", err)) assert.True(t, strings.Contains(commentText, "*No automated checks match this pull request.*"), "should contains automated check placeholder") assert.True(t, strings.Contains(commentText, "*No manual checks match this pull request.*"), "should contains manual check placeholder") + assert.True(t, strings.Contains(commentText, "All **Automated Checks** passed. ✅"), "should contains automated checks passed placeholder") content.AutoRules = autoRules + content.AutoAllSatisfied = true commentText, err = generateComment(content) assert.Nil(t, err, fmt.Sprintf("error is not nil: %v", err)) assert.False(t, strings.Contains(commentText, "*No automated checks match this pull request.*"), "should not contains automated check placeholder") assert.True(t, strings.Contains(commentText, "*No manual checks match this pull request.*"), "should contains manual check placeholder") + assert.True(t, strings.Contains(commentText, "All **Automated Checks** passed. ✅"), "should contains automated checks passed placeholder") assert.Equal(t, 2, len(autoCheckSuccessLine.FindAllStringSubmatch(commentText, -1)), "wrong number of succeeded automatic check") assert.Equal(t, 3, len(autoCheckFailLine.FindAllStringSubmatch(commentText, -1)), "wrong number of failed automatic check") + content.AutoAllSatisfied = false + commentText, err = generateComment(content) + assert.Nil(t, err, fmt.Sprintf("error is not nil: %v", err)) + assert.False(t, strings.Contains(commentText, "*No automated checks match this pull request.*"), "should not contains automated check placeholder") + assert.True(t, strings.Contains(commentText, "*No manual checks match this pull request.*"), "should contains manual check placeholder") + assert.False(t, strings.Contains(commentText, "All **Automated Checks** passed. ✅"), "should contains automated checks passed placeholder") + assert.Equal(t, 2, len(autoCheckSuccessLine.FindAllStringSubmatch(commentText, -1)), "wrong number of succeeded automatic check") + assert.Equal(t, 3+3, len(autoCheckFailLine.FindAllStringSubmatch(commentText, -1)), "wrong number of failed automatic check") + content.ManualRules = manualRules commentText, err = generateComment(content) assert.Nil(t, err, fmt.Sprintf("error is not nil: %v", err)) assert.False(t, strings.Contains(commentText, "*No automated checks match this pull request.*"), "should not contains automated check placeholder") assert.False(t, strings.Contains(commentText, "*No manual checks match this pull request.*"), "should not contains manual check placeholder") + assert.False(t, strings.Contains(commentText, "All **Automated Checks** passed. ✅"), "should contains automated checks passed placeholder") manualChecks := getCommentManualChecks(commentText) assert.Equal(t, len(manualChecks), len(manualRules), "wrong number of manual checks found") diff --git a/contribs/github-bot/internal/config/config.go b/contribs/github-bot/internal/config/config.go index 2d595c7ce51..fd29f5e5f57 100644 --- a/contribs/github-bot/internal/config/config.go +++ b/contribs/github-bot/internal/config/config.go @@ -22,6 +22,10 @@ type ManualCheck struct { Teams Teams // Members of these teams can check the checkbox to make the check pass. } +// This is the description for a persistent rule with a non-standard behavior +// that allow maintainer to force the "success" state of the CI check +const ForceSkipDescription = "**SKIP**: Do not block the CI for this PR" + // This function returns the configuration of the bot consisting of automatic and manual checks // in which the GitHub client is injected. func Config(gh *client.GitHub) ([]AutomaticCheck, []ManualCheck) { @@ -53,6 +57,11 @@ func Config(gh *client.GitHub) ([]AutomaticCheck, []ManualCheck) { } manual := []ManualCheck{ + { + // WARN: Do not edit this special rule which must remain persistent. + Description: ForceSkipDescription, + If: c.Always(), + }, { Description: "The pull request description provides enough details", If: c.Not(c.AuthorInTeam(gh, "core-contributors")), diff --git a/examples/gno.land/r/matijamarjanovic/home/config.gno b/examples/gno.land/r/matijamarjanovic/home/config.gno new file mode 100644 index 00000000000..2a9669c0b58 --- /dev/null +++ b/examples/gno.land/r/matijamarjanovic/home/config.gno @@ -0,0 +1,64 @@ +package home + +import ( + "errors" + "std" +) + +var ( + mainAddr = std.Address("g1ej0qca5ptsw9kfr64ey8jvfy9eacga6mpj2z0y") // matija's main address + backupAddr std.Address // backup address + + errorInvalidAddr = errors.New("config: invalid address") + errorUnauthorized = errors.New("config: unauthorized") +) + +func Address() std.Address { + return mainAddr +} + +func Backup() std.Address { + return backupAddr +} + +func SetAddress(newAddress std.Address) error { + if !newAddress.IsValid() { + return errorInvalidAddr + } + + if err := checkAuthorized(); err != nil { + return err + } + + mainAddr = newAddress + return nil +} + +func SetBackup(newAddress std.Address) error { + if !newAddress.IsValid() { + return errorInvalidAddr + } + + if err := checkAuthorized(); err != nil { + return err + } + + backupAddr = newAddress + return nil +} + +func checkAuthorized() error { + caller := std.GetOrigCaller() + if caller != mainAddr && caller != backupAddr { + return errorUnauthorized + } + + return nil +} + +func AssertAuthorized() { + caller := std.GetOrigCaller() + if caller != mainAddr && caller != backupAddr { + panic(errorUnauthorized) + } +} diff --git a/examples/gno.land/r/matijamarjanovic/home/gno.mod b/examples/gno.land/r/matijamarjanovic/home/gno.mod new file mode 100644 index 00000000000..0457c947c01 --- /dev/null +++ b/examples/gno.land/r/matijamarjanovic/home/gno.mod @@ -0,0 +1 @@ +module gno.land/r/matijamarjanovic/home diff --git a/examples/gno.land/r/matijamarjanovic/home/home.gno b/examples/gno.land/r/matijamarjanovic/home/home.gno new file mode 100644 index 00000000000..3757324108a --- /dev/null +++ b/examples/gno.land/r/matijamarjanovic/home/home.gno @@ -0,0 +1,238 @@ +package home + +import ( + "std" + "strings" + + "gno.land/p/demo/ufmt" + "gno.land/p/moul/md" + "gno.land/r/leon/hof" +) + +var ( + pfp string // link to profile picture + pfpCaption string // profile picture caption + abtMe string + + modernVotes int64 + classicVotes int64 + minimalVotes int64 + currentTheme string + + modernLink string + classicLink string + minimalLink string +) + +func init() { + pfp = "https://static.artzone.ai/media/38734/conversions/IPF9dR7ro7n05CmMLLrXIojycr1qdLFxgutaaanG-w768.webp" + pfpCaption = "My profile picture - Tarantula Nebula" + abtMe = `Motivated Computer Science student with strong + analytical and problem-solving skills. Proficient in + programming and version control, with a high level of + focus and attention to detail. Eager to apply academic + knowledge to real-world projects and contribute to + innovative technology solutions. + In addition to my academic pursuits, + I enjoy traveling and staying active through weightlifting. + I have a keen interest in electronic music and often explore various genres. + I believe in maintaining a balanced lifestyle that complements my professional development.` + + modernVotes = 0 + classicVotes = 0 + minimalVotes = 0 + currentTheme = "classic" + modernLink = "https://www.google.com" + classicLink = "https://www.google.com" + minimalLink = "https://www.google.com" + hof.Register() +} + +func UpdatePFP(url, caption string) { + AssertAuthorized() + pfp = url + pfpCaption = caption +} + +func UpdateAboutMe(col1 string) { + AssertAuthorized() + abtMe = col1 +} + +func maxOfThree(a, b, c int64) int64 { + max := a + if b > max { + max = b + } + if c > max { + max = c + } + return max +} + +func VoteModern() { + ugnotAmount := std.GetOrigSend().AmountOf("ugnot") + votes := ugnotAmount + modernVotes += votes + updateCurrentTheme() +} + +func VoteClassic() { + ugnotAmount := std.GetOrigSend().AmountOf("ugnot") + votes := ugnotAmount + classicVotes += votes + updateCurrentTheme() +} + +func VoteMinimal() { + ugnotAmount := std.GetOrigSend().AmountOf("ugnot") + votes := ugnotAmount + minimalVotes += votes + updateCurrentTheme() +} + +func updateCurrentTheme() { + maxVotes := maxOfThree(modernVotes, classicVotes, minimalVotes) + + if maxVotes == modernVotes { + currentTheme = "modern" + } else if maxVotes == classicVotes { + currentTheme = "classic" + } else { + currentTheme = "minimal" + } +} + +func CollectBalance() { + AssertAuthorized() + + banker := std.GetBanker(std.BankerTypeRealmSend) + ownerAddr := Address() + + banker.SendCoins(std.CurrentRealm().Addr(), ownerAddr, banker.GetCoins(std.CurrentRealm().Addr())) +} + +func Render(path string) string { + var sb strings.Builder + + // Theme-specific header styling + switch currentTheme { + case "modern": + // Modern theme - Clean and minimalist with emojis + sb.WriteString(md.H1("🚀 Matija's Space")) + sb.WriteString(md.Image(pfpCaption, pfp)) + sb.WriteString("\n") + sb.WriteString(md.Italic(pfpCaption)) + sb.WriteString("\n") + sb.WriteString(md.HorizontalRule()) + sb.WriteString(abtMe) + sb.WriteString("\n") + + case "minimal": + // Minimal theme - No emojis, minimal formatting + sb.WriteString(md.H1("Matija Marjanovic")) + sb.WriteString("\n") + sb.WriteString(abtMe) + sb.WriteString("\n") + sb.WriteString(md.Image(pfpCaption, pfp)) + sb.WriteString("\n") + sb.WriteString(pfpCaption) + sb.WriteString("\n") + + default: // classic + // Classic theme - Traditional blog style with decorative elements + sb.WriteString(md.H1("✨ Welcome to Matija's Homepage ✨")) + sb.WriteString("\n") + sb.WriteString(md.Image(pfpCaption, pfp)) + sb.WriteString("\n") + sb.WriteString(pfpCaption) + sb.WriteString("\n") + sb.WriteString(md.HorizontalRule()) + sb.WriteString(md.H2("About me")) + sb.WriteString("\n") + sb.WriteString(abtMe) + sb.WriteString("\n") + } + + // Theme-specific voting section + switch currentTheme { + case "modern": + sb.WriteString(md.HorizontalRule()) + sb.WriteString(md.H2("🎨 Theme Selector")) + sb.WriteString("Choose your preferred viewing experience:\n") + items := []string{ + md.Link(ufmt.Sprintf("Modern Design (%d votes)", modernVotes), modernLink), + md.Link(ufmt.Sprintf("Classic Style (%d votes)", classicVotes), classicLink), + md.Link(ufmt.Sprintf("Minimal Look (%d votes)", minimalVotes), minimalLink), + } + sb.WriteString(md.BulletList(items)) + + case "minimal": + sb.WriteString("\n") + sb.WriteString(md.H3("Theme Selection")) + sb.WriteString(ufmt.Sprintf("Current theme: %s\n", currentTheme)) + sb.WriteString(ufmt.Sprintf("Votes - Modern: %d | Classic: %d | Minimal: %d\n", + modernVotes, classicVotes, minimalVotes)) + sb.WriteString(md.Link("Modern", modernLink)) + sb.WriteString(" | ") + sb.WriteString(md.Link("Classic", classicLink)) + sb.WriteString(" | ") + sb.WriteString(md.Link("Minimal", minimalLink)) + sb.WriteString("\n") + + default: // classic + sb.WriteString(md.HorizontalRule()) + sb.WriteString(md.H2("✨ Theme Customization ✨")) + sb.WriteString(md.Bold("Choose Your Preferred Theme:")) + sb.WriteString("\n\n") + items := []string{ + ufmt.Sprintf("Modern 🚀 (%d votes) - %s", modernVotes, md.Link("Vote", modernLink)), + ufmt.Sprintf("Classic ✨ (%d votes) - %s", classicVotes, md.Link("Vote", classicLink)), + ufmt.Sprintf("Minimal ⚡ (%d votes) - %s", minimalVotes, md.Link("Vote", minimalLink)), + } + sb.WriteString(md.BulletList(items)) + } + + // Theme-specific footer/links section + switch currentTheme { + case "modern": + sb.WriteString(md.HorizontalRule()) + sb.WriteString(md.Link("GitHub", "https://github.com/matijamarjanovic")) + sb.WriteString(" | ") + sb.WriteString(md.Link("LinkedIn", "https://www.linkedin.com/in/matijamarjanovic")) + sb.WriteString("\n") + + case "minimal": + sb.WriteString("\n") + sb.WriteString(md.Link("GitHub", "https://github.com/matijamarjanovic")) + sb.WriteString(" | ") + sb.WriteString(md.Link("LinkedIn", "https://www.linkedin.com/in/matijamarjanovic")) + sb.WriteString("\n") + + default: // classic + sb.WriteString(md.HorizontalRule()) + sb.WriteString(md.H3("✨ Connect With Me")) + items := []string{ + md.Link("🌟 GitHub", "https://github.com/matijamarjanovic"), + md.Link("💼 LinkedIn", "https://www.linkedin.com/in/matijamarjanovic"), + } + sb.WriteString(md.BulletList(items)) + } + + return sb.String() +} + +func UpdateModernLink(link string) { + AssertAuthorized() + modernLink = link +} + +func UpdateClassicLink(link string) { + AssertAuthorized() + classicLink = link +} + +func UpdateMinimalLink(link string) { + AssertAuthorized() + minimalLink = link +} diff --git a/examples/gno.land/r/matijamarjanovic/home/home_test.gno b/examples/gno.land/r/matijamarjanovic/home/home_test.gno new file mode 100644 index 00000000000..8cc6e6e5608 --- /dev/null +++ b/examples/gno.land/r/matijamarjanovic/home/home_test.gno @@ -0,0 +1,134 @@ +package home + +import ( + "std" + "strings" + "testing" + + "gno.land/p/demo/uassert" + "gno.land/p/demo/urequire" +) + +// Helper function to set up test environment +func setupTest() { + std.TestSetOrigCaller(std.Address("g1ej0qca5ptsw9kfr64ey8jvfy9eacga6mpj2z0y")) +} + +func TestUpdatePFP(t *testing.T) { + setupTest() + pfp = "" + pfpCaption = "" + + UpdatePFP("https://example.com/pic.png", "New Caption") + + urequire.Equal(t, pfp, "https://example.com/pic.png", "Profile picture URL should be updated") + urequire.Equal(t, pfpCaption, "New Caption", "Profile picture caption should be updated") +} + +func TestUpdateAboutMe(t *testing.T) { + setupTest() + abtMe = "" + + UpdateAboutMe("This is my new bio.") + + urequire.Equal(t, abtMe, "This is my new bio.", "About Me should be updated") +} + +func TestVoteModern(t *testing.T) { + setupTest() + modernVotes, classicVotes, minimalVotes = 0, 0, 0 + + coinsSent := std.NewCoins(std.NewCoin("ugnot", 75000000)) + coinsSpent := std.NewCoins(std.NewCoin("ugnot", 1)) + + std.TestSetOrigSend(coinsSent, coinsSpent) + VoteModern() + + uassert.Equal(t, int64(75000000), modernVotes, "Modern votes should be calculated correctly") + uassert.Equal(t, "modern", currentTheme, "Theme should be updated to modern") +} + +func TestVoteClassic(t *testing.T) { + setupTest() + modernVotes, classicVotes, minimalVotes = 0, 0, 0 + + coinsSent := std.NewCoins(std.NewCoin("ugnot", 75000000)) + coinsSpent := std.NewCoins(std.NewCoin("ugnot", 1)) + + std.TestSetOrigSend(coinsSent, coinsSpent) + VoteClassic() + + uassert.Equal(t, int64(75000000), classicVotes, "Classic votes should be calculated correctly") + uassert.Equal(t, "classic", currentTheme, "Theme should be updated to classic") +} + +func TestVoteMinimal(t *testing.T) { + setupTest() + modernVotes, classicVotes, minimalVotes = 0, 0, 0 + + coinsSent := std.NewCoins(std.NewCoin("ugnot", 75000000)) + coinsSpent := std.NewCoins(std.NewCoin("ugnot", 1)) + + std.TestSetOrigSend(coinsSent, coinsSpent) + VoteMinimal() + + uassert.Equal(t, int64(75000000), minimalVotes, "Minimal votes should be calculated correctly") + uassert.Equal(t, "minimal", currentTheme, "Theme should be updated to minimal") +} + +func TestRender(t *testing.T) { + setupTest() + // Reset the state to known values + modernVotes, classicVotes, minimalVotes = 0, 0, 0 + currentTheme = "classic" + pfp = "https://example.com/pic.png" + pfpCaption = "Test Caption" + abtMe = "Test About Me" + + out := Render("") + urequire.NotEqual(t, out, "", "Render output should not be empty") + + // Test classic theme specific content + uassert.True(t, strings.Contains(out, "✨ Welcome to Matija's Homepage ✨"), "Classic theme should have correct header") + uassert.True(t, strings.Contains(out, pfp), "Should contain profile picture URL") + uassert.True(t, strings.Contains(out, pfpCaption), "Should contain profile picture caption") + uassert.True(t, strings.Contains(out, "About me"), "Should contain About me section") + uassert.True(t, strings.Contains(out, abtMe), "Should contain about me content") + uassert.True(t, strings.Contains(out, "Theme Customization"), "Should contain theme customization section") + uassert.True(t, strings.Contains(out, "Connect With Me"), "Should contain connect section") +} + +func TestRenderModernTheme(t *testing.T) { + setupTest() + modernVotes, classicVotes, minimalVotes = 100, 0, 0 + currentTheme = "modern" + updateCurrentTheme() + + out := Render("") + uassert.True(t, strings.Contains(out, "🚀 Matija's Space"), "Modern theme should have correct header") +} + +func TestRenderMinimalTheme(t *testing.T) { + setupTest() + modernVotes, classicVotes, minimalVotes = 0, 0, 100 + currentTheme = "minimal" + updateCurrentTheme() + + out := Render("") + uassert.True(t, strings.Contains(out, "Matija Marjanovic"), "Minimal theme should have correct header") +} + +func TestUpdateLinks(t *testing.T) { + setupTest() + + newLink := "https://example.com/vote" + + UpdateModernLink(newLink) + urequire.Equal(t, modernLink, newLink, "Modern link should be updated") + + UpdateClassicLink(newLink) + urequire.Equal(t, classicLink, newLink, "Classic link should be updated") + + UpdateMinimalLink(newLink) + urequire.Equal(t, minimalLink, newLink, "Minimal link should be updated") +} diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 78b11a4ebc5..a3e498710bb 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -743,7 +743,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { for i, cx := range n.Cases { cx = Preprocess( store, last, cx).(Expr) - checkOrConvertType(store, last, &cx, tt, false) // #nosec G601 + checkOrConvertType(store, last, n, &cx, tt, false) // #nosec G601 n.Cases[i] = cx } } @@ -882,7 +882,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // Preprocess and convert tag if const. if n.X != nil { n.X = Preprocess(store, last, n.X).(Expr) - convertIfConst(store, last, n.X) + convertIfConst(store, last, n, n.X) } } return n, TRANS_CONTINUE @@ -1102,10 +1102,10 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // First, convert untyped as necessary. if !shouldSwapOnSpecificity(lcx.T, rcx.T) { // convert n.Left to right type. - checkOrConvertType(store, last, &n.Left, rcx.T, false) + checkOrConvertType(store, last, n, &n.Left, rcx.T, false) } else { // convert n.Right to left type. - checkOrConvertType(store, last, &n.Right, lcx.T, false) + checkOrConvertType(store, last, n, &n.Right, lcx.T, false) } // Then, evaluate the expression. cx := evalConst(store, last, n) @@ -1125,7 +1125,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { rnt.String())) } // convert n.Left to pt type, - checkOrConvertType(store, last, &n.Left, pt, false) + checkOrConvertType(store, last, n, &n.Left, pt, false) // if check pass, convert n.Right to (gno) pt type, rn := Expr(Call(pt.String(), n.Right)) // and convert result back. @@ -1154,7 +1154,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { } if !isUntyped(rt) { // right is typed - checkOrConvertType(store, last, &n.Left, rt, false) + checkOrConvertType(store, last, n, &n.Left, rt, false) } else { if shouldSwapOnSpecificity(lt, rt) { checkUntypedShiftExpr(n.Right) @@ -1165,10 +1165,10 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { } } else if lcx.T == nil { // LHS is nil. // convert n.Left to typed-nil type. - checkOrConvertType(store, last, &n.Left, rt, false) + checkOrConvertType(store, last, n, &n.Left, rt, false) } else { if isUntyped(rt) { - checkOrConvertType(store, last, &n.Right, lt, false) + checkOrConvertType(store, last, n, &n.Right, lt, false) } } } else if ric { // right is const, left is not @@ -1186,7 +1186,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // convert n.Left to (gno) pt type, ln := Expr(Call(pt.String(), n.Left)) // convert n.Right to pt type, - checkOrConvertType(store, last, &n.Right, pt, false) + checkOrConvertType(store, last, n, &n.Right, pt, false) // and convert result back. tx := constType(n, lnt) // reset/create n2 to preprocess left child. @@ -1212,7 +1212,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { } // both untyped, e.g. 1< float64. // (const) untyped bigint -> int. if !constConverted { - convertConst(store, last, arg0, nil) + convertConst(store, last, n, arg0, nil) } // evaluate the new expression. cx := evalConst(store, last, n) @@ -1397,15 +1397,15 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { if isUntyped(at) { switch arg0.Op { case EQL, NEQ, LSS, GTR, LEQ, GEQ: - assertAssignableTo(at, ct, false) + assertAssignableTo(n, at, ct, false) break default: - checkOrConvertType(store, last, &n.Args[0], ct, false) + checkOrConvertType(store, last, n, &n.Args[0], ct, false) } } case *UnaryExpr: if isUntyped(at) { - checkOrConvertType(store, last, &n.Args[0], ct, false) + checkOrConvertType(store, last, n, &n.Args[0], ct, false) } default: // do nothing @@ -1549,7 +1549,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { panic("should not happen") } // Specify function param/result generics. - sft := ft.Specify(store, argTVs, isVarg) + sft := ft.Specify(store, n, argTVs, isVarg) spts := sft.Params srts := FieldTypeList(sft.Results).Types() // If generics were specified, override attr @@ -1575,12 +1575,12 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { for i, tv := range argTVs { if hasVarg { if (len(spts) - 1) <= i { - assertAssignableTo(tv.T, spts[len(spts)-1].Type.Elem(), true) + assertAssignableTo(n, tv.T, spts[len(spts)-1].Type.Elem(), true) } else { - assertAssignableTo(tv.T, spts[i].Type, true) + assertAssignableTo(n, tv.T, spts[i].Type, true) } } else { - assertAssignableTo(tv.T, spts[i].Type, true) + assertAssignableTo(n, tv.T, spts[i].Type, true) } } } else { @@ -1591,16 +1591,16 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { if len(spts) <= i { panic("expected final vargs slice but got many") } - checkOrConvertType(store, last, &n.Args[i], spts[i].Type, true) + checkOrConvertType(store, last, n, &n.Args[i], spts[i].Type, true) } else { - checkOrConvertType(store, last, &n.Args[i], + checkOrConvertType(store, last, n, &n.Args[i], spts[len(spts)-1].Type.Elem(), true) } } else { - checkOrConvertType(store, last, &n.Args[i], spts[i].Type, true) + checkOrConvertType(store, last, n, &n.Args[i], spts[i].Type, true) } } else { - checkOrConvertType(store, last, &n.Args[i], spts[i].Type, true) + checkOrConvertType(store, last, n, &n.Args[i], spts[i].Type, true) } } } @@ -1621,10 +1621,10 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { case StringKind, ArrayKind, SliceKind: // Replace const index with int *ConstExpr, // or if not const, assert integer type.. - checkOrConvertIntegerKind(store, last, n.Index) + checkOrConvertIntegerKind(store, last, n, n.Index) case MapKind: mt := baseOf(gnoTypeOf(store, dt)).(*MapType) - checkOrConvertType(store, last, &n.Index, mt.Key, false) + checkOrConvertType(store, last, n, &n.Index, mt.Key, false) default: panic(fmt.Sprintf( "unexpected index base kind for type %s", @@ -1635,15 +1635,15 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { case *SliceExpr: // Replace const L/H/M with int *ConstExpr, // or if not const, assert integer type.. - checkOrConvertIntegerKind(store, last, n.Low) - checkOrConvertIntegerKind(store, last, n.High) - checkOrConvertIntegerKind(store, last, n.Max) + checkOrConvertIntegerKind(store, last, n, n.Low) + checkOrConvertIntegerKind(store, last, n, n.High) + checkOrConvertIntegerKind(store, last, n, n.Max) // if n.X is untyped, convert to corresponding type t := evalStaticTypeOf(store, last, n.X) if isUntyped(t) { dt := defaultTypeOf(t) - checkOrConvertType(store, last, &n.X, dt, false) + checkOrConvertType(store, last, n, &n.X, dt, false) } // TRANS_LEAVE ----------------------- @@ -1722,28 +1722,28 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { key := n.Elts[i].Key.(*NameExpr).Name path := cclt.GetPathForName(key) ft := cclt.GetStaticTypeOfAt(path) - checkOrConvertType(store, last, &n.Elts[i].Value, ft, false) + checkOrConvertType(store, last, n, &n.Elts[i].Value, ft, false) } } else { for i := 0; i < len(n.Elts); i++ { ft := cclt.Fields[i].Type - checkOrConvertType(store, last, &n.Elts[i].Value, ft, false) + checkOrConvertType(store, last, n, &n.Elts[i].Value, ft, false) } } case *ArrayType: for i := 0; i < len(n.Elts); i++ { - convertType(store, last, &n.Elts[i].Key, IntType) - checkOrConvertType(store, last, &n.Elts[i].Value, cclt.Elt, false) + convertType(store, last, n, &n.Elts[i].Key, IntType) + checkOrConvertType(store, last, n, &n.Elts[i].Value, cclt.Elt, false) } case *SliceType: for i := 0; i < len(n.Elts); i++ { - convertType(store, last, &n.Elts[i].Key, IntType) - checkOrConvertType(store, last, &n.Elts[i].Value, cclt.Elt, false) + convertType(store, last, n, &n.Elts[i].Key, IntType) + checkOrConvertType(store, last, n, &n.Elts[i].Value, cclt.Elt, false) } case *MapType: for i := 0; i < len(n.Elts); i++ { - checkOrConvertType(store, last, &n.Elts[i].Key, cclt.Key, false) - checkOrConvertType(store, last, &n.Elts[i].Value, cclt.Value, false) + checkOrConvertType(store, last, n, &n.Elts[i].Key, cclt.Key, false) + checkOrConvertType(store, last, n, &n.Elts[i].Value, cclt.Value, false) } case *NativeType: clt = cclt.GnoType(store) @@ -1943,7 +1943,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // TRANS_LEAVE ----------------------- case *FieldTypeExpr: // Replace const Tag with default *ConstExpr. - convertIfConst(store, last, n.Tag) + convertIfConst(store, last, n, n.Tag) // TRANS_LEAVE ----------------------- case *ArrayTypeExpr: @@ -1952,7 +1952,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { } else { // Replace const Len with int *ConstExpr. cx := evalConst(store, last, n.Len) - convertConst(store, last, cx, IntType) + convertConst(store, last, n, cx, IntType) n.Len = cx } // NOTE: For all TypeExprs, the node is not replaced @@ -1993,7 +1993,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // Rhs consts become default *ConstExprs. for _, rx := range n.Rhs { // NOTE: does nothing if rx is "nil". - convertIfConst(store, last, rx) + convertIfConst(store, last, n, rx) } nameExprs := make(NameExprs, len(n.Lhs)) @@ -2001,7 +2001,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { nameExprs[i] = *n.Lhs[i].(*NameExpr) } - defineOrDecl(store, last, false, nameExprs, nil, n.Rhs) + defineOrDecl(store, last, n, false, nameExprs, nil, n.Rhs) } else { // ASSIGN, or assignment operation (+=, -=, <<=, etc.) // NOTE: Keep in sync with DEFINE above. if len(n.Lhs) > len(n.Rhs) { @@ -2090,11 +2090,11 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { } else { // len(Lhs) == len(Rhs) if n.Op == SHL_ASSIGN || n.Op == SHR_ASSIGN { // Special case if shift assign <<= or >>=. - convertType(store, last, &n.Rhs[0], UintType) + convertType(store, last, n, &n.Rhs[0], UintType) } else if n.Op == ADD_ASSIGN || n.Op == SUB_ASSIGN || n.Op == MUL_ASSIGN || n.Op == QUO_ASSIGN || n.Op == REM_ASSIGN { // e.g. a += b, single value for lhs and rhs, lt := evalStaticTypeOf(store, last, n.Lhs[0]) - checkOrConvertType(store, last, &n.Rhs[0], lt, true) + checkOrConvertType(store, last, n, &n.Rhs[0], lt, true) } else { // all else, like BAND_ASSIGN, etc // General case: a, b = x, y. for i, lx := range n.Lhs { @@ -2104,7 +2104,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { } // if lt is interface, nothing will happen - checkOrConvertType(store, last, &n.Rhs[i], lt, true) + checkOrConvertType(store, last, n, &n.Rhs[i], lt, true) } } } @@ -2181,12 +2181,12 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // TRANS_LEAVE ----------------------- case *ForStmt: // Cond consts become bool *ConstExprs. - checkOrConvertBoolKind(store, last, n.Cond) + checkOrConvertBoolKind(store, last, n, n.Cond) // TRANS_LEAVE ----------------------- case *IfStmt: // Cond consts become bool *ConstExprs. - checkOrConvertBoolKind(store, last, n.Cond) + checkOrConvertBoolKind(store, last, n, n.Cond) // TRANS_LEAVE ----------------------- case *RangeStmt: @@ -2242,7 +2242,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // XXX how to deal? panic("not yet implemented") } else { - checkOrConvertType(store, last, &n.Results[i], rt, false) + checkOrConvertType(store, last, n, &n.Results[i], rt, false) } } } @@ -2250,7 +2250,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // TRANS_LEAVE ----------------------- case *SendStmt: // Value consts become default *ConstExprs. - checkOrConvertType(store, last, &n.Value, nil, false) + checkOrConvertType(store, last, n, &n.Value, nil, false) // TRANS_LEAVE ----------------------- case *SelectCaseStmt: @@ -2303,7 +2303,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // runDeclaration(), as this uses OpStaticTypeOf. } - defineOrDecl(store, last, n.Const, n.NameExprs, n.Type, n.Values) + defineOrDecl(store, last, n, n.Const, n.NameExprs, n.Type, n.Values) // TODO make note of constance in static block for // future use, or consider "const paths". set as @@ -2383,6 +2383,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { func defineOrDecl( store Store, bn BlockNode, + n Node, isConst bool, nameExprs []NameExpr, typeExpr Expr, @@ -2399,9 +2400,9 @@ func defineOrDecl( tvs := make([]TypedValue, numNames) if numVals == 1 && numNames > 1 { - parseMultipleAssignFromOneExpr(sts, tvs, store, bn, nameExprs, typeExpr, valueExprs[0]) + parseMultipleAssignFromOneExpr(store, bn, n, sts, tvs, nameExprs, typeExpr, valueExprs[0]) } else { - parseAssignFromExprList(sts, tvs, store, bn, isConst, nameExprs, typeExpr, valueExprs) + parseAssignFromExprList(store, bn, n, sts, tvs, isConst, nameExprs, typeExpr, valueExprs) } node := skipFile(bn) @@ -2420,10 +2421,11 @@ func defineOrDecl( // parseAssignFromExprList parses assignment to multiple variables from a list of expressions. // This function will alter the value of sts, tvs. func parseAssignFromExprList( - sts []Type, - tvs []TypedValue, store Store, bn BlockNode, + n Node, + sts []Type, + tvs []TypedValue, isConst bool, nameExprs []NameExpr, typeExpr Expr, @@ -2450,7 +2452,7 @@ func parseAssignFromExprList( } // Convert if const to nt. for i := range valueExprs { - checkOrConvertType(store, bn, &valueExprs[i], nt, false) + checkOrConvertType(store, bn, n, &valueExprs[i], nt, false) } } else if isConst { // Derive static type from values. @@ -2462,10 +2464,10 @@ func parseAssignFromExprList( // Convert n.Value to default type. for i, vx := range valueExprs { if cx, ok := vx.(*ConstExpr); ok { - convertConst(store, bn, cx, nil) + convertConst(store, bn, n, cx, nil) // convertIfConst(store, last, vx) } else { - checkOrConvertType(store, bn, &vx, nil, false) + checkOrConvertType(store, bn, n, &vx, nil, false) } vt := evalStaticTypeOf(store, bn, vx) sts[i] = vt @@ -2506,10 +2508,11 @@ func parseAssignFromExprList( // - a, b := n.(T) // - a, b := n[i], where n is a map func parseMultipleAssignFromOneExpr( - sts []Type, - tvs []TypedValue, store Store, bn BlockNode, + n Node, + sts []Type, + tvs []TypedValue, nameExprs []NameExpr, typeExpr Expr, valueExpr Expr, @@ -2567,7 +2570,7 @@ func parseMultipleAssignFromOneExpr( if st != nil { tt := tuple.Elts[i] - if checkAssignableTo(tt, st, false) != nil { + if checkAssignableTo(n, tt, st, false) != nil { panic( fmt.Sprintf( "cannot use %v (value of type %s) as %s value in assignment", @@ -3491,14 +3494,14 @@ func isConstType(x Expr) bool { } // check before convert type -func checkOrConvertType(store Store, last BlockNode, x *Expr, t Type, autoNative bool) { +func checkOrConvertType(store Store, last BlockNode, n Node, x *Expr, t Type, autoNative bool) { if debug { debug.Printf("checkOrConvertType, *x: %v:, t:%v \n", *x, t) } if cx, ok := (*x).(*ConstExpr); ok { if _, ok := t.(*NativeType); !ok { // not native type, refer to time4_native.gno. // e.g. int(1) == int8(1) - assertAssignableTo(cx.T, t, autoNative) + assertAssignableTo(n, cx.T, t, autoNative) } } else if bx, ok := (*x).(*BinaryExpr); ok && (bx.Op == SHL || bx.Op == SHR) { xt := evalStaticTypeOf(store, last, *x) @@ -3507,22 +3510,22 @@ func checkOrConvertType(store Store, last BlockNode, x *Expr, t Type, autoNative } if isUntyped(xt) { // check assignable first, see: types/shift_b6.gno - assertAssignableTo(xt, t, autoNative) + assertAssignableTo(n, xt, t, autoNative) if t == nil || t.Kind() == InterfaceKind { t = defaultTypeOf(xt) } bx.assertShiftExprCompatible2(t) - checkOrConvertType(store, last, &bx.Left, t, autoNative) + checkOrConvertType(store, last, n, &bx.Left, t, autoNative) } else { - assertAssignableTo(xt, t, autoNative) + assertAssignableTo(n, xt, t, autoNative) } return } else if *x != nil { xt := evalStaticTypeOf(store, last, *x) if t != nil { - assertAssignableTo(xt, t, autoNative) + assertAssignableTo(n, xt, t, autoNative) } if isUntyped(xt) { // Push type into expr if qualifying binary expr. @@ -3534,8 +3537,8 @@ func checkOrConvertType(store Store, last BlockNode, x *Expr, t Type, autoNative rt := evalStaticTypeOf(store, last, bx.Right) if t != nil { // push t into bx.Left and bx.Right - checkOrConvertType(store, last, &bx.Left, t, autoNative) - checkOrConvertType(store, last, &bx.Right, t, autoNative) + checkOrConvertType(store, last, n, &bx.Left, t, autoNative) + checkOrConvertType(store, last, n, &bx.Right, t, autoNative) return } else { if shouldSwapOnSpecificity(lt, rt) { @@ -3546,11 +3549,11 @@ func checkOrConvertType(store Store, last BlockNode, x *Expr, t Type, autoNative // without a specific context type, '1.0< + (const (undefined)) (mismatched types int and untyped nil) diff --git a/gnovm/tests/files/assign38.gno b/gnovm/tests/files/assign38.gno new file mode 100644 index 00000000000..5ef3549ccf6 --- /dev/null +++ b/gnovm/tests/files/assign38.gno @@ -0,0 +1,10 @@ +package main + +func main() { + a := 1 + a = nil + println(a) +} + +// Error: +// main/files/assign38.gno:5:2: cannot use nil as int value in assignment diff --git a/gnovm/tests/files/block0.gno b/gnovm/tests/files/block0.gno new file mode 100644 index 00000000000..b6d554ce500 --- /dev/null +++ b/gnovm/tests/files/block0.gno @@ -0,0 +1,14 @@ +package main + +func foo() int { + { + return 1 + } +} + +func main() { + println(foo()) +} + +// Output: +// 1 diff --git a/gnovm/tests/files/fun28.gno b/gnovm/tests/files/fun28.gno new file mode 100644 index 00000000000..cf969f9f34b --- /dev/null +++ b/gnovm/tests/files/fun28.gno @@ -0,0 +1,10 @@ +package main + +func f(i int) {} + +func main() { + f(nil) +} + +// Error: +// main/files/fun28.gno:6:2: cannot use nil as int value in argument to f diff --git a/gnovm/tests/files/slice3.gno b/gnovm/tests/files/slice3.gno new file mode 100644 index 00000000000..1132da01420 --- /dev/null +++ b/gnovm/tests/files/slice3.gno @@ -0,0 +1,9 @@ +package main + +func main() { + i := []string{nil} + println(i) +} + +// Error: +// main/files/slice3.gno:4:7: cannot use nil as string value in array, slice literal or map literal diff --git a/gnovm/tests/files/type40.gno b/gnovm/tests/files/type40.gno index 65210798007..fe312e220e0 100644 --- a/gnovm/tests/files/type40.gno +++ b/gnovm/tests/files/type40.gno @@ -43,4 +43,4 @@ func main() { // 5 // 6 // 7 -// yo \ No newline at end of file +// yo diff --git a/gnovm/tests/files/type41.gno b/gnovm/tests/files/type41.gno new file mode 100644 index 00000000000..ea1a3b1df24 --- /dev/null +++ b/gnovm/tests/files/type41.gno @@ -0,0 +1,9 @@ +package main + +type A nil + +func main() { +} + +// Error: +// main/files/type41.gno:3:6: nil is not a type diff --git a/gnovm/tests/files/var35.gno b/gnovm/tests/files/var35.gno new file mode 100644 index 00000000000..87b1cc68590 --- /dev/null +++ b/gnovm/tests/files/var35.gno @@ -0,0 +1,8 @@ +package main + +func main() { + var i int = nil +} + +// Error: +// main/files/var35.gno:4:6: cannot use nil as int value in variable declaration diff --git a/misc/loop/go.mod b/misc/loop/go.mod index 70e9d21734b..af7783e57bb 100644 --- a/misc/loop/go.mod +++ b/misc/loop/go.mod @@ -8,7 +8,7 @@ require ( github.com/docker/docker v24.0.7+incompatible github.com/docker/go-connections v0.4.0 github.com/gnolang/gno v0.1.0-nightly.20240627 - github.com/gnolang/tx-archive v0.4.0 + github.com/gnolang/tx-archive v0.4.2 github.com/prometheus/client_golang v1.17.0 github.com/sirupsen/logrus v1.9.3 ) diff --git a/misc/loop/go.sum b/misc/loop/go.sum index 8e0feb11e4a..0d235f2cfb1 100644 --- a/misc/loop/go.sum +++ b/misc/loop/go.sum @@ -68,8 +68,8 @@ github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHqu github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/gnolang/tx-archive v0.4.0 h1:+1Rgo0U0HjLQLq/xqeGdJwtAzo9xWj09t1oZLvrL3bU= -github.com/gnolang/tx-archive v0.4.0/go.mod h1:seKHGnvxUnDgH/mSsCEdwG0dHY/FrpbUm6Hd0+KMd9w= +github.com/gnolang/tx-archive v0.4.2 h1:xBBqLLKY9riv9yxpQgVhItCWxIji2rX6xNFmCY1cEOQ= +github.com/gnolang/tx-archive v0.4.2/go.mod h1:AGUBGO+DCLuKL80a1GJRnpcJ5gxVd9L4jEJXQB9uXp4= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=