Skip to content

Commit

Permalink
Fix Default Subclass Properties in checkUnset (#475)
Browse files Browse the repository at this point in the history
* auto-indent fillProps

* auto-indent fillConstructor

* make file objects non-handle

* fix constrained processing for constructors

Remove dependency on defined class names and instead operate on all properties.

* Support Set appending when parsing constrained

Originally was all-or-nothing. Now detects if a Set already exists and appends instead
when that occurs.

---------

Co-authored-by: Lawrence Niu <[email protected]>
  • Loading branch information
lawrence-mbf and lawrence-mbf authored Feb 2, 2023
1 parent 9400310 commit 16a5411
Show file tree
Hide file tree
Showing 8 changed files with 284 additions and 254 deletions.
2 changes: 1 addition & 1 deletion +file/Attribute.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
classdef Attribute < handle
classdef Attribute
properties
name; %attribute key
doc; %doc string
Expand Down
2 changes: 1 addition & 1 deletion +file/Dataset.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
classdef Dataset < handle
classdef Dataset
properties
name;
doc;
Expand Down
2 changes: 1 addition & 1 deletion +file/Group.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
classdef Group < handle
classdef Group
properties
doc;
name;
Expand Down
4 changes: 2 additions & 2 deletions +file/Link.m
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
classdef Link < handle
properties(SetAccess=private)
classdef Link
properties (SetAccess = private)
doc;
name;
required;
Expand Down
1 change: 0 additions & 1 deletion +file/fillClass.m
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@
name,...
depnm,...
defaults,... %all defaults, regardless of inheritance
[required optional],...
classprops,...
namespace);
setterFcns = file.fillSetters(setdiff(nonInherited, readonly));
Expand Down
305 changes: 153 additions & 152 deletions +file/fillConstructor.m
Original file line number Diff line number Diff line change
@@ -1,175 +1,176 @@
function fcstr = fillConstructor(name, parentname, defaults, propnames, props, namespace)
caps = upper(name);
fcnbody = ['% ' caps ' Constructor for ' name];
function fcstr = fillConstructor(name, parentname, defaults, props, namespace)
caps = upper(name);
fcnbody = ['% ' caps ' Constructor for ' name];

txt = fillBody(parentname, defaults, propnames, props, namespace);
if ~isempty(txt)
fcnbody = [fcnbody newline txt];
end
txt = fillBody(parentname, defaults, props, namespace);
if ~isempty(txt)
fcnbody = [fcnbody newline txt];
end

fcnbody = strjoin({fcnbody,...
sprintf('if strcmp(class(obj), ''%s'')', namespace.getFullClassName(name)),...
' types.util.checkUnset(obj, unique(varargin(1:2:end)));',...
'end'}, newline);
fcnbody = strjoin({fcnbody,...
sprintf('if strcmp(class(obj), ''%s'')', namespace.getFullClassName(name)),...
' types.util.checkUnset(obj, unique(varargin(1:2:end)));',...
'end'}, newline);

% insert check for DynamicTable class and child classes
txt = fillCheck(name, namespace);
if ~isempty(txt)
fcnbody = [fcnbody newline txt];
end
% insert check for DynamicTable class and child classes
txt = fillCheck(name, namespace);
if ~isempty(txt)
fcnbody = [fcnbody newline txt];
end

fcstr = strjoin({...
['function obj = ' name '(varargin)']...
file.addSpaces(fcnbody, 4)...
'end'}, newline);
fcstr = strjoin({...
['function obj = ' name '(varargin)']...
file.addSpaces(fcnbody, 4)...
'end'}, newline);

end

