Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions build_tools/cpython_oracle/ledger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,14 @@ deviations:
spec_ref: docs/language_specification/function_variadic_arguments.md
existing_diagnostic: SPY0220
planned_diagnostic: SPY0472
- id: identifier-letters-bmp-only
category: syntax
audience: python
severity: error
code: SPY0015
spec_ref: docs/language_specification/identifiers.md
existing_diagnostic: SPY0015
planned_diagnostic: null
- id: identity-with-value-types
category: operators
audience: python
Expand Down Expand Up @@ -381,6 +389,14 @@ deviations:
spec_ref: docs/language_specification/function_default_parameters.md
existing_diagnostic: SPY0433
planned_diagnostic: null
- id: leading-zero-decimal-integer
category: syntax
audience: python
severity: none
code: null
spec_ref: docs/language_specification/integer_literals.md
existing_diagnostic: null
planned_diagnostic: null
- id: lru-cache-maxsize-saturates
category: stdlib
audience: python
Expand Down
2 changes: 1 addition & 1 deletion docs/design/gap-discovery-contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ source-scan guards run in the regular suite.

Well-guarded classes needing no new mechanism: lambda-boundary parsing (differential parse oracle,
#1037), unparser fidelity (round-trip property tests), CLR name round-trip (interop sweep + #1040),
deployment closure (`StandaloneDeploymentTests` for the used stdlib closure; `ReferenceDeploymentMatrixTests` for the user-reference axis it lacked, reference source × route with `--no-deps`, `build` and `project` as the assembly-only controls, #2223).
deployment closure (`StandaloneDeploymentTests` for the used stdlib closure; `ReferenceDeploymentMatrixTests` for the user-reference axis it lacked, reference source × route × {default, `--no-deps`} over every producing command, #2223; `build` and `project` deploy since #2313 cell 0).
| Emittable-constant admission (#1762, #1769, #1460) | Every parameter default, decorator argument and module `const` initializer the validator admits is one the emitter prints as a C# constant (`const` / `default`): ONE classifier (`Semantic/Validation/ConstantDefaultClassifier`) with per-site admission tables decides, and `CodeGenInfo.IsCompileTimeConstant` is computed from the same `ModuleConst` table — the emitter re-derives nothing | `ParameterDefaultConstantMatrixTests` (kind × host, every admitted cell an executing program, every refused cell SPY0401, SPY0908 in no cell, totality-anchored) + the `DispatchSiteInventoryTests` row for the classifier switch + `EmitterParameterDefaultSeamConformanceTests` (Roslyn syntax scan of `CodeGen/`: every `WithDefault(` takes `GenerateParameterDefault(...)`; the late-bound `x_lb = null` sentinel is the one non-user default and is admitted by exact shape, not by site; seam and sentinel site counts anchored to literals so an empty scan fails) | none |
| LiteralString use surface (#1766, #1741) | A `LiteralString` value is a `str` at every value-use route — operators, comparison chains, protocols, truthiness, indexing and slicing, iteration, membership, method dispatch including the static-extension lowering — through ONE `OperandView`; the store seam is untouched (`LiteralString <: str` already) | `LiteralStringSurfaceMatrixTests` (receiver {`str`, `LiteralString`} × operation; the two columns print identical stdout; refusal twin refuses identically; the R-P `x += s` control refuses on the `LiteralString` column only) | none |
| SemanticType codec totality (#1751) | The warm (`--incremental`) build round-trips every `SemanticType` kind the cold build produced: the serializer registry is total over the concrete subclasses, or names its exclusion with a reason that is itself asserted | `TypeCodecRegistryTotalityTests` (reflection over every concrete `SemanticType`; the exclusion set is asserted equal to `{ConstructorReferenceType}` and its reason — SPY0342 refuses the export at the cold build — is the positive control) + `ColdWarmBuildDifferentialTests` (`LiteralString` signature and `-> Self` programs, cold == warm) | none |
Expand Down
38 changes: 38 additions & 0 deletions docs/deviations.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2211,3 +2211,41 @@ deviations:
t.start()
t.join()
print(t.ident > 0) # True

- id: leading-zero-decimal-integer
code: null
category: syntax
audience: python
severity: none
python_behavior: "A decimal integer literal with a leading zero is a SyntaxError: `01`, `0_1` and `007` are `leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers`. An all-zero literal (`00`, `0_0`) is accepted."
sharpy_behavior: "Leading zeros are digits (C#'s rule — C# accepts `007` as 7): `01` and `0_1` lex as the integer 1 and `007` as 7, silently. An octal literal is `0o7` in both languages, so no Python program changes meaning — a Python refusal becomes a Sharpy acceptance. Ruled R-HT (#2300, P68 numeric twin; DEVIATION-by-default under python-fidelity-scope D5 a); refusing them was rejected."
spec_ref: docs/language_specification/integer_literals.md
existing_diagnostic: null
planned_diagnostic: null
example:
python: |
print(01) # SyntaxError: leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers
print(00, 0_0) # 0 0
sharpy: |
def main():
print(01, 0_1, 007) # 1 1 7
print(00, 0_0) # 0 0

- id: identifier-letters-bmp-only
code: SPY0015
category: syntax
audience: python
severity: error
python_behavior: "An identifier's letters are any Unicode code points with the XID_Start/XID_Continue property, including letters outside the Basic Multilingual Plane: `𝒳 = 1` (U+1D4B3 MATHEMATICAL SCRIPT CAPITAL X) binds `𝒳`."
sharpy_behavior: "Identifier letters are the letters of the Basic Multilingual Plane — C#'s identifier alphabet, whose letter test is per UTF-16 code unit (Axiom 1). `été = 1` is an identifier; `𝒳 = 1` is refused with SPY0015 `Unexpected character: '\\uD835'`, the message naming the lone high surrogate of the pair (it prints as U+FFFD `'�'` in a terminal). Ruled R-HW (#2300, P68 record round)."
spec_ref: docs/language_specification/identifiers.md
existing_diagnostic: SPY0015
planned_diagnostic: null
example:
python: |
𝒳 = 1
print(𝒳) # 1
sharpy: |
def main():
𝒳 = 1 # SPY0015: Unexpected character: '�' (the lone high surrogate U+D835)
# control — a BMP letter: `été = 1` then `print(été)` prints 1
34 changes: 34 additions & 0 deletions docs/language_specification/float_literals.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ precise = 3.141_592_653
- `d` or `D` for `float64` (System.Double): `3.14d` (redundant but allowed)
- `m` or `M` for `decimal` (System.Decimal): `3.14m`

A suffix means the same on every spelling of a float — `.5f` is `0.5f`, and `1e5f` and `1_0.2_5m`
take it too. The integer suffixes `L`, `u` and `ul` (see [Integer Literals](integer_literals.md))
belong to integers; on a float literal they are refused (SPY0023), as in C#:

```python
# x = 3.14L # ERROR (SPY0023): Invalid numeric suffix: L (a float literal accepts only the float suffixes f, d and m)
# y = 1e5u # ERROR (SPY0023): Invalid numeric suffix: u (a float literal accepts only the float suffixes f, d and m)
```

## Narrowing an Unsuffixed Literal to `float32` or `decimal`

An **unsuffixed** float literal may narrow to `float32` or `decimal` at **every** store
Expand Down Expand Up @@ -80,9 +89,34 @@ Two limits:
- Only a **literal** narrows. A `float64`-typed *expression* or variable still requires an explicit
cast: `x: float32 = y` is SPY0220 for `y: float`.
- The value must be within `float32`'s range. `x: float32 = 1e40` is SPY0220, not a silent infinity.
The same holds for `decimal`, whose range is about ±7.9e28: `d: decimal = 1e29` is SPY0220.

This is a deliberate divergence from C#, which rejects `float f = 0.1;` outright. Precision loss
within range is accepted — writing the annotation is taken as asking for it.

## A Literal Must Fit Its Own Type

Whatever its spelling, a float literal must have a finite value in the type it resolves to —
`float64` unsuffixed, `float32` with `f`, `decimal` with `m`. A literal outside that range is
refused at compile time with SPY0210, the code an out-of-range integer literal draws, and never
carried as an infinity. Python reads `1e400` as `inf`; Sharpy refuses it, because a silent infinity
would flow on through arithmetic.

```python
def main() -> None:
big = 1.7976931348623157e308 # float64's largest value
print(big) # 1.7976931348623157e+308
f = 3.4028235e38f # float32's largest value
print(f) # 3.4028235e+38
m = 79228162514264337593543950335m # decimal's largest value
print(m) # 79228162514264337593543950335
# x = 1e400 # ERROR (SPY0210): Float literal '1e400' is too large for 'double'
# y = 1e39f # ERROR (SPY0210): Float literal '1e39' is too large for 'float'
# z = 1e29m # ERROR (SPY0210): Float literal '1e29' is too large for 'decimal'
```

A negated literal is checked by its digits, so `-1e400` is refused the same way. A literal too
small to represent is not out of range: `1e-400` is `0.0`, as in Python.

*Implementation*
- *✅ Native - Direct mapping to C# float literals with compiler-inserted suffixes.*
5 changes: 4 additions & 1 deletion docs/language_specification/identifiers.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ digit ::= '0'..'9'
- Can contain letters, digits, and underscores
- Case-sensitive: `myVar`, `myvar`, and `MYVAR` are different identifiers
- Cannot be a keyword
- Unicode letters are allowed but discouraged for interop
- Unicode letters are allowed but discouraged for interop. They are the letters of the Basic
Multilingual Plane — C#'s identifier alphabet (Axiom 1): `été` is an identifier, while a letter
outside the BMP, such as `𝒳` (U+1D4B3), is refused with SPY0015 although Python accepts it
(deviation `identifier-letters-bmp-only` in `docs/deviations.yaml`)

## Examples

Expand Down
10 changes: 10 additions & 0 deletions docs/language_specification/integer_literals.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ million = 1_000_000
billion = 1_000_000_000
```

Leading zeros are digits (C#'s rule): `007` is the decimal integer 7 and `0_1` is 1. Python refuses
both (`leading zeros in decimal integer literals are not permitted`); an octal literal is spelled
`0o7` in both languages (deviation `leading-zero-decimal-integer` in `docs/deviations.yaml`).

<!-- spec-sweep: output -->
```python
def main():
print(007, 0_1, 0o7) # 7 1 7
```

## Type Inference

- Integer literals without a suffix are inferred as `int` (32-bit, `System.Int32`) by default
Expand Down
11 changes: 6 additions & 5 deletions docs/language_specification/module_system.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,12 +135,13 @@ $ sharpyc project app/app.spyproj
error[SPY0305]: Assembly not found: /…/libs/Greeter.dll
```

Deployment: `compile`, for a `.spy` or a `.spyproj`, and `run` copy every referenced assembly
beside the output, whether the program uses it or not, together with the dependencies that sit
beside it. `compile --no-deps` copies none of them (the assembly and its `.deps.json` /
Deployment: every command that produces an assembly — `build`, `compile` (for a `.spy` or a
`.spyproj`), `project` and `run` — copies every referenced assembly beside the output, whether the
program uses it or not, together with `Sharpy.Core.dll`, the standard-library assemblies and the
dependencies that sit beside each, so the output directory runs with `dotnet`. `--no-deps` on
`build`, `compile` or `project` copies none of them (the assembly and its `.deps.json` /
`.runtimeconfig.json` host files are still written), and a `--self-contained` publish carries every
reference. `build` and `project` write the assembly only, without its references or
`Sharpy.Core.dll`, so a directory meant to run with `dotnet` comes from `compile`.
reference.

For example, a library built from `greeter.spy` with
`sharpyc compile greeter.spy -t library -o libs/Greeter.dll`:
Expand Down
2 changes: 1 addition & 1 deletion docs/stdlib/random.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import random

Initialize the random number generator with the given seed.

### `random.next_double() -> float`
### `random.random() -> float`

Return the next random floating point number in the range [0.0, 1.0).

Expand Down
12 changes: 12 additions & 0 deletions docs/stdlib/threading.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,24 @@ import threading

### `threading.current_thread() -> Thread`

Return the Thread object of the calling thread: the object `start()` was called
on, or the main thread. A thread the threading module did not start gets a daemon
`Dummy-N` thread, which stays in `enumerate()`.

### `threading.active_count() -> int`

Return the number of Thread objects currently alive: the length of `enumerate()`.

### `threading.main_thread() -> Thread`

Return the main Thread object, named `MainThread`: the thread that first used the
threading module, which in a Sharpy program is the thread `main()` runs on.

### `threading.enumerate() -> list[Thread]`

Return a list of every Thread object currently alive: the main thread, every started
thread that has not finished, and the dummy threads `current_thread()` created.

### `threading.lock() -> Lock`

### `threading.r_lock() -> RLock`
Expand Down
4 changes: 3 additions & 1 deletion src/Sharpy.Cli.Tests/CliOptionEffectSweepTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ internal static System.Collections.Generic.List<string> TotalityFailures(RootCom
["build --reference"] = "Sharpy.Cli.Tests.Commands.OptionEffectEvidenceTests.Build_Reference_BindsTheImport",
["build --module-path"] = "Sharpy.Cli.Tests.Commands.OptionEffectEvidenceTests.Build_ModulePath_ResolvesAnImport",
["build --server"] = "Sharpy.Cli.Tests.E2E.CompileServerE2ETests.Client_FallsBackToInProcess_WhenNoServerIsRunning",
["build --no-deps"] = "Sharpy.Cli.Tests.E2E.ReferenceDeploymentMatrixTests.NoDeps_WritesTheAssemblyOnly",

["compile@.spy --output"] = "Sharpy.Cli.Tests.Commands.CompileCommandTests.Compile_ValidSource_ProducesDll",
["compile@.spyproj --output"] = "Sharpy.Cli.Tests.Commands.GeneratedCSharpLayoutTests.CompileProject_OutputOption_PlacesTheAssemblyAndItsCSharp",
Expand All @@ -315,7 +316,7 @@ internal static System.Collections.Generic.List<string> TotalityFailures(RootCom
["compile@.spyproj --module-path"] = "Sharpy.Cli.Tests.Commands.CompileOptionEffectTableTests.ModulePath_ResolvesABareReferenceName",
["compile@.spy --self-contained"] = "Sharpy.Cli.Tests.E2E.ReferenceDeploymentMatrixTests.SelfContained_Compile_PublishesTheReference",
["compile@.spy --no-deps"] = "Sharpy.Cli.Tests.Commands.CompileCommandTests.Compile_NoDeps_SkipsCopyingRuntimeDependencies",
["compile@.spyproj --no-deps"] = "Sharpy.Cli.Tests.E2E.ReferenceDeploymentMatrixTests.Control_TheOptOuts_WriteTheAssemblyOnly",
["compile@.spyproj --no-deps"] = "Sharpy.Cli.Tests.E2E.ReferenceDeploymentMatrixTests.NoDeps_WritesTheAssemblyOnly",
["compile@.spyproj --incremental"] = "Sharpy.Cli.Tests.Commands.OptionEffectEvidenceTests.CompileProject_Incremental_WritesTheIncrementalCache",
["compile@.spyproj --clean"] = "Sharpy.Cli.Tests.Commands.OptionEffectEvidenceTests.CompileProject_Clean_DeletesBinAndObjFirst",
["compile@.spy --emit-csharp"] = "Sharpy.Cli.Tests.Commands.CompileCommandTests.Compile_EmitCSharp_WritesCSharpFileAlongsideDll",
Expand All @@ -335,6 +336,7 @@ internal static System.Collections.Generic.List<string> TotalityFailures(RootCom
["project --reference"] = "Sharpy.Cli.Tests.E2E.ProjectReferenceOptionTests.AReference_IsHonoured_AndAMissingOneIsSpy0305",
["project --module-path"] = "Sharpy.Cli.Tests.E2E.ModuleSearchPathCliTests.ProjectModulePathOption_ReachesImportResolution",
["project --incremental"] = "Sharpy.Cli.Tests.Commands.OptionEffectEvidenceTests.Project_Incremental_WritesTheIncrementalCache",
["project --no-deps"] = "Sharpy.Cli.Tests.E2E.ReferenceDeploymentMatrixTests.NoDeps_WritesTheAssemblyOnly",

["emit csharp --output"] = "Sharpy.Cli.Tests.Commands.EmitCommandTests.CSharp_WritesGeneratedFile",
["emit csharp --reference"] = "Sharpy.Cli.Tests.Commands.OptionEffectEvidenceTests.EmitCSharp_Reference_BindsTheImport",
Expand Down
Loading
Loading