Skip to content

Records - #128

Merged
phax merged 7 commits into
masterfrom
records
Feb 3, 2026
Merged

Records#128
phax merged 7 commits into
masterfrom
records

Conversation

@phax

@phax phax commented Feb 3, 2026

Copy link
Copy Markdown
Owner

Based on #126

joelittlejohn and others added 7 commits February 1, 2026 10:04
This commit adds:

- EClassType.RECORD for record type declarations
- JRecordComponent class to represent record components
- _record() methods in IJClassContainer to create records
- recordComponent() and recordComponentVararg() methods in JDefinedClass
- compactConstructor() for validation-only constructors
- isRecord() helper method
- JDocComment.addParam(JRecordComponent) for javadoc support

Tests cover: basic records, empty records, object/array/
varargs components, generics, annotations, compact and canonical
constructors, methods, static members, nested records, and javadoc.
- Change from ValueEnforcer to explicit IllegalStateException throws
- Fix missing semicolons in throw statements
- Fix error message in recordComponentVararg()
- Add tests for calling record methods on non-record classes
- Add test for duplicate compact constructor
Add support for Java records (JEP 395, Java 16+)
@phax
phax merged commit 4556df1 into master Feb 3, 2026
4 checks passed
@phax
phax deleted the records branch February 3, 2026 11:06
@glelouet

glelouet commented Feb 3, 2026

Copy link
Copy Markdown
Collaborator

The record components should be used as variables too.

JRecordTest::testRecordWithCompactConstructor should use record components directly

		JRecordComponent lo = rec.recordComponent (cm.INT, "lo");
		JRecordComponent hi = rec.recordComponent (cm.INT, "hi");
 …
		compactCtor.body ()
							 ._if (lo.gt (hi))

Same on the next test methods testRecordWithCanonicalConstructor

Make JRecordComponent implement IJExpression ? Only default methods AFAIK, parent IJGenerable::generate already implemented since JRecordComponent implements IJGenerable.

@phax

phax commented Feb 3, 2026

Copy link
Copy Markdown
Owner Author

Yes, I've seen this too and tried making JRecordComponent and IJAssignmentTarget but it lead to invalid code. I guess we would need a JRecordComponentRef class instead

@glelouet

glelouet commented Feb 3, 2026

Copy link
Copy Markdown
Collaborator

can't be assignment target, it's const.

@phax

phax commented Feb 3, 2026

Copy link
Copy Markdown
Owner Author

Inside the constructor it is assigned to

@glelouet

glelouet commented Feb 3, 2026

Copy link
Copy Markdown
Collaborator

Yeah, also in future "with" it will be.

phax added a commit that referenced this pull request Feb 3, 2026
@phax

phax commented Feb 3, 2026

Copy link
Copy Markdown
Owner Author

@glelouet better?

@glelouet

glelouet commented Feb 3, 2026

Copy link
Copy Markdown
Collaborator

What do you mean by inside the constructor through ?
@phax

Ho I get it. Constructors of the record can set their own value, eg

Range(T min, T max){
 Range(Pair<T> p){
   this.min=p.first;
  this.max=p.second;
 }
}

@phax

phax commented Feb 3, 2026

Copy link
Copy Markdown
Owner Author

Syntactic sugar with fixed coding styleguide for getters and setters ;-)

@glelouet

glelouet commented Feb 3, 2026

Copy link
Copy Markdown
Collaborator

@phax The modified parts are better indeed, but the rest of the test is still to be changed ? (Not complaining, just stating)

@phax

phax commented Feb 3, 2026

Copy link
Copy Markdown
Owner Author

Feel free to complain ;-) Noted

@glelouet

glelouet commented Feb 3, 2026

Copy link
Copy Markdown
Collaborator

Syntactic sugar with fixed coding styleguide for getters and setters ;-)

Yeah I like that refthis can take a record into account. Something required when doing, say, record constructors :P

@glelouet

glelouet commented Feb 3, 2026

Copy link
Copy Markdown
Collaborator

Feel free to complain ;-) Noted

No point in complaining if that's not brought alongside an improvement effort.

@glelouet

glelouet commented Feb 3, 2026

Copy link
Copy Markdown
Collaborator

@phax

As a side note can we also have a throw() expression that takes a Class<? extends exception> or a codemodelreference as a parameter ?

I just realize the

				._throw (JExpr._new (cm.ref (IllegalArgumentException.class)));

Could definitely be improved into

				._throw (IllegalArgumentException.class);

If JBlock had a sugar coating corresponding method.

@phax

phax commented Feb 3, 2026

Copy link
Copy Markdown
Owner Author

Will do it later - have other priorities atm

@glelouet

glelouet commented Feb 3, 2026

Copy link
Copy Markdown
Collaborator

There is no codemodel reference in the JBlock so only using AbstractJClass ; added second method in JBlock :

	@NonNull
	public JThrow _throw (@NonNull final IJExpression aExpr)
	{
		return internalInsert (new JThrow (aExpr));
	}
	
	@NonNull
	public JThrow _throw (@NonNull final AbstractJClass throwClass)
	{
		return _throw (JExpr._new( throwClass));
	}

Only saves a _new call, not sure if worth.

@glelouet

glelouet commented Feb 3, 2026

Copy link
Copy Markdown
Collaborator

@phax can you show me where it is allowed to assign values to the record fields ?

I could not find a way in https://docs.oracle.com/en/java/javase/25/language/records.html#GUID-6699E26F-4A9B-4393-A08B-1E47D4B2D263

On the opposite, I found

You can define alternative, noncanonical constructors whose argument list doesn't match the record's type parameters. However, these constructors must invoke the record's canonical constructor.

Which implies that there is no assignment ?

edit : okay it's in the cannonical constructor.

        this.length = length;
        this.width = width;

phax added a commit that referenced this pull request Feb 3, 2026
phax added a commit that referenced this pull request Feb 3, 2026
@phax

phax commented Feb 3, 2026

Copy link
Copy Markdown
Owner Author

Took your proposal in - seems like a good compromise

@glelouet

glelouet commented Feb 3, 2026

Copy link
Copy Markdown
Collaborator

maybe could even add a varagar of params to pass to the _new call.
example use case _throw(cm.ref(UnsupportedOperationException.class), Jexpr.lit("case not supported") );

Just an idea though.

=> (only second method changed)

	@NonNull
	public JThrow _throw (@NonNull final IJExpression aExpr)
	{
		return internalInsert (new JThrow (aExpr));
	}
	
	@NonNull
	public JThrow _throw (@NonNull final AbstractJClass throwClass, IJExpression... params)
	{
		JInvocation init = JExpr._new( throwClass);
		if(params!=null )
		{
			for(IJExpression ije : params)
			{
				init.arg(ije);
			}
		}
		return _throw (init);
	}

phax added a commit that referenced this pull request Feb 3, 2026
@phax

phax commented Feb 3, 2026

Copy link
Copy Markdown
Owner Author

Good idea - done. But using a separate method

@glelouet

glelouet commented Feb 3, 2026

Copy link
Copy Markdown
Collaborator

@phax the null test was because the varargs was allowed null.
If you split in two cases : no varargs XOR varagars nonnull, no need to test for nullity in the second one.

@phax

phax commented Feb 3, 2026

Copy link
Copy Markdown
Owner Author

you can't have enough null checks ;-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants