Skip to content

Commit 9e2e694

Browse files
committed
Merge branch 'allow-and-bang-typed-bindings' of github.com:edgarfgp/fsharp into allow-and-bang-typed-bindings
2 parents b5a9e9f + 81c0aba commit 9e2e694

File tree

107 files changed

+13425
-13361
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

107 files changed

+13425
-13361
lines changed

.config/service-branch-merge.json

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@
1212
"release/dev17.14": {
1313
"MergeToBranch": "main",
1414
"ExtraSwitches": "-QuietComments"
15-
},
16-
"main": {
17-
"MergeToBranch": "release/dev18.0",
18-
"ExtraSwitches": "-QuietComments"
1915
}
2016
}
2117
}

.github/workflows/branch-merge.yml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,3 @@ jobs:
1616
uses: dotnet/arcade/.github/workflows/inter-branch-merge-base.yml@main
1717
with:
1818
configuration_file_path: '.config/service-branch-merge.json'
19-
feature-lsp-flow:
20-
uses: dotnet/arcade/.github/workflows/inter-branch-merge-base.yml@main
21-
with:
22-
configuration_file_path: '.config/feature-lsp-branch-merge.json'

.github/workflows/commands.yml

Lines changed: 170 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,131 +1,182 @@
1-
name: Commands on PR
1+
name: Run CLI Commands via PR Comment
2+
23
on:
34
issue_comment:
45
types: [created]
5-
schedule:
6-
# once a day at 13:00 UTC
7-
- cron: '0 13 * * *'
8-
9-
permissions:
10-
contents: write
11-
issues: write
12-
pull-requests: read
136

147
jobs:
15-
cleanup_old_runs:
16-
if: github.event.schedule == '0 13 * * *'
8+
# This first job by definiton runs user-supplied code - you must NOT elevate its permissions to `write`
9+
# Malicious code could change nuget source URL, build targets or even compiler itself to pass a GH token
10+
# And use it to create branches, spam issues etc. Any write-actions happen in the second job, which does not allow
11+
# user extension points (i.e. plain scripts, must NOT run scripts from within checked-out code)
12+
detect-and-run:
1713
runs-on: ubuntu-latest
18-
permissions:
19-
actions: write
20-
env:
21-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
14+
outputs:
15+
command: ${{ steps.parse.outputs.command }}
16+
arg: ${{ steps.parse.outputs.arguments }}
17+
if: github.event.issue.pull_request
2218
steps:
23-
- name: Delete old workflow runs
24-
run: |
25-
_UrlPath="/repos/$GITHUB_REPOSITORY/actions/workflows"
26-
_CurrentWorkflowID="$(gh api -X GET "$_UrlPath" | jq '.workflows[] | select(.name == '\""$GITHUB_WORKFLOW"\"') | .id')"
19+
- name: Parse comment
20+
id: parse
21+
uses: dotnet/comment-pipeline@e08a11834acf1e825ac727b732ac9d4cb8120c51
22+
with:
23+
comment: ${{ toJSON(github.event.comment) }}
24+
commands: |
25+
/run fantomas
26+
/run ilverify
27+
/run xlf
28+
/run test-baseline
29+
github-token: ${{ secrets.GITHUB_TOKEN }}
2730

28-
# delete workitems which are 'completed'. (other candidate values of status field are: 'queued' and 'in_progress')
31+
- name: Checkout the repository
32+
uses: actions/checkout@v4
33+
34+
- name: Checkout PR branch
35+
if: ${{ steps.parse.outputs.command }}
36+
run: gh auth setup-git && gh pr checkout ${{ github.event.issue.number }}
37+
env:
38+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2939

30-
gh api -X GET "$_UrlPath/$_CurrentWorkflowID/runs" --paginate \
31-
| jq '.workflow_runs[] | select(.status == "completed") | .id' \
32-
| xargs -I{} gh api -X DELETE "/repos/$GITHUB_REPOSITORY/actions/runs"/{}
40+
- name: Install dotnet
41+
uses: actions/setup-dotnet@v3
42+
with:
43+
global-json-file: global.json
44+
45+
- name: Install dotnet tools
46+
run: dotnet tool restore
47+
48+
- name: Setup .NET 9.0.0 Runtime for test execution
49+
if: ${{ steps.parse.outputs.command == '/run test-baseline' }}
50+
uses: actions/setup-dotnet@v4
51+
with:
52+
dotnet-version: '9.0.x'
3353

