|
| 1 | +# Plan: Enable Strict Entry Point Main Function Requirement |
| 2 | + |
| 3 | +## Problem Summary |
| 4 | + |
| 5 | +In `src/Sharpy.Compiler/Semantic/Validation/ModuleLevelValidatorV2.cs` (lines 111-123), there is commented-out code that would enforce the requirement that **entry point files must have a `main()` function**. This is currently disabled for "backward compatibility." |
| 6 | + |
| 7 | +According to the language specification in `docs/language_specification/program_entry_point.md`: |
| 8 | +> "Every executable Sharpy program requires a `main()` function as its entry point" |
| 9 | + |
| 10 | +The validator currently: |
| 11 | +1. ✅ Rejects bare executable statements when there IS a `main()` function present |
| 12 | +2. ✅ Rejects bare executable statements in non-entry-point (library) modules |
| 13 | +3. ❌ Allows entry point files without a `main()` function (for backward compatibility) |
| 14 | + |
| 15 | +## Current State Analysis |
| 16 | + |
| 17 | +### Test Fixtures Already Compliant |
| 18 | +After analysis, **all test fixture files that are entry points already have `main()` functions**. The files without `main()` are all library modules (in multi-file test scenarios), which correctly don't need one. |
| 19 | + |
| 20 | +### Files Without `main()` (Library Modules - Correct) |
| 21 | +These are all imported modules, not entry points: |
| 22 | +- `imports/import_with_classes/math_utils.spy` |
| 23 | +- `imports/simple_import_test/math_utils.spy` |
| 24 | +- `imports/module_import_access/calculator.spy` |
| 25 | +- `module_imports/geometry_shapes/geometry.spy` |
| 26 | +- `module_imports/geometry_shapes/validators.spy` |
| 27 | +- `module_imports/complex_type_relationships/geometry.spy` |
| 28 | +- `module_imports/complex_type_relationships/calculator.spy` |
| 29 | +- `cross_module_inheritance/*/*.spy` (all library files) |
| 30 | + |
| 31 | +These files are correctly identified as non-entry-points via `IsEntryPoint = false`. |
| 32 | + |
| 33 | +### Error Test Cases (Already Testing the Rules) |
| 34 | +- `errors/main_function_with_statements.spy` - Tests rejection of bare executable statements when `main()` exists |
| 35 | +- `errors/module_level_executable_statement.spy` - Tests rejection of bare `print()` at module level |
| 36 | + |
| 37 | +## Implementation Plan |
| 38 | + |
| 39 | +### Phase 1: Enable Strict Enforcement |
| 40 | + |
| 41 | +**File:** `src/Sharpy.Compiler/Semantic/Validation/ModuleLevelValidatorV2.cs` |
| 42 | + |
| 43 | +Uncomment and modify the entry point validation logic (lines 111-123): |
| 44 | + |
| 45 | +```csharp |
| 46 | +// Entry point files should have a main() function |
| 47 | +if (_context.IsEntryPoint && !hasMainFunction) |
| 48 | +{ |
| 49 | + AddError(_context, |
| 50 | + "Entry point file requires a 'main()' function", |
| 51 | + module.LineStart, module.ColumnStart); |
| 52 | +} |
| 53 | +``` |
| 54 | + |
| 55 | +**Rationale for removing the inner condition:** The original commented code had an inner check `if (executableStatements.Count == 0 && untypedVariables.Count == 0)` which would only error if there were NO executable statements AND NO untyped variables. This seems backwards - we should always require `main()` for entry points regardless of whether there are other violations. |
| 56 | + |
| 57 | +### Phase 2: Add Error Test Case |
| 58 | + |
| 59 | +**New file:** `src/Sharpy.Compiler.Tests/Integration/TestFixtures/errors/entry_point_missing_main.spy` |
| 60 | + |
| 61 | +```python |
| 62 | +# Error test: Entry point file must have a main() function |
| 63 | + |
| 64 | +counter: int = 0 |
| 65 | + |
| 66 | +def helper() -> int: |
| 67 | + return counter + 1 |
| 68 | + |
| 69 | +# No main() function defined - this is an error for entry point files |
| 70 | +``` |
| 71 | + |
| 72 | +**New file:** `src/Sharpy.Compiler.Tests/Integration/TestFixtures/errors/entry_point_missing_main.error` |
| 73 | + |
| 74 | +``` |
| 75 | +Entry point file requires a 'main()' function |
| 76 | +``` |
| 77 | + |
| 78 | +### Phase 3: Update Unit Tests |
| 79 | + |
| 80 | +**File:** `src/Sharpy.Compiler.Tests/Semantic/Validation/ModuleLevelValidatorV2Tests.cs` |
| 81 | + |
| 82 | +Add a test case verifying that entry point files without `main()` are rejected: |
| 83 | + |
| 84 | +```csharp |
| 85 | +[Fact] |
| 86 | +public void EntryPointFile_WithoutMainFunction_ReportsError() |
| 87 | +{ |
| 88 | + // Arrange: Entry point file with only declarations, no main() |
| 89 | + var source = @" |
| 90 | +counter: int = 0 |
| 91 | + |
| 92 | +def helper() -> int: |
| 93 | + return 42 |
| 94 | +"; |
| 95 | + var (module, context) = ParseWithContext(source, isEntryPoint: true); |
| 96 | + var validator = new ModuleLevelValidatorV2(); |
| 97 | + |
| 98 | + // Act |
| 99 | + validator.Validate(module, context); |
| 100 | + |
| 101 | + // Assert |
| 102 | + Assert.True(context.Diagnostics.HasErrors); |
| 103 | + Assert.Contains(context.Diagnostics.Errors, |
| 104 | + e => e.Message.Contains("Entry point file requires a 'main()' function")); |
| 105 | +} |
| 106 | + |
| 107 | +[Fact] |
| 108 | +public void LibraryModule_WithoutMainFunction_NoError() |
| 109 | +{ |
| 110 | + // Arrange: Library module (not entry point) with only declarations |
| 111 | + var source = @" |
| 112 | +counter: int = 0 |
| 113 | + |
| 114 | +def helper() -> int: |
| 115 | + return 42 |
| 116 | +"; |
| 117 | + var (module, context) = ParseWithContext(source, isEntryPoint: false); |
| 118 | + var validator = new ModuleLevelValidatorV2(); |
| 119 | + |
| 120 | + // Act |
| 121 | + validator.Validate(module, context); |
| 122 | + |
| 123 | + // Assert |
| 124 | + Assert.False(context.Diagnostics.HasErrors); |
| 125 | +} |
| 126 | +``` |
| 127 | + |
| 128 | +### Phase 4: Verify All Tests Pass |
| 129 | + |
| 130 | +Run the full test suite to ensure: |
| 131 | +1. All existing tests still pass (they all have `main()`) |
| 132 | +2. The new error test case works correctly |
| 133 | +3. Library modules (non-entry-points) are not affected |
| 134 | + |
| 135 | +```bash |
| 136 | +dotnet test |
| 137 | +``` |
| 138 | + |
| 139 | +## Risk Assessment |
| 140 | + |
| 141 | +**Low Risk:** All existing test fixture files already have `main()` functions, so this change should not break any existing tests. The change only affects: |
| 142 | +1. The theoretical case of an entry point file without `main()` (which was "working" via backward compatibility) |
| 143 | +2. Adding proper validation for what the spec already requires |
| 144 | + |
| 145 | +## Acceptance Criteria |
| 146 | + |
| 147 | +1. ✅ Entry point files without `main()` produce error: "Entry point file requires a 'main()' function" |
| 148 | +2. ✅ Library modules without `main()` continue to work (no error) |
| 149 | +3. ✅ Entry point files WITH `main()` continue to work |
| 150 | +4. ✅ All existing tests pass |
| 151 | +5. ✅ New error test case added and passing |
| 152 | +6. ✅ Unit tests for the validator updated |
| 153 | + |
| 154 | +## Files to Modify |
| 155 | + |
| 156 | +1. `src/Sharpy.Compiler/Semantic/Validation/ModuleLevelValidatorV2.cs` - Enable strict enforcement |
| 157 | +2. `src/Sharpy.Compiler.Tests/Semantic/Validation/ModuleLevelValidatorV2Tests.cs` - Add unit tests |
| 158 | +3. `src/Sharpy.Compiler.Tests/Integration/TestFixtures/errors/entry_point_missing_main.spy` - New error test |
| 159 | +4. `src/Sharpy.Compiler.Tests/Integration/TestFixtures/errors/entry_point_missing_main.error` - Expected error message |
0 commit comments