From 792d75a5d8491a5f60ca658694684d1e48ede8df Mon Sep 17 00:00:00 2001 From: Sung-Shik Jongmans Date: Fri, 19 Jun 2026 10:17:02 +0200 Subject: [PATCH 1/5] Add test for issue that uses of common keyword fields of ADTs aren't collected properly (#2193) --- .../lang/rascalcore/check/tests/UseDefTests.rsc | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/org/rascalmpl/compiler/lang/rascalcore/check/tests/UseDefTests.rsc b/src/org/rascalmpl/compiler/lang/rascalcore/check/tests/UseDefTests.rsc index 8a812d28544..ea83526749c 100644 --- a/src/org/rascalmpl/compiler/lang/rascalcore/check/tests/UseDefTests.rsc +++ b/src/org/rascalmpl/compiler/lang/rascalcore/check/tests/UseDefTests.rsc @@ -107,4 +107,19 @@ test bool overloadedSyntaxField(){ return useDefOK(mtext, ("c": <0, {2}>)) // check uses of first declaration of c && useDefOK(mtext, ("c": <1, {3}>)) // check uses of first declaration of c ; -} \ No newline at end of file +} + +test bool issue2193() = + useDefOK("module Issue2193 + data D(int foo = 1) = d(int bar = 2); + void main() { + D x = d(); + x.foo = 16; + y = x.foo; + }", + ( + "D": <0, {1}>, + "foo": <0, {1, 2}>, + "d": <0, {1}>, + "x": <0, {1, 2}> + )); \ No newline at end of file From 5297c7e90a99d1cc816df7577a397c4e0212a729 Mon Sep 17 00:00:00 2001 From: Sung-Shik Jongmans Date: Fri, 19 Jun 2026 14:26:55 +0200 Subject: [PATCH 2/5] Fix issue that uses of common keyword fields of ADTs weren't collected properly (#2193) --- .../rascalcore/check/CollectDataDeclaration.rsc | 13 ++++++++++++- .../rascalcore/check/tests/ChangeAndHashTests.rsc | 6 +++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/org/rascalmpl/compiler/lang/rascalcore/check/CollectDataDeclaration.rsc b/src/org/rascalmpl/compiler/lang/rascalcore/check/CollectDataDeclaration.rsc index 2f0dcd52f02..958a58c839c 100644 --- a/src/org/rascalmpl/compiler/lang/rascalcore/check/CollectDataDeclaration.rsc +++ b/src/org/rascalmpl/compiler/lang/rascalcore/check/CollectDataDeclaration.rsc @@ -54,6 +54,7 @@ int dataCounter = 0; void dataDeclaration(Tags tags, Declaration current, list[Variant] variants, Collector c){ userType = current.user; + currentModuleName = str s := c.top(key_current_module) ? s : ""; adtName = prettyPrintName(userType.name); tagsMap = getTags(tags); @@ -80,7 +81,17 @@ void dataDeclaration(Tags tags, Declaration current, list[Variant] variants, Col beginDefineOrReuseTypeParameters(c, closed=true); collect(typeParameters, c); if(!isEmpty(commonKeywordParameterList)){ - collect(commonKeywordParameterList, c); + declaredFieldNames = {}; + for(KeywordFormal kwf <- commonKeywordParameterList){ + fieldName = prettyPrintName(kwf.name); + if(fieldName in declaredFieldNames) c.report(error(kwf, "Double declaration of field `%v`", fieldName)); + declaredFieldNames += fieldName; + kwfType = kwf.\type; + dt = defType([kwfType], makeKeywordFieldType(fieldName, kwf)); + dt.md5 = normalizedMD5Hash(currentModuleName, adtName, kwf); + c.define(kwf.name, keywordFieldId(), kwf.name, dt); + } + for(kwa <- commonKeywordParameterList) { c.enterScope(kwa); collect(kwa.\type, kwa.expression, c); c.fact(kwa, kwa.\type); c.leaveScope(kwa); } } endDefineOrReuseTypeParameters(c); diff --git a/src/org/rascalmpl/compiler/lang/rascalcore/check/tests/ChangeAndHashTests.rsc b/src/org/rascalmpl/compiler/lang/rascalcore/check/tests/ChangeAndHashTests.rsc index b40dc9ed3e0..4053e151b20 100644 --- a/src/org/rascalmpl/compiler/lang/rascalcore/check/tests/ChangeAndHashTests.rsc +++ b/src/org/rascalmpl/compiler/lang/rascalcore/check/tests/ChangeAndHashTests.rsc @@ -282,10 +282,10 @@ test bool nearClonesDifferentParameterNameOK() = checkModuleOK(" // Data declarations test bool commonKwFieldAdded() - = expectEqual("data D;", "data D(int x = 0);"); + = expectSubset("data D;", "data D(int x = 0);"); -test bool commenKwFieldDeleted() - = expectEqual("data D(int x = 0);", "data D;"); +test bool commonKwFieldDeleted() + = expectSuperset("data D(int x = 0);", "data D;"); test bool consAdded() = expectSubset("data D;", "data D = d(int n);"); From 46daca0f06606291aa6882b3f6fa37f39bfeba16 Mon Sep 17 00:00:00 2001 From: Sung-Shik Jongmans Date: Fri, 19 Jun 2026 14:34:09 +0200 Subject: [PATCH 3/5] Add helper function to define ADT fields in collector (refactoring to avoid code duplication) --- .../check/CollectDataDeclaration.rsc | 58 ++++++++++--------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/src/org/rascalmpl/compiler/lang/rascalcore/check/CollectDataDeclaration.rsc b/src/org/rascalmpl/compiler/lang/rascalcore/check/CollectDataDeclaration.rsc index 958a58c839c..6798435a223 100644 --- a/src/org/rascalmpl/compiler/lang/rascalcore/check/CollectDataDeclaration.rsc +++ b/src/org/rascalmpl/compiler/lang/rascalcore/check/CollectDataDeclaration.rsc @@ -83,15 +83,16 @@ void dataDeclaration(Tags tags, Declaration current, list[Variant] variants, Col if(!isEmpty(commonKeywordParameterList)){ declaredFieldNames = {}; for(KeywordFormal kwf <- commonKeywordParameterList){ - fieldName = prettyPrintName(kwf.name); - if(fieldName in declaredFieldNames) c.report(error(kwf, "Double declaration of field `%v`", fieldName)); - declaredFieldNames += fieldName; - kwfType = kwf.\type; - dt = defType([kwfType], makeKeywordFieldType(fieldName, kwf)); - dt.md5 = normalizedMD5Hash(currentModuleName, adtName, kwf); - c.define(kwf.name, keywordFieldId(), kwf.name, dt); + declaredFieldNames = defineField(c, kwf, keywordFieldId(), declaredFieldNames, + moreHashContribs = [currentModuleName, adtName]); + } + + for(KeywordFormal kwf <- commonKeywordParameterList){ + c.enterScope(kwf); + collect(kwf.\type, kwf.expression, c); + c.fact(kwf, kwf.\type); + c.leaveScope(kwf); } - for(kwa <- commonKeywordParameterList) { c.enterScope(kwa); collect(kwa.\type, kwa.expression, c); c.fact(kwa, kwa.\type); c.leaveScope(kwa); } } endDefineOrReuseTypeParameters(c); @@ -144,27 +145,14 @@ void collect(current:(Variant) ` ( <{TypeArg ","}* arguments> ( <{TypeArg ","}* arguments> Date: Mon, 22 Jun 2026 12:53:30 +0200 Subject: [PATCH 4/5] Fix type error --- .../check/CollectDataDeclaration.rsc | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/org/rascalmpl/compiler/lang/rascalcore/check/CollectDataDeclaration.rsc b/src/org/rascalmpl/compiler/lang/rascalcore/check/CollectDataDeclaration.rsc index 6798435a223..36d1fe8e2a7 100644 --- a/src/org/rascalmpl/compiler/lang/rascalcore/check/CollectDataDeclaration.rsc +++ b/src/org/rascalmpl/compiler/lang/rascalcore/check/CollectDataDeclaration.rsc @@ -178,17 +178,21 @@ void collect(current:(Variant) ` ( <{TypeArg ","}* arguments> ` := fieldDef || + (KeywordFormal) ` = ` := fieldDef) { - str fieldOrgId = ""; - str fieldId = prettyPrintName(fieldOrgId); - if (fieldId in declaredFieldIds) c.report(error(fieldDef, "Double declaration of field `%v`", fieldId)); - declaredFieldIds += fieldId; + str fieldOrgId = ""; + str fieldId = prettyPrintName(fieldOrgId); + if (fieldId in declaredFieldIds) c.report(error(fieldDef, "Double declaration of field `%v`", fieldId)); + declaredFieldIds += fieldId; - Type fieldType = fieldDef.\type; - DefInfo fieldDefInfo = defType([fieldType], makeFieldType(fieldId, fieldType)); - fieldDefInfo.md5 = normalizedMD5Hash([fieldId, fieldType, *moreHashContribs]); + DefInfo fieldDefInfo = defType([fieldType], makeFieldType(fieldId, fieldType)); + fieldDefInfo.md5 = normalizedMD5Hash([fieldId, fieldType, *moreHashContribs]); + + c.define(name, fieldIdRole, fieldDef, fieldDefInfo); + } else { + throw "Cannot define field: ``"; + } - c.define(fieldDef.name, fieldIdRole, fieldDef, fieldDefInfo); return declaredFieldIds; } \ No newline at end of file From 547a00bbd19f25993c687aec61f94bbbb785c786 Mon Sep 17 00:00:00 2001 From: Sung-Shik Jongmans Date: Mon, 22 Jun 2026 14:03:17 +0200 Subject: [PATCH 5/5] Fix regressions --- .../check/CollectDataDeclaration.rsc | 67 +++++++++++++------ .../rascalcore/check/tests/UseDefTests.rsc | 28 +++++++- 2 files changed, 75 insertions(+), 20 deletions(-) diff --git a/src/org/rascalmpl/compiler/lang/rascalcore/check/CollectDataDeclaration.rsc b/src/org/rascalmpl/compiler/lang/rascalcore/check/CollectDataDeclaration.rsc index 36d1fe8e2a7..104bb394871 100644 --- a/src/org/rascalmpl/compiler/lang/rascalcore/check/CollectDataDeclaration.rsc +++ b/src/org/rascalmpl/compiler/lang/rascalcore/check/CollectDataDeclaration.rsc @@ -76,30 +76,36 @@ void dataDeclaration(Tags tags, Declaration current, list[Variant] variants, Col c.define(userType.name, dataId(), current, dt); adtParentScope = c.getScope(); - c.enterScope(current); - c.push(currentAdt, ); - beginDefineOrReuseTypeParameters(c, closed=true); - collect(typeParameters, c); - if(!isEmpty(commonKeywordParameterList)){ - declaredFieldNames = {}; - for(KeywordFormal kwf <- commonKeywordParameterList){ - declaredFieldNames = defineField(c, kwf, keywordFieldId(), declaredFieldNames, - moreHashContribs = [currentModuleName, adtName]); - } + c.push(currentAdt, ); + NestedScopes nestedScopes = newNestedScopes(c); + beginDefineOrReuseTypeParameters(c, closed=true); + collect(typeParameters, c); + if(!isEmpty(commonKeywordParameterList)){ + nestedScopes.enter(current.commonKeywordParameters); + declaredFieldNames = {}; for(KeywordFormal kwf <- commonKeywordParameterList){ - c.enterScope(kwf); - collect(kwf.\type, kwf.expression, c); + // Collect default expression (of current common keyword + // field) in outer scope + collect(kwf.\type, kwf.expression, c); + // Define current common keyword field, and collect + // next common keyword fields (in subsequent iterations, + // if any), in inner scope(s) + nestedScopes.enter(kwf); + declaredFieldNames = defineField( + c, kwf, keywordFieldId(), declaredFieldNames, + moreHashContribs = [currentModuleName, adtName]); c.fact(kwf, kwf.\type); - c.leaveScope(kwf); } - } - endDefineOrReuseTypeParameters(c); - - // visit all the variants in the parent scope of the data declaration + } + endDefineOrReuseTypeParameters(c); + // Note: Don't leave scopes of common keyword fields yet, so they are + // accessible in the variants. + + nestedScopes.enter(current); collect(variants, c); - c.pop(currentAdt); - c.leaveScope(current); + nestedScopes.leaveAll(); + c.pop(currentAdt); } AType(Solver) makeFieldType(str fieldName, Tree fieldType) @@ -177,6 +183,29 @@ void collect(current:(Variant) ` ( <{TypeArg ","}* arguments> ; +} + private set[str] defineField(Collector c, Tree fieldDef, IdRole fieldIdRole, set[str] declaredFieldIds, list[value] moreHashContribs = []) { if ((TypeArg) ` ` := fieldDef || (KeywordFormal) ` = ` := fieldDef) { diff --git a/src/org/rascalmpl/compiler/lang/rascalcore/check/tests/UseDefTests.rsc b/src/org/rascalmpl/compiler/lang/rascalcore/check/tests/UseDefTests.rsc index ea83526749c..f86065936e7 100644 --- a/src/org/rascalmpl/compiler/lang/rascalcore/check/tests/UseDefTests.rsc +++ b/src/org/rascalmpl/compiler/lang/rascalcore/check/tests/UseDefTests.rsc @@ -122,4 +122,30 @@ test bool issue2193() = "foo": <0, {1, 2}>, "d": <0, {1}>, "x": <0, {1, 2}> - )); \ No newline at end of file + )); + +test bool issue2193Regression1(){ + mtext = "module Issue2193Regression1 + data D = d(int i); + data E(D d = d(5));"; + return useDefOK(mtext, ("d": <0, {2}>)) // check uses of first declaration of d + && useDefOK(mtext, ("d": <1, {}>)) // check uses of second declaration of d + && useDefOK(mtext, ( + "D": <0, {1}>, + "i": <0, {}>, + "E": <0, {}> + )); +} + +test bool issue2193Regression2(){ + mtext = "module Issue2193Regression1 + data E(D d = d(5)); + data D = d(int i);"; + return useDefOK(mtext, ("d": <0, {}>)) // check uses of first declaration of d + && useDefOK(mtext, ("d": <2, {1}>)) // check uses of second declaration of d + && useDefOK(mtext, ( + "E": <0, {}>, + "D": <1, {0}>, + "i": <0, {}> + )); +}