Code review comment: I think mapping ctor expression is more suitable as we don't know the error type completely yet

Code before review:
public void visit(BLangErrorConstructorExpr errorConstructorExpr) {
BLangUserDefinedType userProvidedTypeRef = errorConstructorExpr.errorTypeRef;
if (userProvidedTypeRef != null) {
symResolver.resolveTypeNode(userProvidedTypeRef, env, DiagnosticErrorCode.UNDEFINED_ERROR_TYPE_DESCRIPTOR);
}
validateErrorConstructorPositionalArgs(errorConstructorExpr);
List<BType> expandedCandidates = getTypeCandidatesForErrorConstructor(errorConstructorExpr);
List<BType> errorDetailTypes = new ArrayList<>(expandedCandidates.size());
for (BType expandedCandidate : expandedCandidates) {
BType detailType = ((BErrorType) expandedCandidate).detailType;
errorDetailTypes.add(detailType);
}
BType detailCandidate;
if (errorDetailTypes.size() == 1) {
detailCandidate = errorDetailTypes.get(0);
} else {
detailCandidate = BUnionType.create(null, new LinkedHashSet<>(errorDetailTypes));
}
BLangRecordLiteral recordLiteral = createRecordLiteralForErrorConstructor(errorConstructorExpr);
BType inferredDetailType = checkExprSilent(recordLiteral, detailCandidate, env);
int index = errorDetailTypes.indexOf(inferredDetailType);
if (index < 0) {
resultType = symTable.semanticError;
dlog.error(errorConstructorExpr.pos, DiagnosticErrorCode.INCOMPATIBLE_TYPES, symTable.errorType, expType);
return;
}
BType selectedCandidate = expandedCandidates.get(index);
if (selectedCandidate != symTable.semanticError
&& (userProvidedTypeRef == null || userProvidedTypeRef.getBType() == selectedCandidate)) {
checkProvidedErrorDetails(errorConstructorExpr, inferredDetailType);
resultType = types.checkType(errorConstructorExpr.pos, selectedCandidate, expType,
DiagnosticErrorCode.INCOMPATIBLE_TYPES);
return;
}
if (userProvidedTypeRef == null && errorDetailTypes.size() > 1) {
dlog.error(errorConstructorExpr.pos, DiagnosticErrorCode.CANNOT_INFER_ERROR_TYPE, expType);
}
BErrorType errorType;
if (userProvidedTypeRef != null && userProvidedTypeRef.getBType().tag == TypeTags.ERROR) {
errorType = (BErrorType) userProvidedTypeRef.getBType();
} else if (expandedCandidates.size() == 1) {
errorType = (BErrorType) expandedCandidates.get(0);
} else {
errorType = symTable.errorType;
}
List<BLangNamedArgsExpression> namedArgs =
checkProvidedErrorDetails(errorConstructorExpr, errorType.detailType);
BType detailType = errorType.detailType;
if (detailType.tag == TypeTags.MAP) {
BType errorDetailTypeConstraint = ((BMapType) detailType).constraint;
for (BLangNamedArgsExpression namedArgExpr: namedArgs) {
if (!types.isAssignable(namedArgExpr.expr.getBType(), errorDetailTypeConstraint)) {
dlog.error(namedArgExpr.pos, DiagnosticErrorCode.INVALID_ERROR_DETAIL_ARG_TYPE,
namedArgExpr.name, errorDetailTypeConstraint, namedArgExpr.expr.getBType());
}
}
} else if (detailType.tag == TypeTags.RECORD) {
BRecordType targetErrorDetailRec = (BRecordType) errorType.detailType;
LinkedList<String> missingRequiredFields = targetErrorDetailRec.fields.values().stream()
.filter(f -> (f.symbol.flags & Flags.REQUIRED) == Flags.REQUIRED)
.map(f -> f.name.value)
.collect(Collectors.toCollection(LinkedList::new));
LinkedHashMap<String, BField> targetFields = targetErrorDetailRec.fields;
for (BLangNamedArgsExpression namedArg : namedArgs) {
BField field = targetFields.get(namedArg.name.value);
Location pos = namedArg.pos;
if (field == null) {
if (targetErrorDetailRec.sealed) {
dlog.error(pos, DiagnosticErrorCode.UNKNOWN_DETAIL_ARG_TO_CLOSED_ERROR_DETAIL_REC,
namedArg.name, targetErrorDetailRec);
} else if (targetFields.isEmpty()
&& !types.isAssignable(namedArg.expr.getBType(), targetErrorDetailRec.restFieldType)) {
dlog.error(pos, DiagnosticErrorCode.INVALID_ERROR_DETAIL_REST_ARG_TYPE,
namedArg.name, targetErrorDetailRec);
}
} else {
missingRequiredFields.remove(namedArg.name.value);
if (!types.isAssignable(namedArg.expr.getBType(), field.type)) {
dlog.error(pos, DiagnosticErrorCode.INVALID_ERROR_DETAIL_ARG_TYPE,
namedArg.name, field.type, namedArg.expr.getBType());
}
}
}
for (String requiredField : missingRequiredFields) {
dlog.error(errorConstructorExpr.pos, DiagnosticErrorCode.MISSING_ERROR_DETAIL_ARG, requiredField);
}
}
if (userProvidedTypeRef != null) {
errorConstructorExpr.setBType(userProvidedTypeRef.getBType());
} else {
errorConstructorExpr.setBType(errorType);
}
resultType = errorConstructorExpr.getBType();
}

Line under review: dlog.error(errorConstructorExpr.pos, DiagnosticErrorCode.INCOMPATIBLE_TYPES, symTable.errorType, expType);

