Skip to content
Open
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
22 changes: 22 additions & 0 deletions docs/language-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,28 @@ must be ordered, within the byte length, and on UTF-8 codepoint boundaries.
Invalid bounds or boundaries trap at runtime. The owner remains responsible
for backing storage and is dropped exactly once.

## Generic Named Types

Structs, enums, interfaces, and transparent type aliases may declare type
parameters. Every use supplies exact explicit arguments, including imported and
nested applications such as `container::Box<Pair<i32, str>>`. Type-context
parsing splits adjacent closing `>>` tokens; expression comparisons keep normal
`<` and `>` behavior.

Transparent aliases do not create a new generic argument identity. If
`MyInt = i32`, then `Choice<MyInt>` and `Choice<i32>` are the same semantic
instance. Nominal identity still belongs to the applied declaration and its
canonical arguments.

Recursive generic applications must preserve exact canonical arguments.
`Node<T>` may contain `*Node<T>` or another fixed-size indirect reference to the
same application. Expanding or transformed recursion such as
`Loop<T> -> Loop<Loop<T>>` or `Pair<A, B> -> Pair<B, A>` is rejected. Direct
by-value recursion remains unsized and rejected independently.

Constraints, defaults, generic inference, generic functions, generic methods,
and monomorphization are not part of current language surface.

## Optional Values And Flow Narrowing

`?T` contains either one `T` value or `none`. `none` is valid only where an
Expand Down
3 changes: 3 additions & 0 deletions docs/ownership-pointer-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,9 @@ lowering reserves one identity for each named recursive composite before
lowering its children. LLVM emits identified aggregate declarations and one
reusable private drop function per owning named composite, so recursive
destruction calls the existing function instead of recursively expanding IR.
Generic recursive links must repeat same canonical type arguments. Exact
`Node<T> -> *Node<T>` recursion reuses one semantic and runtime shell;
argument-expanding recursion is rejected before lowering.

## Final Rules

Expand Down
22 changes: 21 additions & 1 deletion internal/frontend/ast/clone.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ func cloneTypeExpr(typ TypeExpr, newID func(NodeID, bool) NodeID, fromArgument b
switch typ := typ.(type) {
case *NamedType:
return &NamedType{NodeIDHolder: id, Name: typ.Name, Location: typ.Location}
case *AppliedType:
args := make([]TypeExpr, len(typ.TypeArgs))
for index, arg := range typ.TypeArgs {
args[index] = cloneTypeExpr(arg, newID, fromArgument)
}
return &AppliedType{NodeIDHolder: id, Name: cloneIdent(typ.Name, newID, fromArgument), TypeArgs: args, Location: typ.Location}
case *OwnedPtrType:
return &OwnedPtrType{NodeIDHolder: id, Target: cloneTypeExpr(typ.Target, newID, fromArgument), Location: typ.Location}
case *RawPtrType:
Expand Down Expand Up @@ -108,12 +114,26 @@ func cloneTypeExpr(typ TypeExpr, newID func(NodeID, bool) NodeID, fromArgument b
}
return &EnumType{NodeIDHolder: id, Variants: variants, Location: typ.Location}
case *ScopeResolution:
return &ScopeResolution{NodeIDHolder: id, Module: cloneIdent(typ.Module, newID, fromArgument), Name: cloneIdent(typ.Name, newID, fromArgument), Location: typ.Location}
return &ScopeResolution{NodeIDHolder: id, Segments: clonePathSegments(typ.Segments, newID, fromArgument), Location: typ.Location}
default:
panic("unhandled type expression in call-default clone")
}
}

func clonePathSegments(segments []PathSegment, newID func(NodeID, bool) NodeID, fromArgument bool) []PathSegment {
cloned := make([]PathSegment, len(segments))
for index, segment := range segments {
args := make([]TypeExpr, len(segment.TypeArgs))
for argIndex, arg := range segment.TypeArgs {
args[argIndex] = cloneTypeExpr(arg, newID, fromArgument)
}
cloned[index] = PathSegment{
Name: cloneIdent(segment.Name, newID, fromArgument), TypeArgs: args, Location: segment.Location,
}
}
return cloned
}