34-
run_command:
35-
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/run')
54+
- name: Run command
55+
id: run-cmd
56+
env:
57+
TEST_UPDATE_BSL: 1
58+
continue-on-error: true
59+
run: |
60+
case "${{ steps.parse.outputs.command }}" in
61+
"/run fantomas") dotnet fantomas . ;;
62+
"/run xlf") dotnet build src/Compiler /t:UpdateXlf ;;
63+
"/run ilverify") pwsh tests/ILVerify/ilverify.ps1 ;;
64+
"/run test-baseline") dotnet test ./FSharp.Compiler.Service.sln --filter "${{ steps.parse.outputs.arguments }}" -c Release || true ;;
65+
*) echo "Unknown command" && exit 1 ;;
66+
esac
67+
68+
- name: Create patch & metadata
69+
id: meta
70+
if: steps.parse.outputs.command
71+
run: |
72+
echo "run_step_outcome=${{ steps.run-cmd.outcome }}" > result
73+
if [[ "${{ steps.run-cmd.outcome }}" == "success" ]]; then
74+
git diff > repo.patch || true
75+
if [ -s repo.patch ]; then echo "hasPatch=true" >> result; else echo "hasPatch=false" >> result; fi
76+
else
77+
echo "hasPatch=false" >> result
78+
fi
79+
cat result
80+
81+
- name: Upload artifacts
82+
uses: actions/upload-artifact@v4
83+
with:
84+
name: cli-results
85+
path: |
86+
repo.patch
87+
result
88+
89+
apply-and-report:
90+
needs: detect-and-run
3691
runs-on: ubuntu-latest
92+
permissions:
93+
contents: write
94+
pull-requests: write
95+
if: needs.detect-and-run.outputs.command != ''
3796
steps:
38-
- name: Extract command to run
39-
uses: actions/github-script@v3
40-
id: command-extractor
41-
with:
42-
result-encoding: string
43-
script: |
44-
if (context.eventName !== "issue_comment") throw "Error: This action only works on issue_comment events.";
45-
46-
// extract the command to run, allowed characters: a-z, A-Z, digits, hyphen, underscore
47-
const regex = /^\/run ([a-zA-Z\d\-\_]+)/;
48-
command = regex.exec(context.payload.comment.body);
49-
if (command == null) throw "Error: No command found in the trigger phrase.";
50-
51-
return command[1];
52-
- name: Get github ref
53-
uses: actions/github-script@v3
54-
id: get-pr
55-
with:
56-
script: |
57-
const result = await github.pulls.get({
58-
pull_number: context.issue.number,
59-
owner: context.repo.owner,
60-
repo: context.repo.repo,
61-
});
62-
return { "ref": result.data.head.ref, "repository": result.data.head.repo.full_name};
63-
- name: Checkout repo
64-
uses: actions/checkout@v2
65-
with:
66-
repository: ${{ fromJson(steps.get-pr.outputs.result).repository }}
67-
ref: ${{ fromJson(steps.get-pr.outputs.result).ref }}
68-
fetch-depth: 0
69-
- name: Install dotnet
70-
uses: actions/setup-dotnet@v3
71-
with:
72-
global-json-file: global.json
73-
- name: Install dotnet tools
74-
run: dotnet tool restore
75-
- name: Process fantomas command
76-
if: steps.command-extractor.outputs.result == 'fantomas'
77-
id: fantomas
78-
run: dotnet fantomas .
79-
- name: Process xlf command
80-
if: steps.command-extractor.outputs.result == 'xlf'
81-
id: xlf
82-
run: dotnet build src/Compiler /t:UpdateXlf
83-
84-
- name: Commit and push changes
85-
if: steps.fantomas.outcome == 'success' || steps.xlf.outcome == 'success' || steps.ilverify.outcome == 'success'
86-
run: |
87-
# Only commit if there are actual changes
88-
if git diff --quiet; then
89-
echo "No changes to commit, skipping."
90-
exit 0
91-
fi
92-
93-
git config --local user.name "github-actions[bot]"
94-
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
95-
git commit -a -m 'Automated command ran: ${{ steps.command-extractor.outputs.result }}
96-
97-
Co-authored-by: ${{ github.event.comment.user.login }} <${{ github.event.comment.user.id }}+${{ github.event.comment.user.login }}@users.noreply.github.com>'
98-
git push origin HEAD:"refs/heads/$PR_HEAD_REF"\
99-
- name: Post command comment
100-
if: steps.fantomas.outcome == 'success' || steps.xlf.outcome == 'success' || steps.ilverify.outcome == 'success'
101-
uses: actions/github-script@v3
102-
with:
103-
script: |
104-
// Probably, there's more universal way of getting outputs, but my gh-actions-fu is not that good.
105-
var output = ""
106-
if ("${{steps.command-extractor.outputs.result}}" == 'fantomas') {
107-
output = "${{steps.fantomas.outputs.result}}"
108-
} else if ("${{steps.command-extractor.outputs.result}}" == 'xlf') {
109-
output = "${{steps.xlf.outputs.result}}"
110-
} else if ("${{steps.command-extractor.outputs.result}}" == 'ilverify') {
111-
output = "${{steps.ilverify.outputs.result}}"
112-
}
113-
const body = `Ran ${{ steps.command-extractor.outputs.result }}: https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.GITHUB_RUN_ID}\n${output}`;
114-
await github.issues.createComment({
115-
issue_number: context.issue.number,
116-
owner: context.repo.owner,
117-
repo: context.repo.repo,
118-
body: body
119-
});
120-
- name: Post command failed comment
121-
if: failure()
122-
uses: actions/github-script@v3
123-
with:
124-
script: |
125-
const body = `Failed to run ${{ steps.command-extractor.outputs.result }}: https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.GITHUB_RUN_ID}`;
126-
await github.issues.createComment({
127-
issue_number: context.issue.number,
128-
owner: context.repo.owner,
129-
repo: context.repo.repo,
130-
body: body
131-
});
97+
- name: Checkout the repository
98+
uses: actions/checkout@v4
99+
100+
- name: Checkout PR branch
101+
run: gh auth setup-git && gh pr checkout ${{ github.event.issue.number }}
102+
env:
103+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
104+
105+
- name: Download artifacts
106+
uses: actions/download-artifact@v4
107+
with:
108+
name: cli-results
109+
110+
- name: Read metadata
111+
id: read-meta
112+
run: |
113+
source result
114+
echo "run_step_outcome=$run_step_outcome" >> $GITHUB_OUTPUT
115+
echo "hasPatch=$hasPatch" >> $GITHUB_OUTPUT
116+
117+
- name: Apply and push patch
118+
if: ${{ steps.read-meta.outputs.run_step_outcome == 'success' && steps.read-meta.outputs.hasPatch == 'true' }}
119+
run: |
120+
patch -p1 -s --force < repo.patch || true
121+
git config user.name "GH Actions"
122+
git config user.email "actions@github.com"
123+
git add -u
124+
git commit -m "Apply patch from ${{ needs.detect-and-run.outputs.command }}"
125+
upstream=$(git rev-parse --abbrev-ref --symbolic-full-name @{u})
126+
remote=${upstream%%/*}
127+
branch=${upstream#*/}
128+
129+
echo "Pushing to $remote $branch"
130+
git push "$remote" HEAD:"$branch"
131+
132+
- name: Count stats
133+
id: stats
134+
if: ${{ steps.read-meta.outputs.run_step_outcome == 'success' && steps.read-meta.outputs.hasPatch == 'true' }}
135+
run: |
136+
files=$(git diff --name-only HEAD~1 HEAD | wc -l)
137+
lines=$(git diff HEAD~1 HEAD | wc -l)
138+
echo "files=$files" >> $GITHUB_OUTPUT
139+
echo "lines=$lines" >> $GITHUB_OUTPUT
140+
- name: Generate and publish report
141+
if: always()
142+
env:
143+
COMMAND: ${{ needs.detect-and-run.outputs.command }}
144+
OUTCOME: ${{ steps.read-meta.outputs.run_step_outcome }}
145+
PATCH: ${{ steps.read-meta.outputs.hasPatch }}
146+
run: |
147+
# Build the markdown report
148+
report="
149+
# 🔧 CLI Command Report
150+
151+
- **Command:** \`${COMMAND}\`
152+
- **Outcome:** ${OUTCOME}
153+
154+
"
155+
156+
if [[ "$OUTCOME" == "success" ]]; then
157+
if [[ "$PATCH" == "true" ]]; then
158+
report+="✅ Patch applied:
159+
- Files changed: ${{ steps.stats.outputs.files }}
160+
- Lines changed: ${{ steps.stats.outputs.lines }}"
161+
else
162+
report+="✅ Command succeeded, no changes needed."
163+
fi
164+
else
165+
report+="❌ Command **failed** — no patch applied."
166+
fi
167+
168+
# Output to GitHub Actions UI
169+
echo "$report" >> "$GITHUB_STEP_SUMMARY"
170+
171+
# Store for use in next step
172+
echo "$report" > pr_report.md
173+
174+
- name: Comment on PR
175+
if: always()
176+
env:
177+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
178+
PR_NUMBER: ${{ env.PR_NUMBER }}
179+
run: |
180+
# Use gh CLI to comment with multi-line markdown
181+
gh pr comment ${{ github.event.issue.number }} \
182+
--body-file pr_report.md

CONTRIBUTING.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,26 @@ If you don't know what a pull request is read this article: <https://help.github
8989
- **DO** submit issues for other features. This facilitates discussion of a feature separately from its implementation, and increases the acceptance rates for pull requests.
9090
- **DO NOT** submit large code formatting changes without discussing with the team first.
9191

92+
#### Repository automation via commands
93+
94+
The following comments in a PR can be used as commands to execute scripts which automate repository maintenance and make it part of the visible diff.
95+
- `/run fantomas` runs `dotnet fantomas .`
96+
- `/run ilverify` updates IL verification baseline
97+
- `/run xlf` refreshes localisation files for translatable strings
98+
- `/run test-baseline ...` runs tests with the `TEST_UPDATE_BSL: 1` environment variable and an argument supplied filter (passed to `dotnet test --filter ..`). Its goal is to refresh baselines.
99+
100+
This code repository uses a lot of baselines - captures for important output - to spot regressions and willingfully accept changes via PR review.
101+
For example, the following errors can appear during CI runs:
102+
- Changes in `Syntax tree tests`
103+
- Differences in generated `IL output`
104+
- Diffrences in produced baseline diagnostics
105+
106+
After identifying a failing test which relies on a baseline, the command can then for example be:
107+
- `/run test-baseline ParseFile` to update parsing tests related to syntactical tree
108+
- `/run test-baseline SurfaceAreaTest` to update the API surface area of FSharp.Compiler.Service
109+
- `/run test-baseline FullyQualifiedName~EmittedIL&FullyQualifiedName~Nullness` to update IL baseline (namespace `EmittedIL`) for tests that touch the `Nullness` feature
110+
111+
92112
### Reviewing pull requests
93113

94114
Our repository gets a high volume of pull requests and reviewing each of them is a significant time commitment. Our team priorities often force us to focus on reviewing a subset of the active pull requests at a given time.

Directory.Build.props

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
<AccelerateBuildsInVisualStudio>true</AccelerateBuildsInVisualStudio>
99
<FSharpNetCoreProductDefaultTargetFramework>net9.0</FSharpNetCoreProductDefaultTargetFramework>
1010
<IgnoreMibc Condition="'$(IgnoreMibc)' == ''">$(DotNetBuildSourceOnly)</IgnoreMibc>
11+
<!-- When building the .NET product, there's no need to publish Windows PDBs. Any conversion to Windows PDBs will be done during staging, if necessary. -->
12+
<PublishWindowsPdb Condition="'$(DotNetBuild)' == 'true'">false</PublishWindowsPdb>
1113
</PropertyGroup>
1214

1315
<!--
@@ -72,7 +74,7 @@
7274
we should also support $(NetPrevious) for all releases.
7375
This will likely include FCS and FSharp.Core as well as shipped products.
7476
Right now, it only covers products we ship (FSC and FSI), not NuGet packages. -->
75-
<When Condition="'$(DotNetBuildSourceOnly)' == 'true' AND '$(DotNetBuildOrchestrator)' == 'true'">
77+
<When Condition="'$(DotNetBuildSourceOnly)' == 'true' AND '$(DotNetBuildFromVMR)' == 'true'">
7678
<PropertyGroup>
7779
<FSharpNetCoreProductTargetFramework>$(NetCurrent)</FSharpNetCoreProductTargetFramework>
7880
</PropertyGroup>

azure-pipelines-PR.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,11 +457,13 @@ stages:
457457
_configuration: Release
458458
_testKind: testFSharpQA
459459
transparentCompiler:
460+
FSHARP_CACHE_OVERRIDE: 256
460461
vs_release:
461462
_configuration: Release
462463
_testKind: testVs
463464
setupVsHive: true
464465
transparentCompiler:
466+
FSHARP_CACHE_OVERRIDE: 256
465467
transparent_compiler_release:
466468
_configuration: Release
467469
_testKind: testCoreclr

0 commit comments

Comments
 (0)