A statically-typed Pythonic language for .NET
Sharpy starts with Python's syntax and adds static typing, null safety, tagged unions, and seamless .NET interop — then compiles to idiomatic C# via Roslyn with zero runtime overhead.
# hello.spy
def greet(name: str) -> str:
return f"Hello, {name}!"
def main():
message = greet("World")
print(message)$ sharpyc run hello.spy
Hello, World!Types are checked at compile time. Inside functions, the compiler infers types so you rarely need to write them — but module-level declarations require annotations.
def main():
x = 42 # Inferred as int
pi = 3.14159 # Inferred as float (double)
name = "hello" # Inferred as str
# Module-level requires annotations
counter: int = 0Non-nullable by default. Nullable types are explicit, and the compiler tracks nullability through control flow.
def process(calc: Calculator?, a: int, b: int) -> int:
result: int? = calc?.add(a, b) # Null-conditional
return result ?? 0 # Null coalescing
def check(x: int?) -> None:
if x is not None:
print(x + 10) # Narrowed to int — no unwrap neededTagged unions for safe error handling — no exceptions required.
def find_user(name: str) -> str?:
if name == "Alice":
return Some("alice@example.com")
return None()
def safe_divide(a: int, b: int) -> int !str:
if b == 0:
return Err("division by zero")
return Ok(a // b)
def main():
print(find_user("Alice").unwrap_or("not found")) # alice@example.com
print(safe_divide(10, 3).unwrap_or(0)) # 3No duck typing — implement interfaces explicitly.
interface IDrawable:
def draw(self) -> str: ...
def area(self) -> int: ...
class Circle(IDrawable):
radius: int
def __init__(self, r: int):
self.radius = r
def draw(self) -> str:
return "Drawing Circle"
def area(self) -> int:
return 3 * self.radius * self.radiusTrue value semantics — copies on assignment, allocated on the stack.
struct Point:
x: int
y: int
def __init__(self, x: int, y: int):
self.x = x
self.y = y
def main():
p1 = Point(10, 20)
p2 = p1 # Copy — value semantics
p2.x = 99
print(p1.x) # 10 — original unchangedType-safe generics on classes and functions with bracket syntax.
class Cell[T]:
value: T
def __init__(self, initial: T):
self.value = initial
def get(self) -> T:
return self.value
def identity[T](x: T) -> T:
return x
def main():
c = Cell[int](42)
print(c.get()) # 42
print(identity[str]("hi")) # hiFirst-class property declarations — no @property boilerplate.
class Person:
property name: str
property age: int
def __init__(self, name: str, age: int):
self.name = name
self.age = ageLightweight named types via type aliases.
type Point = tuple[x: float, y: float]
def main():
p: Point = (x=1.0, y=2.0)
print(p.x) # 1.0def describe(value: int):
match value:
case 1:
print("one")
case x if x > 100:
print(f"big: {x}")
case _:
print("other")Import .NET types directly. snake_case calls auto-map to PascalCase .NET methods.
from system import Console
def main():
Console.write_line("Hello from .NET!")
Console.write_line(f"2 + 2 = {2 + 2}")async def fetch_value() -> str:
return "hello async"
async def main():
result: str = await fetch_value()
print(result) # hello asyncClasses, inheritance, decorators, comprehensions, f-strings, generators, lambdas, dunder methods, try/except, match, enum — they all work as you'd expect. Sharpy is designed so that valid Sharpy code reads like Python.
@abstract
class Shape:
name: str
def __init__(self, name: str):
self.name = name
@abstract
def area(self) -> float: ...
class Circle(Shape):
radius: float
def __init__(self, radius: float):
super().__init__("Circle")
self.radius = radius
@override
def area(self) -> float:
return 3.14 * self.radius * self.radius
def fibonacci(n: int) -> int:
a, b = (0, 1)
i = 0
while i < n:
yield a
a, b = (b, a + b)
i += 1
def main():
c = Circle(4.0)
print(f"{c.name}: {c.area()}") # Circle: 50.24
doubled = [x * 2 for x in range(5)]
evens = {x for x in range(10) if x % 2 == 0}
squares = {x: x * x for x in range(5)}- .NET 10.0 SDK (Download)
git clone https://github.com/antonsynd/sharpy.git
cd sharpy
dotnet build sharpy.sln
dotnet test# Compile and execute
dotnet run --project src/Sharpy.Cli -- run hello.spy
# View generated C#
dotnet run --project src/Sharpy.Cli -- emit csharp hello.spy
# View parsed AST
dotnet run --project src/Sharpy.Cli -- emit ast hello.spy
# Multi-file project
dotnet run --project src/Sharpy.Cli -- project path/to/project.spyprojSharpy follows three axioms in strict priority order:
| Priority | Axiom | Meaning |
|---|---|---|
| 1 | .NET | Always compiles to valid C# for the CLR |
| 2 | Types | Statically typed, non-nullable by default |
| 3 | Python | Syntax and idioms yield to the above when conflicts arise |
- Documentation Site - Full documentation (language reference, stdlib API, tooling)
- Try Sharpy Online - Browser-based playground
- Language Specification - Complete language reference (source)
- Contributing Guide - How to contribute
sharpy/
├── src/
│ ├── Sharpy.Compiler/ # Compiler (lexer, parser, semantic, codegen)
│ ├── Sharpy.Core/ # Standard library (runtime)
│ ├── Sharpy.Cli/ # CLI tool
│ ├── Sharpy.Lsp/ # Language Server Protocol server
│ ├── Sharpy.Compiler.Tests/ # 4,914 test fixtures + unit tests
│ ├── Sharpy.Compiler.Benchmarks/ # Performance benchmarks
│ ├── Sharpy.Core.Tests/ # Runtime library tests
│ └── Sharpy.Lsp.Tests/ # LSP server tests
├── editors/vscode/ # VS Code extension
├── docs/language_specification/ # Authoritative language specification
└── build_tools/ # Build automation and dogfooding tools
Sharpy includes a Language Server Protocol (LSP) server for IDE integration.
Install the Sharpy extension from the marketplace or build from editors/vscode/.
Any editor supporting LSP can connect to the Sharpy language server:
sharpyc lspSee docs/tooling/editor-integration.md for configuration guides for Neovim, Emacs, Sublime Text, Helix, and Zed.
MIT License - see LICENSE for details.
Links: GitHub · Documentation · Playground · Issues