From efbb07fb919402be06b964418f102eb135a7cd95 Mon Sep 17 00:00:00 2001 From: metagn Date: Tue, 3 Dec 2024 08:04:19 +0300 Subject: [PATCH 1/6] fix array/set literals with generic expression elements fixes #24484 --- compiler/semexprs.nim | 45 ++++++++++++++++++++--------- tests/proc/tgenericdefaultparam.nim | 22 ++++++++++++++ 2 files changed, 54 insertions(+), 13 deletions(-) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index f959783225a12..33421353f60aa 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -725,7 +725,7 @@ proc semArrayConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PTyp # nkBracket nodes can also be produced by the VM as seq constant nodes # in which case, we cannot produce a new array type for the node, # as this might lose type info even when the node has array type - let constructType = n.typ.isNil + let constructType = n.typ.isNil or n.typ.kind == tyFromExpr var expectedElementType, expectedIndexType: PType = nil var expectedBase: PType = nil if constructType: @@ -797,17 +797,26 @@ proc semArrayConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PTyp localError(c.config, x.info, "invalid order in array constructor") x = x[1] - let xx = semExprWithType(c, x, {efTypeAllowed}, expectedElementType) - result.add xx - if constructType: - typ = commonType(c, typ, xx.typ) + if typ.kind == tyFromExpr and c.inGenericContext > 0: + result.add semGenericStmt(c, x) + else: + let xx = semExprWithType(c, x, {efTypeAllowed}, expectedElementType) + result.add xx + if constructType: + typ = commonType(c, typ, xx.typ) #n[i] = semExprWithType(c, x, {}) #result.add fitNode(c, typ, n[i]) inc(lastIndex) - if constructType: - addSonSkipIntLit(result.typ, typ, c.idgen) - for i in 0.. 0: + if constructType: + result.typ() = nil # current result.typ is invalid, index type is nil + result.typ() = makeTypeFromExpr(c, result.copyTree) + return + else: + if constructType: + addSonSkipIntLit(result.typ, typ, c.idgen) + for i in 0.. 0: + n[i] = semGenericStmt(c, n[i]) + elif isRange(n[i]): checkSonsLen(n[i], 3, c.config) n[i][1] = semExprWithType(c, n[i][1], {efTypeAllowed}, expectedElementType) n[i][2] = semExprWithType(c, n[i][2], {efTypeAllowed}, expectedElementType) @@ -2828,7 +2839,10 @@ proc semSetConstr(c: PContext, n: PNode, expectedType: PType = nil): PNode = if doSetType: typ = skipTypes(n[i].typ, {tyGenericInst, tyVar, tyLent, tyOrdinal, tyAlias, tySink}) if doSetType: - if not isOrdinalType(typ, allowEnumWithHoles=true): + if typ.kind == tyFromExpr and c.inGenericContext > 0: + # propagate it as set element type + discard + elif not isOrdinalType(typ, allowEnumWithHoles=true): localError(c.config, n.info, errOrdinalTypeExpected % typeToString(typ, preferDesc)) typ = makeRangeType(c, 0, MaxSetElements-1, n.info) elif isIntLit(typ): @@ -2842,11 +2856,16 @@ proc semSetConstr(c: PContext, n: PNode, expectedType: PType = nil): PNode = typ = makeRangeType(c, 0, MaxSetElements-1, n.info) if expectedElementType == nil: expectedElementType = typ - addSonSkipIntLit(result.typ, typ, c.idgen) + if typ.kind == tyFromExpr and c.inGenericContext > 0: + result.typ() = makeTypeFromExpr(c, result.copyTree) + else: + addSonSkipIntLit(result.typ, typ, c.idgen) for i in 0.. 0: + m = n[i] + elif isRange(n[i]): m = newNodeI(nkRange, info) m.add fitNode(c, typ, n[i][1], info) m.add fitNode(c, typ, n[i][2], info) diff --git a/tests/proc/tgenericdefaultparam.nim b/tests/proc/tgenericdefaultparam.nim index 7bce591ce5ee2..d20af411ed1eb 100644 --- a/tests/proc/tgenericdefaultparam.nim +++ b/tests/proc/tgenericdefaultparam.nim @@ -96,3 +96,25 @@ block: # issue #24121 proc baz[T: FooBar](x: T, y = foo(x)): string = y doAssert baz(Foo(123)) == "b" doAssert baz(Bar(123)) == "c" + +block: # issue #24484 + type E = enum A + proc foo[T](t: set[T] = {T.A}) = + discard + foo[E]() + + proc bar[T](t: set[T] = {T(0), 5}) = + doAssert t == {0, 5} + bar[uint8]() + doAssert not compiles(bar[string]()) + +block: # issue #24484, array version + type E = enum A + proc foo[T](t: openArray[T] = [T.A]) = + discard + foo[E]() + + proc bar[T](t: openArray[T] = [T(0), 5]) = + doAssert t == [T(0), 5] + bar[uint8]() + From e74b11eb302b38748e592b5320f44c694995799b Mon Sep 17 00:00:00 2001 From: metagn Date: Tue, 3 Dec 2024 08:28:48 +0300 Subject: [PATCH 2/6] also add tuples --- compiler/semexprs.nim | 27 +++++++++++++++++++-------- tests/proc/tgenericdefaultparam.nim | 10 ++++++++++ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 33421353f60aa..d2ed0f2220feb 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -2976,14 +2976,20 @@ proc semTuplePositionsConstr(c: PContext, n: PNode, flags: TExprFlags; expectedT var typ = newTypeS(tyTuple, c) # leave typ.n nil! for i in 0.. 0: + typ = makeTypeFromExpr(c, n.copyTree) + else: + if expectedElemType != nil and + (expectedElemType.kind != tyNil and not hasEmpty(expectedElemType)): + # hasEmpty/nil check is to not break existing code like + # `const foo = [(1, {}), (2, {false})]`, + # `const foo = if true: (0, nil) else: (1, new(int))` + n[i] = fitNode(c, expectedElemType, n[i], n[i].info) + addSonSkipIntLit(typ, n[i].typ.skipTypes({tySink}), c.idgen) result.typ() = typ include semobjconstr @@ -3074,6 +3080,11 @@ proc semExport(c: PContext, n: PNode): PNode = proc semTupleConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType = nil): PNode = var tupexp = semTuplePositionsConstr(c, n, flags, expectedType) + # convert `tupexp` to typedesc if necessary: + if tupexp.typ.kind == tyFromExpr: + # tyFromExpr is already ambivalent between types and values + result = tupexp + return var isTupleType: bool = false if tupexp.len > 0: # don't interpret () as type isTupleType = tupexp[0].typ.kind == tyTypeDesc diff --git a/tests/proc/tgenericdefaultparam.nim b/tests/proc/tgenericdefaultparam.nim index d20af411ed1eb..04f0774308f93 100644 --- a/tests/proc/tgenericdefaultparam.nim +++ b/tests/proc/tgenericdefaultparam.nim @@ -118,3 +118,13 @@ block: # issue #24484, array version doAssert t == [T(0), 5] bar[uint8]() +block: # issue #24484, tuple version + type E = enum A + proc foo[T](t = (T.A,)) = + discard + foo[E]() + + proc bar[T](t: (T, int) = (T(0), 5)) = + doAssert t == (T(0), 5) + bar[uint8]() + From afea1c859f5946c6c083db570760ecd54a871bd2 Mon Sep 17 00:00:00 2001 From: metagn Date: Tue, 3 Dec 2024 09:14:23 +0300 Subject: [PATCH 3/6] Update compiler/semexprs.nim Co-authored-by: Andreas Rumpf --- compiler/semexprs.nim | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index d2ed0f2220feb..698ea1d185713 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -3083,8 +3083,7 @@ proc semTupleConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PTyp # convert `tupexp` to typedesc if necessary: if tupexp.typ.kind == tyFromExpr: # tyFromExpr is already ambivalent between types and values - result = tupexp - return + return tupexp var isTupleType: bool = false if tupexp.len > 0: # don't interpret () as type isTupleType = tupexp[0].typ.kind == tyTypeDesc From 8a9d0acd1f6d89671e7533b9a759ba62c6a1cc9e Mon Sep 17 00:00:00 2001 From: metagn Date: Fri, 7 Feb 2025 20:13:29 +0300 Subject: [PATCH 4/6] add test for #24672 --- tests/proc/tgenericdefaultparam.nim | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/proc/tgenericdefaultparam.nim b/tests/proc/tgenericdefaultparam.nim index 04f0774308f93..038110f5d5173 100644 --- a/tests/proc/tgenericdefaultparam.nim +++ b/tests/proc/tgenericdefaultparam.nim @@ -128,3 +128,8 @@ block: # issue #24484, tuple version doAssert t == (T(0), 5) bar[uint8]() +block: # issue #24672 + func initArray[T](arg: array[1, T] = [T.high]): array[1, T] = + return arg + + discard initArray[float]() # this would compile and print [inf] in previous versions. From 4ca2d6a3586e0793ade4758ce4ad47f4a6a58eae Mon Sep 17 00:00:00 2001 From: metagn Date: Fri, 11 Apr 2025 15:53:34 +0300 Subject: [PATCH 5/6] attempt to simplify tuples --- compiler/semexprs.nim | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 4cbbb5a422915..c94c0749ebcc0 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -2883,26 +2883,27 @@ proc semTuplePositionsConstr(c: PContext, n: PNode, flags: TExprFlags; expectedT var typ = newTypeS(tyTuple, c) # leave typ.n nil! for i in 0.. 0: - typ = makeTypeFromExpr(c, n.copyTree) - else: - if expectedElemType != nil and - (expectedElemType.kind != tyNil and not hasEmpty(expectedElemType)): - # hasEmpty/nil check is to not break existing code like - # `const foo = [(1, {}), (2, {false})]`, - # `const foo = if true: (0, nil) else: (1, new(int))` - let conversion = indexTypesMatch(c, expectedElemType, n[i].typ, n[i]) - # ignore matching error, full tuple will be matched later which may call converter, see #24609 - if conversion != nil: - n[i] = conversion - addSonSkipIntLit(typ, n[i].typ.skipTypes({tySink}), c.idgen) + n[i] = semExprWithType(c, n[i], {}, expectedElemType) + if c.inGenericContext > 0 and n[i].typ != nil and + n[i].typ.kind == tyFromExpr: + # tuple field depends on generic expression, consider remainder of tuple as such + for j in (i + 1) ..< n.len: + n[j] = semGenericStmt(c, n[j]) + result.typ() = makeTypeFromExpr(c, n.copyTree) + return + if expectedElemType != nil and + (expectedElemType.kind != tyNil and not hasEmpty(expectedElemType)): + # hasEmpty/nil check is to not break existing code like + # `const foo = [(1, {}), (2, {false})]`, + # `const foo = if true: (0, nil) else: (1, new(int))` + let conversion = indexTypesMatch(c, expectedElemType, n[i].typ, n[i]) + # ignore matching error, full tuple will be matched later which may call converter, see #24609 + if conversion != nil: + n[i] = conversion + addSonSkipIntLit(typ, n[i].typ.skipTypes({tySink}), c.idgen) let oldType = n.typ result.typ() = typ - if oldType != nil and not hasEmpty(oldType) and typ.kind != tyFromExpr: # see hasEmpty comment above + if oldType != nil and not hasEmpty(oldType): # see hasEmpty comment above # convert back to old type let conversion = indexTypesMatch(c, oldType, typ, result) # ignore matching error, the goal is just to keep the original type info From fc08ed66ca99fbaf9df543bb14e3f51568be3a7a Mon Sep 17 00:00:00 2001 From: metagn Date: Fri, 11 Apr 2025 16:20:04 +0300 Subject: [PATCH 6/6] attempt more uniform cleanup --- compiler/semexprs.nim | 113 +++++++++++++++++++++++++----------------- 1 file changed, 68 insertions(+), 45 deletions(-) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index c94c0749ebcc0..de4ea2cc2b376 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -773,7 +773,11 @@ proc semArrayConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PTyp let yy = semExprWithType(c, x, {efTypeAllowed}, expectedElementType) var typ: PType - if constructType: + var isGeneric = false + if yy.typ != nil and yy.typ.kind == tyFromExpr: + isGeneric = true + typ = nil # will not be used + elif constructType: typ = yy.typ if expectedElementType == nil: expectedElementType = typ @@ -796,26 +800,27 @@ proc semArrayConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PTyp localError(c.config, x.info, "invalid order in array constructor") x = x[1] - if typ.kind == tyFromExpr and c.inGenericContext > 0: - result.add semGenericStmt(c, x) - else: - let xx = semExprWithType(c, x, {efTypeAllowed}, expectedElementType) - result.add xx - if constructType: - typ = commonType(c, typ, xx.typ) + let xx = semExprWithType(c, x, {efTypeAllowed}, expectedElementType) + result.add xx + if xx.typ != nil and xx.typ.kind == tyFromExpr: + isGeneric = true + elif constructType: + typ = commonType(c, typ, xx.typ) #n[i] = semExprWithType(c, x, {}) #result.add fitNode(c, typ, n[i]) inc(lastIndex) - if typ.kind == tyFromExpr and c.inGenericContext > 0: - if constructType: - result.typ() = nil # current result.typ is invalid, index type is nil - result.typ() = makeTypeFromExpr(c, result.copyTree) - return - else: - if constructType: - addSonSkipIntLit(result.typ, typ, c.idgen) + if isGeneric: for i in 0.. 0: - n[i] = semGenericStmt(c, n[i]) - elif isRange(n[i]): + if isRange(n[i]): checkSonsLen(n[i], 3, c.config) n[i][1] = semExprWithType(c, n[i][1], {efTypeAllowed}, expectedElementType) n[i][2] = semExprWithType(c, n[i][2], {efTypeAllowed}, expectedElementType) - if doSetType: - typ = skipTypes(n[i][1].typ, - {tyGenericInst, tyVar, tyLent, tyOrdinal, tyAlias, tySink}) - n[i].typ() = n[i][2].typ # range node needs type too + if (n[i][1].typ != nil and n[i][1].typ.kind == tyFromExpr) or + (n[i][2].typ != nil and n[i][2].typ.kind == tyFromExpr): + isGeneric = true + else: + if doSetType: + typ = skipTypes(n[i][1].typ, + {tyGenericInst, tyVar, tyLent, tyOrdinal, tyAlias, tySink}) + n[i].typ() = n[i][2].typ # range node needs type too elif n[i].kind == nkRange: # already semchecked if doSetType: @@ -2736,13 +2744,12 @@ proc semSetConstr(c: PContext, n: PNode, expectedType: PType = nil): PNode = {tyGenericInst, tyVar, tyLent, tyOrdinal, tyAlias, tySink}) else: n[i] = semExprWithType(c, n[i], {efTypeAllowed}, expectedElementType) - if doSetType: + if n[i].typ != nil and n[i].typ.kind == tyFromExpr: + isGeneric = true + elif doSetType: typ = skipTypes(n[i].typ, {tyGenericInst, tyVar, tyLent, tyOrdinal, tyAlias, tySink}) - if doSetType: - if typ.kind == tyFromExpr and c.inGenericContext > 0: - # propagate it as set element type - discard - elif not isOrdinalType(typ, allowEnumWithHoles=true): + if doSetType and not isGeneric: + if not isOrdinalType(typ, allowEnumWithHoles=true): localError(c.config, n.info, errOrdinalTypeExpected % typeToString(typ, preferDesc)) typ = makeRangeType(c, 0, MaxSetElements-1, n.info) elif isIntLit(typ): @@ -2756,16 +2763,19 @@ proc semSetConstr(c: PContext, n: PNode, expectedType: PType = nil): PNode = typ = makeRangeType(c, 0, MaxSetElements-1, n.info) if expectedElementType == nil: expectedElementType = typ - if typ.kind == tyFromExpr and c.inGenericContext > 0: + if isGeneric: + for i in 0.. 0: - m = n[i] - elif isRange(n[i]): + if isRange(n[i]): m = newNodeI(nkRange, info) m.add fitNode(c, typ, n[i][1], info) m.add fitNode(c, typ, n[i][2], info) @@ -2833,6 +2843,7 @@ proc semTupleFieldsConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType var typ = newTypeS(tyTuple, c) typ.n = newNodeI(nkRecList, n.info) # nkIdentDefs var ids = initIntSet() + var isGeneric = false for i in 0.. 0 and n[i].typ != nil and - n[i].typ.kind == tyFromExpr: - # tuple field depends on generic expression, consider remainder of tuple as such - for j in (i + 1) ..< n.len: - n[j] = semGenericStmt(c, n[j]) - result.typ() = makeTypeFromExpr(c, n.copyTree) - return - if expectedElemType != nil and + if n[i].typ != nil and n[i].typ.kind == tyFromExpr: + isGeneric = true + elif expectedElemType != nil and (expectedElemType.kind != tyNil and not hasEmpty(expectedElemType)): # hasEmpty/nil check is to not break existing code like # `const foo = [(1, {}), (2, {false})]`, @@ -2901,6 +2917,13 @@ proc semTuplePositionsConstr(c: PContext, n: PNode, flags: TExprFlags; expectedT if conversion != nil: n[i] = conversion addSonSkipIntLit(typ, n[i].typ.skipTypes({tySink}), c.idgen) + if isGeneric: + for i in 0..