Code after addressing the review:
public void visit(BLangErrorConstructorExpr errorConstructorExpr) {
BLangUserDefinedType userProvidedTypeRef = errorConstructorExpr.errorTypeRef;
if (userProvidedTypeRef != null) {
symResolver.resolveTypeNode(userProvidedTypeRef, env, DiagnosticErrorCode.UNDEFINED_ERROR_TYPE_DESCRIPTOR);
}
validateErrorConstructorPositionalArgs(errorConstructorExpr);
List<BType> expandedCandidates = getTypeCandidatesForErrorConstructor(errorConstructorExpr);
List<BType> errorDetailTypes = new ArrayList<>(expandedCandidates.size());
for (BType expandedCandidate : expandedCandidates) {
BType detailType = ((BErrorType) expandedCandidate).detailType;
errorDetailTypes.add(detailType);
}
BType detailCandidate;
if (errorDetailTypes.size() == 1) {
detailCandidate = errorDetailTypes.get(0);
} else {
detailCandidate = BUnionType.create(null, new LinkedHashSet<>(errorDetailTypes));
}
BLangRecordLiteral recordLiteral = createRecordLiteralForErrorConstructor(errorConstructorExpr);
BType inferredDetailType = checkExprSilent(recordLiteral, detailCandidate, env);
int index = errorDetailTypes.indexOf(inferredDetailType);
BType selectedCandidate = index < 0 ? symTable.semanticError : expandedCandidates.get(index);
if (selectedCandidate != symTable.semanticError
&& (userProvidedTypeRef == null || userProvidedTypeRef.getBType() == selectedCandidate)) {
checkProvidedErrorDetails(errorConstructorExpr, inferredDetailType);
resultType = types.checkType(errorConstructorExpr.pos, selectedCandidate, expType,
DiagnosticErrorCode.INCOMPATIBLE_TYPES);
return;
}
if (userProvidedTypeRef == null && errorDetailTypes.size() > 1) {
dlog.error(errorConstructorExpr.pos, DiagnosticErrorCode.CANNOT_INFER_ERROR_TYPE, expType);
}
boolean validTypeRefFound = false;
BErrorType errorType;
if (userProvidedTypeRef != null && userProvidedTypeRef.getBType().tag == TypeTags.ERROR) {
errorType = (BErrorType) userProvidedTypeRef.getBType();
validTypeRefFound = true;
} else if (expandedCandidates.size() == 1) {
errorType = (BErrorType) expandedCandidates.get(0);
} else {
errorType = symTable.errorType;
}
List<BLangNamedArgsExpression> namedArgs =
checkProvidedErrorDetails(errorConstructorExpr, errorType.detailType);
BType detailType = errorType.detailType;
if (detailType.tag == TypeTags.MAP) {
BType errorDetailTypeConstraint = ((BMapType) detailType).constraint;
for (BLangNamedArgsExpression namedArgExpr: namedArgs) {
if (!types.isAssignable(namedArgExpr.expr.getBType(), errorDetailTypeConstraint)) {
dlog.error(namedArgExpr.pos, DiagnosticErrorCode.INVALID_ERROR_DETAIL_ARG_TYPE,
namedArgExpr.name, errorDetailTypeConstraint, namedArgExpr.expr.getBType());
}
}
} else if (detailType.tag == TypeTags.RECORD) {
BRecordType targetErrorDetailRec = (BRecordType) errorType.detailType;
LinkedList<String> missingRequiredFields = targetErrorDetailRec.fields.values().stream()
.filter(f -> (f.symbol.flags & Flags.REQUIRED) == Flags.REQUIRED)
.map(f -> f.name.value)
.collect(Collectors.toCollection(LinkedList::new));
LinkedHashMap<String, BField> targetFields = targetErrorDetailRec.fields;
for (BLangNamedArgsExpression namedArg : namedArgs) {
BField field = targetFields.get(namedArg.name.value);
Location pos = namedArg.pos;
if (field == null) {
if (targetErrorDetailRec.sealed) {
dlog.error(pos, DiagnosticErrorCode.UNKNOWN_DETAIL_ARG_TO_CLOSED_ERROR_DETAIL_REC,
namedArg.name, targetErrorDetailRec);
} else if (targetFields.isEmpty()
&& !types.isAssignable(namedArg.expr.getBType(), targetErrorDetailRec.restFieldType)) {
dlog.error(pos, DiagnosticErrorCode.INVALID_ERROR_DETAIL_REST_ARG_TYPE,
namedArg.name, targetErrorDetailRec);
}
} else {
missingRequiredFields.remove(namedArg.name.value);
if (!types.isAssignable(namedArg.expr.getBType(), field.type)) {
dlog.error(pos, DiagnosticErrorCode.INVALID_ERROR_DETAIL_ARG_TYPE,
namedArg.name, field.type, namedArg.expr.getBType());
}
}
}
for (String requiredField : missingRequiredFields) {
dlog.error(errorConstructorExpr.pos, DiagnosticErrorCode.MISSING_ERROR_DETAIL_ARG, requiredField);
}
}
if (userProvidedTypeRef != null) {
errorConstructorExpr.setBType(userProvidedTypeRef.getBType());
} else {
errorConstructorExpr.setBType(errorType);
}
BType resolvedType = errorConstructorExpr.getBType();
if (resolvedType != symTable.semanticError && expType != symTable.noType &&
!types.isAssignable(resolvedType, expType)) {
if (validTypeRefFound) {
dlog.error(errorConstructorExpr.pos, DiagnosticErrorCode.INCOMPATIBLE_TYPES,
expType, userProvidedTypeRef);
} else {
dlog.error(errorConstructorExpr.pos,
DiagnosticErrorCode.ERROR_CONSTRUCTOR_COMPATIBLE_TYPE_NOT_FOUND, expType);
}
resultType = symTable.semanticError;
return;
}
resultType = resolvedType;
}