func cloneParam(param Param, newID func(NodeID, bool) NodeID, fromArgument bool) Param {
cloned := Param{IsMutable: param.IsMutable, Name: cloneIdent(param.Name, newID, fromArgument), Type: cloneTypeExpr(param.Type, newID, fromArgument), Location: param.Location}
if param.Default != nil {
Expand Down
34 changes: 34 additions & 0 deletions internal/frontend/ast/decl.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,28 @@ type NamedType struct {
Location *source.Location
}

type AppliedType struct {
NodeIDHolder
Name *Ident
TypeArgs []TypeExpr
Location *source.Location
}

func (*AppliedType) typeNode() {}
func (t *AppliedType) forEachChild(visit func(Node)) {
visit(t.Name)
for _, arg := range t.TypeArgs {
visit(arg)
}
}
func (t *AppliedType) loc() *source.Location { return t.Location }
func (t *AppliedType) TypeText() string {
if t == nil {
return ""
}
return appliedTypeText(t.Name, t.TypeArgs)
}

func (*NamedType) typeNode() {}
func (*NamedType) forEachChild(func(Node)) {}
func (t *NamedType) loc() *source.Location { return t.Location }
Expand Down Expand Up @@ -467,6 +489,9 @@ func (d *TypeAliasDecl) forEachChild(visit func(Node)) {
}
func (d *TypeAliasDecl) loc() *source.Location { return d.Location }
func (d *TypeAliasDecl) DeclName() *Ident { return d.Name }
func (d *TypeAliasDecl) DeclarationTypeParams() []TypeParam {
return d.TypeParams
}
func (d *TypeAliasDecl) UnderlyingType() TypeExpr {
return d.Type
}
Expand All @@ -493,6 +518,9 @@ func (d *StructDecl) forEachChild(visit func(Node)) {
}
func (d *StructDecl) loc() *source.Location { return d.Location }
func (d *StructDecl) DeclName() *Ident { return d.Name }
func (d *StructDecl) DeclarationTypeParams() []TypeParam {
return d.TypeParams
}
func (d *StructDecl) UnderlyingType() TypeExpr {
return d.Type
}
Expand All @@ -518,6 +546,9 @@ func (d *InterfaceDecl) forEachChild(visit func(Node)) {
}
func (d *InterfaceDecl) loc() *source.Location { return d.Location }
func (d *InterfaceDecl) DeclName() *Ident { return d.Name }
func (d *InterfaceDecl) DeclarationTypeParams() []TypeParam {
return d.TypeParams
}
func (d *InterfaceDecl) UnderlyingType() TypeExpr {
return d.Type
}
Expand All @@ -543,6 +574,9 @@ func (d *EnumDecl) forEachChild(visit func(Node)) {
}
func (d *EnumDecl) loc() *source.Location { return d.Location }
func (d *EnumDecl) DeclName() *Ident { return d.Name }
func (d *EnumDecl) DeclarationTypeParams() []TypeParam {
return d.TypeParams
}
func (d *EnumDecl) UnderlyingType() TypeExpr {
return d.Type
}
Expand Down
66 changes: 50 additions & 16 deletions internal/frontend/ast/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,25 @@ func (e *Ident) copyExpr(substitutions map[string]Expr, newID func(NodeID, bool)

type ScopeResolution struct {
NodeIDHolder
Module *Ident
Segments []PathSegment
Location *source.Location
}

type PathSegment struct {
Name *Ident
TypeArgs []TypeExpr
Location *source.Location
}

func (*ScopeResolution) exprNode() {}
func (*ScopeResolution) typeNode() {}
func (e *ScopeResolution) forEachChild(visit func(Node)) {
visit(e.Module)
visit(e.Name)
for _, segment := range e.Segments {
visit(segment.Name)
for _, arg := range segment.TypeArgs {
visit(arg)
}
}
}
func (e *ScopeResolution) loc() *source.Location { return e.Location }
func (e *ScopeResolution) exprText() string {
Expand All @@ -58,29 +67,54 @@ func (e *ScopeResolution) TypeText() string {
if e == nil {
return ""
}
module := ""
if e.Module != nil {
module = e.Module.Name
}
name := ""
if e.Name != nil {
name = e.Name.Name
parts := make([]string, 0, len(e.Segments))
for _, segment := range e.Segments {
parts = append(parts, appliedTypeText(segment.Name, segment.TypeArgs))
}
if module == "" {
return name
return strings.Join(parts, "::")
}

// ImportMember accepts only paths whose first segment is an import qualifier
// and whose second segment is the imported symbol. Longer paths belong to
// enum/type member resolution in later semantic work.
func (e *ScopeResolution) ImportMember() (qualifier, member *Ident, ok bool) {
if e == nil || len(e.Segments) != 2 || len(e.Segments[0].TypeArgs) != 0 ||
e.Segments[0].Name == nil || e.Segments[1].Name == nil {
return nil, nil, false
}
if name == "" {
return module + "::"
return e.Segments[0].Name, e.Segments[1].Name, true
}

// ImportValueMember excludes applied final segments because generic functions
// and values are not part of named-type application support.
func (e *ScopeResolution) ImportValueMember() (qualifier, member *Ident, ok bool) {
qualifier, member, ok = e.ImportMember()
if !ok || len(e.Segments[1].TypeArgs) != 0 {
return nil, nil, false
}
return module + "::" + name
return qualifier, member, true
}

func (e *ScopeResolution) copyExpr(substitutions map[string]Expr, newID func(NodeID, bool) NodeID, fromArgument bool) Expr {
if e == nil {
return nil
}
id := newID(e.ID(), fromArgument)
return &ScopeResolution{NodeIDHolder: NodeIDHolder{NodeID: id}, Module: cloneIdent(e.Module, newID, fromArgument), Name: cloneIdent(e.Name, newID, fromArgument), Location: e.Location}
return &ScopeResolution{NodeIDHolder: NodeIDHolder{NodeID: id}, Segments: clonePathSegments(e.Segments, newID, fromArgument), Location: e.Location}
}

func appliedTypeText(name *Ident, args []TypeExpr) string {
if name == nil {
return ""
}
if len(args) == 0 {
return name.Name
}
parts := make([]string, len(args))
for index, arg := range args {
parts[index] = TypeText(arg)
}
return name.Name + "<" + strings.Join(parts, ", ") + ">"
}

type SelectorExpr struct {
Expand Down
1 change: 1 addition & 0 deletions internal/frontend/ast/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ type TypeDecl interface {
Decl
AttributedNode
DeclName() *Ident
DeclarationTypeParams() []TypeParam
UnderlyingType() TypeExpr
}

Expand Down
47 changes: 39 additions & 8 deletions internal/frontend/parser/parse_expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -492,20 +492,51 @@ func (p *Parser) parseIdentExpr() ast.Expr {
if id == nil {
return nil
}
if p.match(token.DCOLON) {
member := p.parseIdent()
if member == nil {
first := ast.PathSegment{Name: id, Location: id.Location}
if p.at(token.LT) && p.typeArgumentsAreFollowedByScope() {
args, close, ok := p.parseTypeArguments()
if !ok {
return nil
}
return reg(p, &ast.ScopeResolution{
Module: id,
Name: member,
Location: source.NewLocation(p.filePath, ast.StartOf(id), ast.EndOf(member)),
})
first.TypeArgs = args
first.Location = source.NewLocation(p.filePath, ast.StartOf(id), close.End)
}
if p.at(token.DCOLON) {
path := p.parseScopeResolution(first)
if path == nil {
return nil
}
return path
}
return id
}

func (p *Parser) typeArgumentsAreFollowedByScope() bool {
if !p.at(token.LT) {
return false
}
depth := 0
for index := p.pos; index < len(p.stream); index++ {
switch p.stream[index].Kind {
case token.LT:
depth++
case token.GT:
depth--
case token.SHR:
depth -= 2
case token.EOF, token.SEMICOLON:
return false
}
if depth < 0 {
return false
}
if depth == 0 {
return index+1 < len(p.stream) && p.stream[index+1].Kind == token.DCOLON
}
}
return false
}

func (p *Parser) parseSelector(left ast.Expr) ast.Expr {
dot := p.consume(token.DOT, "expected '.'")
if dot == nil {
Expand Down
Loading