Skip to content
This repository was archived by the owner on Jun 19, 2026. It is now read-only.
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
21 changes: 7 additions & 14 deletions internal/analysis/layout/analysis/analyze.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,10 +180,10 @@ func (a *analyzer) layoutUnderlying(syntax any, typ typeinfo.Type) (int64, int64
}
elemSize, elemAlign, known, _ := a.layoutUnderlying(nil, t.Inner)
if !known {
return 0, maxInt64(1, elemAlign), false, nil
return 0, max(1, elemAlign), false, nil
}
stride := alignUp(elemSize, elemAlign)
return stride * t.Len, maxInt64(1, elemAlign), true, nil
return stride * t.Len, max(1, elemAlign), true, nil
case *typeinfo.TupleType:
return a.layoutSequential(t.Elems)
case *typeinfo.StructType:
Expand Down Expand Up @@ -246,7 +246,7 @@ func (a *analyzer) layoutSequential(elems []typeinfo.Type) (int64, int64, bool,
}
offset = alignUp(offset, align)
offset += size
maxAlign = maxInt64(maxAlign, align)
maxAlign = max(maxAlign, align)
known = known && elemKnown
}
return alignUp(offset, maxAlign), maxAlign, known, nil
Expand Down Expand Up @@ -281,7 +281,7 @@ func (a *analyzer) layoutStruct(st *typeinfo.StructType) (int64, int64, bool, *l
})
order = append(order, i)
offset += size
maxAlign = maxInt64(maxAlign, align)
maxAlign = max(maxAlign, align)
known = known && fieldKnown
}
total := alignUp(offset, maxAlign)
Expand All @@ -305,8 +305,8 @@ func (a *analyzer) layoutTaggedUnionDetail(members []typeinfo.Type) (int64, int6
memberLayouts := make([]*layout.UnionMemberLayout, 0, len(members))
for _, member := range members {
size, align, memberKnown, _ := a.layoutUnderlying(nil, member)
payloadSize = maxInt64(payloadSize, size)
payloadAlign = maxInt64(payloadAlign, align)
payloadSize = max(payloadSize, size)
payloadAlign = max(payloadAlign, align)
known = known && memberKnown
memberLayouts = append(memberLayouts, &layout.UnionMemberLayout{
Index: len(memberLayouts),
Expand All @@ -315,7 +315,7 @@ func (a *analyzer) layoutTaggedUnionDetail(members []typeinfo.Type) (int64, int6
Align: align,
})
}
align := maxInt64(tagAlign, payloadAlign)
align := max(tagAlign, payloadAlign)
payloadOffset := alignUp(tagSize, payloadAlign)
size := alignUp(payloadOffset+payloadSize, align)
for _, member := range memberLayouts {
Expand Down Expand Up @@ -370,13 +370,6 @@ func alignUp(value, align int64) int64 {
return value + (align - rem)
}

func maxInt64(a, b int64) int64 {
if a > b {
return a
}
return b
}

func unionStructLayout(u *layout.UnionLayout) *layout.StructLayout {
if u == nil {
return nil
Expand Down
50 changes: 50 additions & 0 deletions internal/driver/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,56 @@ func ParsePathForIDE(path string) Result {
return parsePath(path, parseModeIDE)
}

// ParsePathWithOverlay parses originalPath using overlayPath content while
// keeping project roots, import paths, and diagnostic paths tied to originalPath.
func ParsePathWithOverlay(originalPath, overlayPath string, ide bool) Result {
mode := parseModeFull
if ide {
mode = parseModeIDE
}
absPath, err := filepath.Abs(originalPath)
diag := diagnostics.NewDiagnosticBag(absPath)
if err != nil {
diag.Add(diagnostics.NewError(err.Error()))
return Result{Diagnostics: diag}
}
absOverlay, err := filepath.Abs(overlayPath)
if err != nil {
diag.Add(diagnostics.NewError(err.Error()))
return Result{Diagnostics: diag}
}
if ext := strings.ToLower(filepath.Ext(absPath)); ext != FerretSourceExt {
diag.Add(diagnostics.NewError("unsupported source file extension"))
return Result{Diagnostics: diag}
}
ws, err := project.Load(absPath, FerretSourceExt)
if err != nil {
diag.Add(diagnostics.NewError(err.Error()))
return Result{Diagnostics: diag}
}
c := NewWithConfig(ws.Context, diag)
var entry *context.Module
switch mode {
case parseModeIDE:
entry, err = c.pipeline.ParseEntryOverlayForIDE(absPath, absOverlay)
default:
entry, err = c.pipeline.ParseEntryOverlay(absPath, absOverlay)
}
if err != nil {
c.ctx.Diagnostics.Add(diagnostics.NewError(err.Error()))
}
result := Result{
Entry: entry,
Modules: c.ctx.NonPreludeModules(),
Diagnostics: c.ctx.Diagnostics,
CompilerState: c.ctx,
}
if entry != nil {
result.Module = entry.AST
}
return result
}

func (c *Compiler) ParseEntry(entryFile string) Result {
entry, err := c.pipeline.ParseEntry(entryFile)
if err != nil {
Expand Down
46 changes: 46 additions & 0 deletions internal/driver/compiler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,52 @@ func TestParsePathForIDEReportsUnusedLocalDiagnostics(t *testing.T) {
}
}

func TestParsePathWithOverlayForIDEAcceptsNonSourceExtension(t *testing.T) {
root := t.TempDir()
sourcePath := filepath.Join(root, "main.fer")
overlayPath := filepath.Join(root, "overlay.tmp")
mustWrite(t, sourcePath, `fn main() -> void {
}
`)
mustWrite(t, overlayPath, `fn main() -> void {
let overlay_value = 1
}
`)

rejected := ParsePathForIDE(overlayPath)
if !rejected.Diagnostics.HasErrors() {
t.Fatalf("expected normal IDE parse to reject non-source extension")
}

result := ParsePathWithOverlay(sourcePath, overlayPath, true)
if result.Diagnostics.HasErrors() {
t.Fatalf("unexpected overlay diagnostics: %#v", result.Diagnostics.Diagnostics())
}
if result.Entry == nil || result.Entry.FilePath != filepath.Clean(sourcePath) {
t.Fatalf("expected source entry %q, got %#v", filepath.Clean(sourcePath), result.Entry)
}
}

func TestParsePathWithOverlayReportsOverlayReadPath(t *testing.T) {
root := t.TempDir()
sourcePath := filepath.Join(root, "main.fer")
overlayPath := filepath.Join(root, "missing-overlay.tmp")
mustWrite(t, sourcePath, `fn main() -> void {
}
`)

result := ParsePathWithOverlay(sourcePath, overlayPath, true)
if !result.Diagnostics.HasErrors() {
t.Fatal("expected missing overlay diagnostic")
}
for _, diag := range result.Diagnostics.Diagnostics() {
if diag != nil && strings.Contains(diag.Message, "cannot read overlay file "+overlayPath) {
return
}
}
t.Fatalf("expected overlay path diagnostic, got %#v", result.Diagnostics.Diagnostics())
}

func TestParsePathRejectsNonCanonicalRecursiveGenericSelfUseBeforeLowering(t *testing.T) {
root := t.TempDir()
mustWrite(t, filepath.Join(root, "main.fer"), `
Expand Down
11 changes: 6 additions & 5 deletions internal/frontend/parser/attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ type DeclAttributeSpec struct {
FunctionOnly bool
MaxArgs int
NoArgsMessage string
Doc string
}

var declAttributeSpecs = []DeclAttributeSpec{
{Name: "extern", FunctionOnly: true, MaxArgs: 1},
{Name: "builtin", FunctionOnly: true, MaxArgs: 1},
{Name: "allow_unused", MaxArgs: 0, NoArgsMessage: "#[allow_unused] does not accept arguments"},
{Name: "if", MaxArgs: -1},
{Name: "ifnot", MaxArgs: -1},
{Name: "extern", FunctionOnly: true, MaxArgs: 1, Doc: "Marks a function as externally linked. Optional argument overrides the linked symbol name."},
{Name: "builtin", FunctionOnly: true, MaxArgs: 1, Doc: "Marks a function as compiler/runtime-provided. Optional argument overrides the linked symbol name."},
{Name: "allow_unused", MaxArgs: 0, NoArgsMessage: "#[allow_unused] does not accept arguments", Doc: "Suppresses unused diagnostics for the annotated declaration."},
{Name: "if", MaxArgs: -1, Doc: "Includes the annotated declaration only when the compile-time condition matches."},
{Name: "ifnot", MaxArgs: -1, Doc: "Includes the annotated declaration only when the compile-time condition does not match."},
}

var declAttributeSpecByName = buildDeclAttributeSpecByName(declAttributeSpecs)
Expand Down
Loading
Loading