Skip to content

Commit 3380030

Browse files
committed
sema: fix dynamic type annotation of generic function calls may leave some types unresolved
1 parent 045eefb commit 3380030

File tree

5 files changed

+200
-50
lines changed

5 files changed

+200
-50
lines changed

src/julec/obj/cxx/scope.jule

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ impl scopeCoder {
133133
}
134134

135135
// Common head object-code for iterations of all kind.
136-
fn iterHead[T](mut &self, mut &it: T, mut &ref: bool, begin: str) {
136+
fn iterHead(mut &self, mut &it: &sema::RangeIter, mut &ref: bool, begin: str) {
137137
self.oc.write("{\n")
138138
self.oc.addIndent()
139139
self.oc.indent()

std/jule/README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,36 @@ An example of a faulty analysis scenario:
9797
9898
- **(14.1)** If enum's type supports the `iota`, so incremental enumeration, the first member uses the `iota` variable as expression by default. So, it enables the incremental enumeration for the following fields.
9999
100+
- **(15)** The recheck strategy revalidates types rather than creating a new instance. Its advantage lies in checking the existing instance again, instead of initializing a new one. This approach improves speed and efficiency by avoiding the need to verify all types for a general type—instead, only the types that require revalidation are rechecked when necessary.
101+
\
102+
Types that need to be rechecked most often arise during the processing of generic types. Below is an example scenario:
103+
```
104+
struct MyStruct[T1, T2] {
105+
x: T1
106+
y: T2
107+
}
108+
109+
fn NewMyStruct[T1, T2](x: T1, y: T2): MyStruct[T1, T2] {
110+
ret MyStruct[T1, T2]{x, y}
111+
}
112+
113+
fn main() {
114+
mc := NewMyStruct(123, 789)
115+
_ = mc
116+
}
117+
```
118+
In the example above, the `NewMyStruct` function is invoked with dynamic annotation, which means the generic types are initially treated as pseudo types. In this context, the return type is nominally `MyStruct[int, int]`, but since the structure has not yet been fully type-checked, the field types still internally reference the pseudo types `T1` and `T2`.
119+
120+
As a result, when evaluating the expression `MyStruct[T1, T2]{x, y}`, a type mismatch error occurs — because the values `x` and `y` are of type `int`, while the structure fields are still marked with unresolved pseudo types. Since `int` and unresolved pseudo types are not compatible, the compiler raises an error.
121+
122+
With the standard strategy, re-checking all function types from scratch using the resolved generic types is not only more costly but also problematic. That's because the type-checking algorithm, for the sake of performance, does not re-check already existing instances.
123+
124+
In this case, `MyStruct[int, int]` would already be considered an existing instance. Initially, a generic instance like `MyStruct[T1, T2]` is registered. When the generic types are resolved via dynamic type annotation, the registered instance is updated to reflect the concrete types, i.e., it becomes `MyStruct[int, int]`.
125+
126+
As a result, the type checker will recognize `MyStruct[int, int]` as an already-handled case and skip re-validation, even though the type-checking for the structure's internal fields has not yet been completed. This leads to incomplete type-checking and potential errors if no mechanism like recheck strategy is used to force revalidation.
127+
128+
With the recheck strategy, when a function instance is created using pseudo generic types, any types that depend on these pseudo types—such as structures that require re-validation—are temporarily stored in memory. After dynamic type annotation resolves the actual types, the recheck algorithm is triggered to revalidate those stored types. This ensures that all types relying on the initially unresolved generic parameters are now correctly and fully type-checked using the concrete types. As a result, the issues arising from incomplete or mismatched type assumptions are resolved, making the generic system more robust and accurate without sacrificing performance.
129+
100130
### Implicit Imports
101131
102132
Implicit imports are as described in developer reference (9). This section addresses which package is supported and what special behaviors it has.

std/jule/sema/eval.jule

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ const (
9696
evalExceptional // Allows calling exceptional functions.
9797
)
9898

99-
// Evaluator.
10099
struct eval {
101100
s: &sema // Used for error logging.
102101
lookup: Lookup
@@ -1197,9 +1196,15 @@ impl eval {
11971196

11981197
// Checks new generics function instance.
11991198
// If instance is already exist, f will point to exist instantantiation.
1200-
fn checkGenericFunc(mut &self, mut &f: &FuncIns, mut &et: &token::Token, mut &model: Expr): (ok: bool, exist: bool) {
1199+
// If recheckNeed is not nil, function will checked with recheck strategy.
1200+
fn checkGenericFunc(mut &self, mut &f: &FuncIns, mut &et: &token::Token,
1201+
mut &model: Expr, mut recheckNeed: []recheckableType): (ok: bool, exist: bool) {
12011202
mut old := f
1202-
ok, exist = self.s.checkGenericFunc(f, et)
1203+
if recheckNeed == nil {
1204+
ok, exist = self.s.checkGenericFunc(f, et)
1205+
} else {
1206+
ok, exist = self.s.checkGenericFuncRecheck(f, et, recheckNeed)
1207+
}
12031208
if ok && exist {
12041209
// Update model by exist function instance.
12051210
// Generic functions returns always new instance, because might be
@@ -1245,7 +1250,7 @@ impl eval {
12451250
ret
12461251
}
12471252

1248-
ok, _ := self.checkGenericFunc(f, i.Expr.Token, v.Model)
1253+
ok, _ := self.checkGenericFunc(f, i.Expr.Token, v.Model, nil)
12491254
if ok {
12501255
v.Type.Kind = f
12511256
} else {
@@ -2242,6 +2247,10 @@ impl eval {
22422247
errorToken: fc.Token,
22432248
}
22442249

2250+
// This will be used to collect recheck-needed types if dynamic annotation enabled.
2251+
// See developer reference (15).
2252+
let mut recheckNeed: []recheckableType
2253+
22452254
if !dynamicAnnotation {
22462255
if !f.reloaded {
22472256
ok := self.s.reloadFuncInsTypes(f)
@@ -2255,18 +2264,23 @@ impl eval {
22552264
if existInstance != nil {
22562265
f = existInstance
22572266
}
2258-
} else if !self.s.buildFuncNonGenericTypes(f, fcac.ignored) {
2259-
v = nil
2260-
ret
2267+
} else {
2268+
// Use non-nil slice for dynamic type annotation.
2269+
// Thus we can use recheck strategy even for zero recheck-need cases.
2270+
recheckNeed = make([]recheckableType, 0, len(f.Params)/2)
2271+
if !self.s.buildFuncNonGenericTypes(f, fcac.ignored, recheckNeed) {
2272+
v = nil
2273+
ret
2274+
}
22612275
}
22622276

22632277
fcac.f = f
22642278

22652279
mut ok := false
22662280
if f.Decl.Owner != nil {
2267-
old, self.s = self.s, old // Save current Sema.
2281+
old, self.s = self.s, old // Save current sema.
22682282
ok = fcac.check()
2269-
old, self.s = self.s, old // Save owner Sema.
2283+
old, self.s = self.s, old // Save owner sema.
22702284
} else {
22712285
ok = fcac.check()
22722286
}
@@ -2276,7 +2290,7 @@ impl eval {
22762290
v = nil
22772291
ret
22782292
}
2279-
ok, _ = self.checkGenericFunc(f, fc.Token, v.Model)
2293+
ok, _ = self.checkGenericFunc(f, fc.Token, v.Model, recheckNeed)
22802294
if !ok {
22812295
v = nil
22822296
ret

std/jule/sema/sema.jule

Lines changed: 86 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -848,18 +848,19 @@ impl sema {
848848
// - For non-generic type parsed string type kinds.
849849
// - For checking non-generic types.
850850
fn buildNonGenericType(mut &self, mut &ast: &ast::Type,
851-
mut &generics: []&ast::Generic, mut &ignored: []&Type): &Type {
851+
mut &generics: []&ast::Generic, mut &ignored: []&Type, mut &recheckNeed: []recheckableType): &Type {
852852
mut tc := &typeChecker{
853853
s: self,
854854
rootLookup: self,
855855
lookup: self,
856856
ignoreGenerics: generics,
857857
ignoredGenerics: &ignored,
858+
recheckNeed: &recheckNeed,
858859
}
859860
ret tc.checkDecl(ast)
860861
}
861862

862-
fn buildFuncNonGenericTypes(mut &self, mut f: &FuncIns, mut &ignored: []&Type): (ok: bool) {
863+
fn buildFuncNonGenericTypes(mut &self, mut f: &FuncIns, mut &ignored: []&Type, mut &recheckNeed: []recheckableType): (ok: bool) {
863864
let mut generics: []&ast::Generic = nil
864865
if f.Decl.IsMethod() {
865866
generics = append(f.Decl.Generics, f.Decl.Owner.Generics...)
@@ -870,12 +871,12 @@ impl sema {
870871
ok = true
871872
for (_, mut p) in f.Params {
872873
if !p.Decl.IsSelf() {
873-
p.Type = sema.buildNonGenericType(p.Decl.TypeSym.Decl, generics, unsafe { ignored })
874+
p.Type = sema.buildNonGenericType(p.Decl.TypeSym.Decl, generics, unsafe { ignored }, unsafe { recheckNeed })
874875
ok = ok && p.Type != nil
875876
}
876877
}
877878
if !f.Decl.IsVoid() {
878-
f.Result = sema.buildNonGenericType(f.Decl.Result.TypeSym.Decl, generics, unsafe { ignored })
879+
f.Result = sema.buildNonGenericType(f.Decl.Result.TypeSym.Decl, generics, unsafe { ignored }, unsafe { recheckNeed })
879880
ok = ok && f.Result != nil
880881
}
881882
ret
@@ -978,36 +979,50 @@ impl sema {
978979
p.Type.Variadic = p.Decl.Variadic
979980
}
980981

982+
// Reload type of function's self (receiver) parameter.
983+
fn reloadSelf(mut &self, mut f: &FuncIns) {
984+
if f.Owner == nil {
985+
// Trait methods have not owner yet. Skip them.
986+
ret
987+
}
988+
// No parameters, no receiver.
989+
if len(f.Params) == 0 {
990+
ret
991+
}
992+
mut p := f.Params[0]
993+
// Receiver parameter already have a type, return immediately.
994+
if p.Type != nil {
995+
ret
996+
}
997+
if p.Decl.IsRef() {
998+
p.Type = &Type{
999+
Kind: &Sptr{
1000+
Elem: &Type{
1001+
Kind: f.Owner,
1002+
},
1003+
},
1004+
}
1005+
} else {
1006+
p.Type = &Type{Kind: f.Owner}
1007+
}
1008+
}
1009+
9811010
fn reloadFuncInsTypes(mut &self, mut f: &FuncIns): bool {
9821011
if f.IsBuiltin() || f.IsAnon() {
9831012
ret true
9841013
}
9851014
ret self.funcEnvironment(f, fn(mut &sema: &sema, mut &generics: []&TypeAlias): bool {
1015+
self.reloadSelf(f)
9861016
mut ok := true
9871017
for (_, mut p) in f.Params {
9881018
if p.Decl.IsSelf() {
989-
if f.Owner == nil {
990-
// Trait methods have not owner yet. Skip them.
991-
continue
992-
}
993-
if p.Decl.IsRef() {
994-
p.Type = &Type{
995-
Kind: &Sptr{
996-
Elem: &Type{
997-
Kind: f.Owner,
998-
},
999-
},
1000-
}
1001-
} else {
1002-
p.Type = &Type{Kind: f.Owner}
1003-
}
1019+
continue
1020+
}
1021+
p.Type = sema.buildTypeWithGenerics(p.Decl.TypeSym.Decl, generics, f.Refers)
1022+
if p.Type != nil {
1023+
self.checkFuncParamKind(p)
10041024
} else {
1005-
p.Type = sema.buildTypeWithGenerics(p.Decl.TypeSym.Decl, generics, f.Refers)
1006-
if p.Type != nil {
1007-
self.checkFuncParamKind(p)
1008-
} else {
1009-
ok = false
1010-
}
1025+
ok = false
10111026
}
10121027
}
10131028
if !f.Decl.IsVoid() {
@@ -2269,6 +2284,52 @@ impl sema {
22692284
ret nil
22702285
}
22712286

2287+
fn recheckType(mut &self, mut t: recheckableType, mut &errorToken: &token::Token,
2288+
mut &refers: &ReferenceStack): (ok: bool) {
2289+
mut tc := &typeChecker{
2290+
s: self,
2291+
rootLookup: self,
2292+
lookup: self,
2293+
refers: refers,
2294+
}
2295+
ret tc.recheck(t, errorToken)
2296+
}
2297+
2298+
// Basically same as the checkGenericFunc,
2299+
// but checks the function with the recheck strategy.
2300+
// See developer reference (15).
2301+
fn checkGenericFuncRecheck(mut &self, mut &f: &FuncIns, mut &et: &token::Token,
2302+
mut recheckNeed: []recheckableType): (ok: bool, exist: bool) {
2303+
// If len(recheckNeed)>0, check the types before handling function.
2304+
if len(recheckNeed) > 0 {
2305+
ok = true
2306+
for (_, mut t) in recheckNeed {
2307+
ok = self.recheckType(t, et, f.Refers) && ok
2308+
}
2309+
if !ok {
2310+
ret false, false
2311+
}
2312+
}
2313+
mut existInstance := f.Decl.appendInstance(f)
2314+
// If instance already exist with same generics, return immediately.
2315+
// This function should be already checked, or it will be.
2316+
if existInstance != nil {
2317+
// Set f to exist one.
2318+
f = existInstance
2319+
ret true, true
2320+
}
2321+
// We still need to check self parameter.
2322+
self.reloadSelf(f)
2323+
// TODO: [check] is possible to optimize here using same environment with realoadFuncInsTypes?
2324+
if !self.checkConstraintsFunc(f, et, existInstance) {
2325+
ret false, false
2326+
}
2327+
// Check generic function instance instantly.
2328+
self.checkFuncInsCaller(f, et)
2329+
ok = true
2330+
ret
2331+
}
2332+
22722333
// Checks new generics function instance.
22732334
// If instance is already exist, f will point to exist instantantiation.
22742335
fn checkGenericFunc(mut &self, mut &f: &FuncIns, mut &et: &token::Token): (ok: bool, exist: bool) {

0 commit comments

Comments
 (0)