Contributing to Sharpy
April 23, 2026 ยท View on GitHub
Thank you for your interest in contributing to Sharpy! This guide will help you get started.
AI contributors: See CLAUDE.md and .github/copilot-instructions.md for detailed AI-specific guidance.
Quick Start
Prerequisites
- .NET 10 SDK
- Python 3 (for verifying language semantics)
- A C# editor (VS Code with C# Dev Kit, Rider, or Visual Studio)
Build and Test
git clone https://github.com/antonsynd/sharpy.git
cd sharpy
dotnet build sharpy.sln # Build everything
dotnet test # Run all tests (~4400 tests)
Run a Sharpy Program
dotnet run --project src/Sharpy.Cli -- run hello.spy
Inspect Compiler Output
dotnet run --project src/Sharpy.Cli -- emit tokens file.spy # Lexer tokens
dotnet run --project src/Sharpy.Cli -- emit ast file.spy # Parsed AST
dotnet run --project src/Sharpy.Cli -- emit csharp file.spy # Generated C#
dotnet run --project src/Sharpy.Cli -- emit parse file.spy # Validate parse only
dotnet run --project src/Sharpy.Cli -- explain SPY0200 # Explain an error code
Project Structure
Source (.spy) --> Lexer --> Parser (AST) --> Semantic --> ValidationPipeline --> RoslynEmitter --> C# --> .NET IL
| Directory | Purpose |
|---|---|
src/Sharpy.Compiler/Lexer/ | Tokenization, indentation tracking |
src/Sharpy.Compiler/Parser/ | Recursive descent parser, immutable AST records |
src/Sharpy.Compiler/Semantic/ | Name resolution, type checking, import resolution |
src/Sharpy.Compiler/Semantic/Validation/ | Pluggable validation pipeline (control flow, access, protocols) |
src/Sharpy.Compiler/CodeGen/ | Roslyn SyntaxFactory-based C# code generation |
src/Sharpy.Compiler/Diagnostics/ | Error codes, diagnostic reporting |
src/Sharpy.Core/ | Runtime standard library |
src/Sharpy.Cli/ | CLI (sharpyc) using System.CommandLine |
src/Sharpy.Compiler.Tests/ | Unit and integration tests |
docs/language_specification/ | Authoritative language specification |
How to Add a Language Feature
Adding a new feature touches the full pipeline. Here's the 6-step process:
1. Lexer (if new tokens are needed)
File: src/Sharpy.Compiler/Lexer/Lexer.cs
Add new token types to TokenType enum and handle them in the lexer's scanning logic. Most features reuse existing tokens.
2. Parser
Files: src/Sharpy.Compiler/Parser/Parser.cs, src/Sharpy.Compiler/Parser/Ast/*.cs
- Define new AST node(s) as immutable records in
Ast/ - Add parsing logic in the appropriate
Parser.*.cspartial class - AST nodes are immutable -- never add mutable state to them
3. Semantic Analysis
Files: src/Sharpy.Compiler/Semantic/NameResolver.cs, TypeChecker.cs
- Register declarations in
NameResolver(first pass) - Add type checking logic in
TypeChecker(second pass) - Annotations go in
SemanticInfo, never on AST nodes
4. Validation (if new rules are needed)
File: src/Sharpy.Compiler/Semantic/Validation/
- Create a new validator implementing
ISemanticValidator - Register it in
ValidationPipelineFactory.CreateDefault() - Validators are sorted by
Ordervalue
5. Code Generation
Files: src/Sharpy.Compiler/CodeGen/RoslynEmitter*.cs
- Use Roslyn
SyntaxFactoryexclusively -- never string templates - Map types through
TypeMapper.cs - Map names through
NameMangler.cs(snake_casetoPascalCase, dunder methods to .NET equivalents)
6. Tests
- Unit tests for each stage (lexer, parser, semantic, codegen)
- Integration test using
CompileAndExecute()inIntegrationTestBase - File-based test fixture (see below)
How to Add a Validation Rule
- Create a class implementing
ISemanticValidatorinsrc/Sharpy.Compiler/Semantic/Validation/ - Set
NameandOrder(determines execution sequence) - Implement
Validate(Module module, SemanticContext context) - Add the validator to
ValidationPipelineFactory.CreateDefault() - Add a diagnostic code in
DiagnosticCodes.cs - Add an explanation in
DiagnosticExplanations.cs - Write tests
How to Add Error Test Fixtures
File-based tests live in src/Sharpy.Compiler.Tests/Integration/TestFixtures/. They're auto-discovered.
Success test
Create a .spy file and a matching .expected file:
TestFixtures/basics/my_feature.spy # Sharpy source
TestFixtures/basics/my_feature.expected # Expected stdout (exact match)
Error test
Create a .spy file and a matching .error file:
TestFixtures/errors/my_error.spy # Sharpy source that should fail
TestFixtures/errors/my_error.error # Substring that must appear in error message
Skipping a test
Add a .skip file alongside the .spy file to temporarily skip it.
Running specific tests
dotnet test --filter "FullyQualifiedName~FileBasedIntegrationTests" # All file-based
dotnet test --filter "FullyQualifiedName~Lexer" # Lexer tests
dotnet test --filter "DisplayName~my_feature" # By name
Debugging Tips
Emit commands
The emit subcommands are your primary debugging tools:
emit tokens-- see what the lexer producesemit ast-- see the parsed AST structureemit csharp-- see the generated C# codeemit parse-- validate just lexing + parsing
Explain error codes
dotnet run --project src/Sharpy.Cli -- explain SPY0265 # Detailed error explanation
dotnet run --project src/Sharpy.Cli -- explain --list # All documented codes
Compiler logging
dotnet run --project src/Sharpy.Cli -- --log-level Debug run file.spy
dotnet run --project src/Sharpy.Cli -- --log-level Debug --log-file debug.log run file.spy
Verify Python behavior
When in doubt about how a feature should behave, check Python first:
python3 -c "print([1,2,3][-1])"
Code Style and Conventions
General
- C# 9.0 target -- no global usings, file-scoped namespaces, or record structs
- Code formatting runs automatically via Claude Code hooks on save
- Follow existing patterns in the codebase
AST nodes
AST nodes are immutable records with { get; init; } properties:
public record FunctionDef : Statement
{
public string Name { get; init; } = "";
public ImmutableArray<Parameter> Parameters { get; init; }
public TypeAnnotation? ReturnType { get; init; }
public ImmutableArray<Statement> Body { get; init; }
}
Code generation
Always use Roslyn SyntaxFactory:
// Correct
ReturnStatement(LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(42)))
// Wrong - never use string templates
$"return {value};"
Diagnostics
- Define error codes in
DiagnosticCodes.cs - Use the
CompilerDiagnostictype with specific codes - Add explanations for new codes in
DiagnosticExplanations.cs
The Three Axioms
All design decisions follow these axioms in priority order:
| Priority | Axiom | Principle |
|---|---|---|
| Highest | .NET | Sharpy compiles to C# 9.0 for the .NET CLR |
| Medium | Type Safety | Explicit static typing, non-nullable by default |
| Yields | Python Syntax | Sharpy uses Python 3 syntax and idioms |
When axioms conflict, higher-priority axioms win. For example, if Python semantics would require runtime type checking that .NET doesn't support efficiently, the .NET axiom takes precedence.
Before Submitting
dotnet build sharpy.sln-- build succeedsdotnet test-- all tests passdotnet format whitespace-- formatting is clean- Tests cover the change (unit + integration where applicable)
- Never modify
.expectedfiles to make tests pass -- fix the implementation