function bodystr = fillBody(pname, defaults, names, props, namespace)
if isempty(defaults)
bodystr = '';
else
overridemap = containers.Map;
for i=1:length(defaults)
nm = defaults{i};
if strcmp(props(nm).dtype, 'char')
overridemap(nm) = ['''' props(nm).value ''''];
else
overridemap(nm) =...
sprintf('types.util.correctType(%d, ''%s'')',...
props(nm).value,...
props(nm).dtype);
function bodystr = fillBody(pname, defaults, props, namespace)
if isempty(defaults)
bodystr = '';
else
overridemap = containers.Map;
for i=1:length(defaults)
nm = defaults{i};
if strcmp(props(nm).dtype, 'char')
overridemap(nm) = ['''' props(nm).value ''''];
else
overridemap(nm) =...
sprintf('types.util.correctType(%d, ''%s'')',...
props(nm).value,...
props(nm).dtype);
end
end
kwargs = io.map2kwargs(overridemap);
%add surrounding quotes to kwargs so misc.cellPrettyPrint can print them correctly
kwargs(1:2:end) = strcat('''', kwargs(1:2:end), '''');
bodystr = ['varargin = [{' misc.cellPrettyPrint(kwargs) '} varargin];' newline];
end
kwargs = io.map2kwargs(overridemap);
%add surrounding quotes to kwargs so misc.cellPrettyPrint can print them correctly
kwargs(1:2:end) = strcat('''', kwargs(1:2:end), '''');
bodystr = ['varargin = [{' misc.cellPrettyPrint(kwargs) '} varargin];' newline];
end
bodystr = [bodystr 'obj = obj@' pname '(varargin{:});'];
bodystr = [bodystr 'obj = obj@' pname '(varargin{:});'];

if isempty(names)
return;
end
% if there's a root object that is a constrained set, let it be hoistable from dynamic arguments
dynamicConstrained = false(size(names));
anon = false(size(names));
isattr = false(size(names));
typenames = repmat({''}, size(names));
varnames = repmat({''}, size(names));
for i=1:length(names)
nm = names{i};
prop = props(nm);

if isa(prop, 'file.Group') || isa(prop, 'file.Dataset')
dynamicConstrained(i) = prop.isConstrainedSet && strcmpi(nm, prop.type);
anon(i) = ~prop.isConstrainedSet && isempty(prop.name);

if ~isempty(prop.type)
varnames{i} = nm;
try
typenames{i} = namespace.getFullClassName(prop.type);
catch ME
if ~strcmp(ME.identifier, 'NWB:Scheme:Namespace:NotFound')
rethrow(ME);
names = keys(props);
if isempty(names)
return;
end
% if there's a root object that is a constrained set, let it be hoistable from dynamic arguments
dynamicConstrained = false(size(names));
anon = false(size(names));
isattr = false(size(names));
typenames = repmat({''}, size(names));
varnames = repmat({''}, size(names));
for i=1:length(names)
nm = names{i};
prop = props(nm);

if isa(prop, 'file.Group') || isa(prop, 'file.Dataset')
dynamicConstrained(i) = prop.isConstrainedSet && strcmpi(nm, prop.type);
anon(i) = ~prop.isConstrainedSet && isempty(prop.name);

if ~isempty(prop.type)
varnames{i} = nm;
try
typenames{i} = namespace.getFullClassName(prop.type);
catch ME
if ~strcmp(ME.identifier, 'NWB:Scheme:Namespace:NotFound')
rethrow(ME);
end
end
end
elseif isa(prop, 'file.Attribute')
isattr(i) = true;
end
elseif isa(prop, 'file.Attribute')
isattr(i) = true;
end
end

%warn for missing namespaces/property types
warnmsg = ['`' pname '`''s constructor is unable to check for type `%1$s` ' ...
'because its namespace or type specifier could not be found. Try generating ' ...
'the namespace or class definition for type `%1$s` or fix its schema.'];
%warn for missing namespaces/property types
warnmsg = ['`' pname '`''s constructor is unable to check for type `%1$s` ' ...
'because its namespace or type specifier could not be found. Try generating ' ...
'the namespace or class definition for type `%1$s` or fix its schema.'];

invalid = cellfun('isempty', typenames);
invalidWarn = invalid & (dynamicConstrained | anon) & ~isattr;
invalidVars = varnames(invalidWarn);
for i=1:length(invalidVars)
warning(warnmsg, invalidVars{i});
end
varnames = lower(varnames);

%we delete the entry in varargin such that any conflicts do not show up in inputParser
deleteFromVars = 'varargin(ivarargin) = [];';
%if constrained/anon sets exist, then check for nonstandard parameters and add as
%container.map
constrainedTypes = typenames(dynamicConstrained & ~invalid);
constrainedVars = varnames(dynamicConstrained & ~invalid);
methodCalls = strcat('[obj.', constrainedVars, ', ivarargin] = ',...
' types.util.parseConstrained(obj,''', constrainedVars, ''', ''',...
constrainedTypes, ''', varargin{:});');
fullBody = cell(length(methodCalls) * 2,1);
fullBody(1:2:end) = methodCalls;
fullBody(2:2:end) = {deleteFromVars};
fullBody = strjoin(fullBody, newline);
bodystr(end+1:end+length(fullBody)+1) = [newline fullBody];

%if anonymous values exist, then check for nonstandard parameters and add
%as Anon

anonTypes = typenames(anon & ~invalid);
anonVars = varnames(anon & ~invalid);
methodCalls = strcat('[obj.', anonVars, ',ivarargin] = ',...
' types.util.parseAnon(''', anonTypes, ''', varargin{:});');
fullBody = cell(length(methodCalls) * 2,1);
fullBody(1:2:end) = methodCalls;
fullBody(2:2:end) = {deleteFromVars};
fullBody = strjoin(fullBody, newline);
bodystr(end+1:end+length(fullBody)+1) = [newline fullBody];

parser = {...
'p = inputParser;',...
'p.KeepUnmatched = true;',...
'p.PartialMatching = false;',...
'p.StructExpand = false;'};

names = names(~dynamicConstrained & ~anon);
defaults = cell(size(names));
for i=1:length(names)
prop = props(names{i});
if (isa(prop, 'file.Group') &&...
(prop.hasAnonData || prop.hasAnonGroups || prop.isConstrainedSet)) ||...
(isa(prop, 'file.Dataset') && prop.isConstrainedSet)
defaults{i} = 'types.untyped.Set()';
else
defaults{i} = '[]';
invalid = cellfun('isempty', typenames);
invalidWarn = invalid & (dynamicConstrained | anon) & ~isattr;
invalidVars = varnames(invalidWarn);
for i=1:length(invalidVars)
warning(warnmsg, invalidVars{i});
end
end
% add parameters
parser = [parser, strcat('addParameter(p, ''', names, ''', ', defaults,');')];
% parse
parser = [parser, {'misc.parseSkipInvalidName(p, varargin);'}];
% get results
parser = [parser, strcat('obj.', names, ' = p.Results.', names, ';')];
parser = strjoin(parser, newline);
bodystr(end+1:end+length(parser)+1) = [newline parser];
varnames = lower(varnames);

%we delete the entry in varargin such that any conflicts do not show up in inputParser
deleteFromVars = 'varargin(ivarargin) = [];';
%if constrained/anon sets exist, then check for nonstandard parameters and add as
%container.map
constrainedTypes = typenames(dynamicConstrained & ~invalid);
constrainedVars = varnames(dynamicConstrained & ~invalid);
methodCalls = strcat('[obj.', constrainedVars, ', ivarargin] = ',...
' types.util.parseConstrained(obj,''', constrainedVars, ''', ''',...
constrainedTypes, ''', varargin{:});');
fullBody = cell(length(methodCalls) * 2,1);
fullBody(1:2:end) = methodCalls;
fullBody(2:2:end) = {deleteFromVars};
fullBody = strjoin(fullBody, newline);
bodystr(end+1:end+length(fullBody)+1) = [newline fullBody];

%if anonymous values exist, then check for nonstandard parameters and add
%as Anon

anonTypes = typenames(anon & ~invalid);
anonVars = varnames(anon & ~invalid);
methodCalls = strcat('[obj.', anonVars, ',ivarargin] = ',...
' types.util.parseAnon(''', anonTypes, ''', varargin{:});');
fullBody = cell(length(methodCalls) * 2,1);
fullBody(1:2:end) = methodCalls;
fullBody(2:2:end) = {deleteFromVars};
fullBody = strjoin(fullBody, newline);
bodystr(end+1:end+length(fullBody)+1) = [newline fullBody];

parser = {...
'p = inputParser;',...
'p.KeepUnmatched = true;',...
'p.PartialMatching = false;',...
'p.StructExpand = false;'};

names = names(~dynamicConstrained & ~anon);
defaults = cell(size(names));
for i=1:length(names)
prop = props(names{i});
if (isa(prop, 'file.Group') &&...
(prop.hasAnonData || prop.hasAnonGroups || prop.isConstrainedSet)) ||...
(isa(prop, 'file.Dataset') && prop.isConstrainedSet)
defaults{i} = 'types.untyped.Set()';
else
defaults{i} = '[]';
end
end
% add parameters
parser = [parser, strcat('addParameter(p, ''', names, ''', ', defaults,');')];
% parse
parser = [parser, {'misc.parseSkipInvalidName(p, varargin);'}];
% get results
parser = [parser, strcat('obj.', names, ' = p.Results.', names, ';')];
parser = strjoin(parser, newline);
bodystr(end+1:end+length(parser)+1) = [newline parser];
end

function checkTxt = fillCheck(name, namespace)
checkTxt = [];

% find if a dynamic table ancestry exists
ancestry = namespace.getRootBranch(name);
isDynamicTableDescendent = false;
for iAncestor = 1:length(ancestry)
ParentRaw = ancestry{iAncestor};
% this is always true, we just use the proper index as typedefs may vary.
typeDefInd = isKey(ParentRaw, namespace.TYPEDEF_KEYS);
isDynamicTableDescendent = isDynamicTableDescendent ...
|| strcmp('DynamicTable', ParentRaw(namespace.TYPEDEF_KEYS{typeDefInd}));
end
checkTxt = [];

% find if a dynamic table ancestry exists
ancestry = namespace.getRootBranch(name);
isDynamicTableDescendent = false;
for iAncestor = 1:length(ancestry)
ParentRaw = ancestry{iAncestor};
% this is always true, we just use the proper index as typedefs may vary.
typeDefInd = isKey(ParentRaw, namespace.TYPEDEF_KEYS);
isDynamicTableDescendent = isDynamicTableDescendent ...
|| strcmp('DynamicTable', ParentRaw(namespace.TYPEDEF_KEYS{typeDefInd}));
end

if ~isDynamicTableDescendent
return;
end
if ~isDynamicTableDescendent
return;
end

checkTxt = strjoin({ ...
sprintf('if strcmp(class(obj), ''%s'')', namespace.getFullClassName(name)), ...
' types.util.dynamictable.checkConfig(obj);', ...
'end',...
}, newline);
checkTxt = strjoin({ ...
sprintf('if strcmp(class(obj), ''%s'')', namespace.getFullClassName(name)), ...
' types.util.dynamictable.checkConfig(obj);', ...
'end',...
}, newline);
end
Loading

0 comments on commit 16a5411

Please sign in to comment.